Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 

223 Zeilen
8.4 KiB

  1. # Kosmorro - Compute The Next Ephemeris
  2. # Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as
  6. # published by the Free Software Foundation, either version 3 of the
  7. # License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. from multiprocessing import Pool as ThreadPool
  17. from skyfield.api import Loader, Topos
  18. from skyfield import almanac
  19. class Ephemeris:
  20. MONTH = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
  21. PLANETS = ['MERCURY', 'VENUS', 'MARS', 'JUPITER BARYCENTER', 'SATURN BARYCENTER', 'URANUS BARYCENTER',
  22. 'NEPTUNE BARYCENTER', 'PLUTO BARYCENTER']
  23. position = None
  24. latitude = None
  25. longitude = None
  26. timescale = None
  27. planets = None
  28. def __init__(self, position):
  29. load = Loader('./cache')
  30. self.timescale = load.timescale()
  31. self.planets = load('de421.bsp')
  32. self.latitude = position['lat']
  33. self.longitude = position['lon']
  34. self.position = Topos(latitude_degrees=position['lat'], longitude_degrees=position['lon'])
  35. def compute_ephemeris_for_day(self, year: int, month: int, day: int) -> dict:
  36. ephemeris = {}
  37. time1 = self.timescale.utc(year, month, day, 2)
  38. time2 = self.timescale.utc(year, month, day + 1, 2)
  39. # Compute sunrise and sunset
  40. ephemeris['sun'] = self.get_sun(time1, time2)
  41. ephemeris['moon'] = self.get_moon(year, month, day)
  42. ephemeris['planets'] = self.get_planets(year, month, day)
  43. return ephemeris
  44. def get_sun(self, time1, time2) -> dict:
  45. t, y = almanac.find_discrete(time1, time2, almanac.sunrise_sunset(self.planets, self.position))
  46. sunrise = t[0] if y[0] else t[1]
  47. sunset = t[1] if not y[1] else t[0]
  48. return {'rise': sunrise, 'set': sunset}
  49. def get_moon(self, year, month, day) -> dict:
  50. time1 = self.timescale.utc(year, month, day - 10)
  51. time2 = self.timescale.utc(year, month, day)
  52. _, y = almanac.find_discrete(time1, time2, almanac.moon_phases(self.planets))
  53. return {'phase': y[-1]}
  54. def get_planets(self, year: int, month: int, day: int) -> dict:
  55. args = []
  56. for planet_name in self.PLANETS:
  57. args.append({'planet': planet_name,
  58. 'observer': {'latitude': self.latitude, 'longitude': self.longitude},
  59. 'year': year, 'month': month, 'day': day})
  60. with ThreadPool(4) as pool:
  61. planets = pool.map(Ephemeris.get_planet, args)
  62. obj = {}
  63. for planet in planets:
  64. obj[planet['name'].split(' ')[0]] = {'rise': planet['rise'], 'maximum': planet['maximum'],
  65. 'set': planet['set']}
  66. return obj
  67. @staticmethod
  68. def get_planet(o) -> dict:
  69. load = Loader('./cache')
  70. planets = load('de421.bsp')
  71. timescale = load.timescale()
  72. position = Topos(latitude_degrees=o['observer']['latitude'], longitude_degrees=o['observer']['longitude'])
  73. observer = planets['earth'] + position
  74. planet = planets[o['planet']]
  75. rise_time = maximum_time = set_time = None
  76. max_elevation = None
  77. is_risen = None
  78. is_elevating = None
  79. last_is_elevating = None
  80. last_position = None
  81. for hours in range(0, 24):
  82. time = timescale.utc(o['year'], o['month'], o['day'], hours)
  83. position = observer.at(time).observe(planet).apparent().altaz()[0].degrees
  84. if is_risen is None:
  85. is_risen = position > 0
  86. if last_position is not None:
  87. is_elevating = last_position < position
  88. if rise_time is None and not is_risen and is_elevating and position > 0:
  89. # Planet has risen in the last hour, let's look for a more precise time!
  90. for minutes in range(0, 60):
  91. time = timescale.utc(o['year'], o['month'], o['day'], hours - 1, minutes)
  92. position = observer.at(time).observe(planet).apparent().altaz()[0].degrees
  93. if position > 0:
  94. # Planet has just risen!
  95. rise_time = time
  96. is_risen = True
  97. break
  98. if set_time is None and is_risen and not is_elevating and position < 0:
  99. # Planet has set in the last hour, let's look for a more precise time!
  100. for minutes in range(0, 60):
  101. time = timescale.utc(o['year'], o['month'], o['day'], hours - 1, minutes)
  102. position = observer.at(time).observe(planet).apparent().altaz()[0].degrees
  103. if position < 0:
  104. # Planet has just set!
  105. set_time = time
  106. is_risen = False
  107. break
  108. if not is_elevating and last_is_elevating:
  109. # Planet has reached its azimuth in the last hour, let's look for a more precise time!
  110. for minutes in range(0, 60):
  111. time = timescale.utc(o['year'], o['month'], o['day'], hours - 1, minutes)
  112. position = observer.at(time).observe(planet).apparent().altaz()[0].degrees
  113. max_elevation = position
  114. maximum_time = time
  115. if last_position > position:
  116. # Planet has reached its azimuth!
  117. is_elevating = False
  118. break
  119. last_position = position
  120. last_position = position
  121. last_is_elevating = is_elevating
  122. if rise_time is not None and set_time is not None and maximum_time is not None:
  123. return {
  124. 'name': o['planet'],
  125. 'rise': rise_time,
  126. 'maximum': maximum_time,
  127. 'set': set_time
  128. }
  129. return {
  130. 'name': o['planet'],
  131. 'rise': rise_time if rise_time is not None else None,
  132. 'maximum': maximum_time if maximum_time is not None else None,
  133. 'set': set_time if set_time is not None else None
  134. }
  135. def compute_ephemeris_for_month(self, year: int, month: int) -> list:
  136. if month == 2:
  137. is_leap_year = (year % 4 == 0 and year % 100 > 0) or (year % 400 == 0)
  138. max_day = 29 if is_leap_year else 28
  139. elif month < 8:
  140. max_day = 30 if month % 2 == 0 else 31
  141. else:
  142. max_day = 31 if month % 2 == 0 else 30
  143. e = []
  144. for day in range(1, max_day + 1):
  145. e.append(self.compute_ephemeris_for_day(year, month, day))
  146. return e
  147. def compute_ephemeris_for_year(self, year: int) -> dict:
  148. e = {}
  149. for month in range(0, 12):
  150. e[self.MONTH[month]] = self.compute_ephemeris_for_month(year, month + 1)
  151. e['seasons'] = self.get_seasons(year)
  152. return e
  153. def get_seasons(self, year: int) -> dict:
  154. t1 = self.timescale.utc(year, 1, 1)
  155. t2 = self.timescale.utc(year, 12, 31)
  156. t, y = almanac.find_discrete(t1, t2, almanac.seasons(self.planets))
  157. seasons = {}
  158. for ti, yi in zip(t, y):
  159. if yi == 0:
  160. season = 'MARCH'
  161. elif yi == 1:
  162. season = 'JUNE'
  163. elif yi == 2:
  164. season = 'SEPTEMBER'
  165. elif yi == 3:
  166. season = 'DECEMBER'
  167. else:
  168. raise AssertionError
  169. seasons[season] = ti.utc_iso()
  170. return seasons
  171. def compute_ephemeris(self, year: int, month: int, day: int):
  172. if day is not None:
  173. return self.compute_ephemeris_for_day(year, month, day)
  174. elif month is not None:
  175. return self.compute_ephemeris_for_month(year, month)
  176. else:
  177. return self.compute_ephemeris_for_year(year)