Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 

225 rindas
8.7 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, start_time, end_time) -> dict:
  45. times, is_risen = almanac.find_discrete(start_time,
  46. end_time,
  47. almanac.sunrise_sunset(self.planets, self.position))
  48. sunrise = times[0] if is_risen[0] else times[1]
  49. sunset = times[1] if not is_risen[1] else times[0]
  50. return {'rise': sunrise, 'set': sunset}
  51. def get_moon(self, year, month, day) -> dict:
  52. time1 = self.timescale.utc(year, month, day - 10)
  53. time2 = self.timescale.utc(year, month, day)
  54. _, moon_phase = almanac.find_discrete(time1, time2, almanac.moon_phases(self.planets))
  55. return {'phase': moon_phase[-1]}
  56. def get_planets(self, year: int, month: int, day: int) -> dict:
  57. args = []
  58. for planet_name in self.PLANETS:
  59. args.append({'planet': planet_name,
  60. 'observer': {'latitude': self.latitude, 'longitude': self.longitude},
  61. 'year': year, 'month': month, 'day': day})
  62. with ThreadPool(4) as pool:
  63. planets = pool.map(Ephemeris.get_planet, args)
  64. obj = {}
  65. for planet in planets:
  66. obj[planet['name'].split(' ')[0]] = {'rise': planet['rise'], 'maximum': planet['maximum'],
  67. 'set': planet['set']}
  68. return obj
  69. @staticmethod
  70. def get_planet(p_obj) -> dict:
  71. load = Loader('./cache')
  72. planets = load('de421.bsp')
  73. timescale = load.timescale()
  74. position = Topos(latitude_degrees=p_obj['observer']['latitude'],
  75. longitude_degrees=p_obj['observer']['longitude'])
  76. observer = planets['earth'] + position
  77. planet = planets[p_obj['planet']]
  78. rise_time = maximum_time = set_time = None
  79. is_risen = None
  80. is_elevating = None
  81. last_is_elevating = None
  82. last_position = None
  83. for hours in range(0, 24):
  84. time = timescale.utc(p_obj['year'], p_obj['month'], p_obj['day'], hours)
  85. position = observer.at(time).observe(planet).apparent().altaz()[0].degrees
  86. if is_risen is None:
  87. is_risen = position > 0
  88. if last_position is not None:
  89. is_elevating = last_position < position
  90. if rise_time is None and not is_risen and is_elevating and position > 0:
  91. # Planet has risen in the last hour, let's look for a more precise time!
  92. for minutes in range(0, 60):
  93. time = timescale.utc(p_obj['year'], p_obj['month'], p_obj['day'], hours - 1, minutes)
  94. position = observer.at(time).observe(planet).apparent().altaz()[0].degrees
  95. if position > 0:
  96. # Planet has just risen!
  97. rise_time = time
  98. is_risen = True
  99. break
  100. if set_time is None and is_risen and not is_elevating and position < 0:
  101. # Planet has set in the last hour, let's look for a more precise time!
  102. for minutes in range(0, 60):
  103. time = timescale.utc(p_obj['year'], p_obj['month'], p_obj['day'], hours - 1, minutes)
  104. position = observer.at(time).observe(planet).apparent().altaz()[0].degrees
  105. if position < 0:
  106. # Planet has just set!
  107. set_time = time
  108. is_risen = False
  109. break
  110. if not is_elevating and last_is_elevating:
  111. # Planet has reached its azimuth in the last hour, let's look for a more precise time!
  112. for minutes in range(0, 60):
  113. time = timescale.utc(p_obj['year'], p_obj['month'], p_obj['day'], hours - 1, minutes)
  114. position = observer.at(time).observe(planet).apparent().altaz()[0].degrees
  115. maximum_time = time
  116. if last_position > position:
  117. # Planet has reached its azimuth!
  118. is_elevating = False
  119. break
  120. last_position = position
  121. last_position = position
  122. last_is_elevating = is_elevating
  123. if rise_time is not None and set_time is not None and maximum_time is not None:
  124. return {
  125. 'name': p_obj['planet'],
  126. 'rise': rise_time,
  127. 'maximum': maximum_time,
  128. 'set': set_time
  129. }
  130. return {
  131. 'name': p_obj['planet'],
  132. 'rise': rise_time if rise_time is not None else None,
  133. 'maximum': maximum_time if maximum_time is not None else None,
  134. 'set': set_time if set_time is not None else None
  135. }
  136. def compute_ephemerides_for_month(self, year: int, month: int) -> list:
  137. if month == 2:
  138. is_leap_year = (year % 4 == 0 and year % 100 > 0) or (year % 400 == 0)
  139. max_day = 29 if is_leap_year else 28
  140. elif month < 8:
  141. max_day = 30 if month % 2 == 0 else 31
  142. else:
  143. max_day = 31 if month % 2 == 0 else 30
  144. ephemerides = []
  145. for day in range(1, max_day + 1):
  146. ephemerides.append(self.compute_ephemeris_for_day(year, month, day))
  147. return ephemerides
  148. def compute_ephemerides_for_year(self, year: int) -> dict:
  149. ephemerides = {}
  150. for month in range(0, 12):
  151. ephemerides[self.MONTH[month]] = self.compute_ephemerides_for_month(year, month + 1)
  152. ephemerides['seasons'] = self.get_seasons(year)
  153. return ephemerides
  154. def get_seasons(self, year: int) -> dict:
  155. start_time = self.timescale.utc(year, 1, 1)
  156. end_time = self.timescale.utc(year, 12, 31)
  157. times, almanac_seasons = almanac.find_discrete(start_time, end_time, almanac.seasons(self.planets))
  158. seasons = {}
  159. for time, almanac_season in zip(times, almanac_seasons):
  160. if almanac_season == 0:
  161. season = 'MARCH'
  162. elif almanac_season == 1:
  163. season = 'JUNE'
  164. elif almanac_season == 2:
  165. season = 'SEPTEMBER'
  166. elif almanac_season == 3:
  167. season = 'DECEMBER'
  168. else:
  169. raise AssertionError
  170. seasons[season] = time.utc_iso()
  171. return seasons
  172. def compute_ephemeris(self, year: int, month: int, day: int):
  173. if day is not None:
  174. return self.compute_ephemeris_for_day(year, month, day)
  175. if month is not None:
  176. return self.compute_ephemerides_for_month(year, month)
  177. return self.compute_ephemerides_for_year(year)