A library that computes the ephemerides.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 

263 lignes
8.6 KiB

  1. #!/usr/bin/env python3
  2. from datetime import date as date_type
  3. from skyfield.errors import EphemerisRangeError
  4. from skyfield.timelib import Time
  5. from skyfield.searchlib import find_discrete, find_maxima, find_minima
  6. from numpy import pi
  7. from .data import Event, Star, Planet, ASTERS
  8. from .dateutil import translate_to_timezone
  9. from .enum import EventType
  10. from .exceptions import OutOfRangeDateError
  11. from .core import get_timescale, get_skf_objects, flatten_list
  12. def _search_conjunction(start_time: Time, end_time: Time, timezone: int) -> [Event]:
  13. earth = get_skf_objects()["earth"]
  14. aster1 = None
  15. aster2 = None
  16. def is_in_conjunction(time: Time):
  17. earth_pos = earth.at(time)
  18. _, aster1_lon, _ = (
  19. earth_pos.observe(aster1.get_skyfield_object()).apparent().ecliptic_latlon()
  20. )
  21. _, aster2_lon, _ = (
  22. earth_pos.observe(aster2.get_skyfield_object()).apparent().ecliptic_latlon()
  23. )
  24. return ((aster1_lon.radians - aster2_lon.radians) / pi % 2.0).astype(
  25. "int8"
  26. ) == 0
  27. is_in_conjunction.rough_period = 60.0
  28. computed = []
  29. conjunctions = []
  30. for aster1 in ASTERS:
  31. # Ignore the Sun
  32. if isinstance(aster1, Star):
  33. continue
  34. for aster2 in ASTERS:
  35. if isinstance(aster2, Star) or aster2 == aster1 or aster2 in computed:
  36. continue
  37. times, is_conjs = find_discrete(start_time, end_time, is_in_conjunction)
  38. for i, time in enumerate(times):
  39. if is_conjs[i]:
  40. aster1_pos = (aster1.get_skyfield_object() - earth).at(time)
  41. aster2_pos = (aster2.get_skyfield_object() - earth).at(time)
  42. distance = aster1_pos.separation_from(aster2_pos).degrees
  43. if distance - aster2.get_apparent_radius(
  44. time, earth
  45. ) < aster1.get_apparent_radius(time, earth):
  46. occulting_aster = (
  47. [aster1, aster2]
  48. if aster1_pos.distance().km < aster2_pos.distance().km
  49. else [aster2, aster1]
  50. )
  51. conjunctions.append(
  52. Event(
  53. EventType.OCCULTATION,
  54. occulting_aster,
  55. translate_to_timezone(time.utc_datetime(), timezone),
  56. )
  57. )
  58. else:
  59. conjunctions.append(
  60. Event(
  61. EventType.CONJUNCTION,
  62. [aster1, aster2],
  63. translate_to_timezone(time.utc_datetime(), timezone),
  64. )
  65. )
  66. computed.append(aster1)
  67. return conjunctions
  68. def _search_oppositions(start_time: Time, end_time: Time, timezone: int) -> [Event]:
  69. earth = get_skf_objects()["earth"]
  70. sun = get_skf_objects()["sun"]
  71. aster = None
  72. def is_oppositing(time: Time) -> [bool]:
  73. earth_pos = earth.at(time)
  74. sun_pos = earth_pos.observe(
  75. sun
  76. ).apparent() # Never do this without eyes protection!
  77. aster_pos = earth_pos.observe(get_skf_objects()[aster.skyfield_name]).apparent()
  78. _, lon1, _ = sun_pos.ecliptic_latlon()
  79. _, lon2, _ = aster_pos.ecliptic_latlon()
  80. return (lon1.degrees - lon2.degrees) > 180
  81. is_oppositing.rough_period = 1.0
  82. events = []
  83. for aster in ASTERS:
  84. if not isinstance(aster, Planet) or aster.skyfield_name in ["MERCURY", "VENUS"]:
  85. continue
  86. times, _ = find_discrete(start_time, end_time, is_oppositing)
  87. for time in times:
  88. events.append(
  89. Event(
  90. EventType.OPPOSITION,
  91. [aster],
  92. translate_to_timezone(time.utc_datetime(), timezone),
  93. )
  94. )
  95. return events
  96. def _search_maximal_elongations(
  97. start_time: Time, end_time: Time, timezone: int
  98. ) -> [Event]:
  99. earth = get_skf_objects()["earth"]
  100. sun = get_skf_objects()["sun"]
  101. aster = None
  102. def get_elongation(time: Time):
  103. sun_pos = (sun - earth).at(time)
  104. aster_pos = (aster.get_skyfield_object() - earth).at(time)
  105. separation = sun_pos.separation_from(aster_pos)
  106. return separation.degrees
  107. get_elongation.rough_period = 1.0
  108. events = []
  109. for aster in ASTERS:
  110. if aster.skyfield_name not in ["MERCURY", "VENUS"]:
  111. continue
  112. times, elongations = find_maxima(
  113. start_time, end_time, f=get_elongation, epsilon=1.0 / 24 / 3600, num=12
  114. )
  115. for i, time in enumerate(times):
  116. elongation = elongations[i]
  117. events.append(
  118. Event(
  119. EventType.MAXIMAL_ELONGATION,
  120. [aster],
  121. translate_to_timezone(time.utc_datetime(), timezone),
  122. details="{:.3n}°".format(elongation),
  123. )
  124. )
  125. return events
  126. def _get_moon_distance():
  127. earth = get_skf_objects()["earth"]
  128. moon = get_skf_objects()["moon"]
  129. def get_distance(time: Time):
  130. earth_pos = earth.at(time)
  131. moon_pos = earth_pos.observe(moon).apparent()
  132. return moon_pos.distance().au
  133. get_distance.rough_period = 1.0
  134. return get_distance
  135. def _search_moon_apogee(start_time: Time, end_time: Time, timezone: int) -> [Event]:
  136. moon = ASTERS[1]
  137. events = []
  138. times, _ = find_maxima(
  139. start_time, end_time, f=_get_moon_distance(), epsilon=1.0 / 24 / 60
  140. )
  141. for time in times:
  142. events.append(
  143. Event(
  144. EventType.MOON_APOGEE,
  145. [moon],
  146. translate_to_timezone(time.utc_datetime(), timezone),
  147. )
  148. )
  149. return events
  150. def _search_moon_perigee(start_time: Time, end_time: Time, timezone: int) -> [Event]:
  151. moon = ASTERS[1]
  152. events = []
  153. times, _ = find_minima(
  154. start_time, end_time, f=_get_moon_distance(), epsilon=1.0 / 24 / 60
  155. )
  156. for time in times:
  157. events.append(
  158. Event(
  159. EventType.MOON_PERIGEE,
  160. [moon],
  161. translate_to_timezone(time.utc_datetime(), timezone),
  162. )
  163. )
  164. return events
  165. def get_events(date: date_type = date_type.today(), timezone: int = 0) -> [Event]:
  166. """Calculate and return a list of events for the given date, adjusted to the given timezone if any.
  167. Find events that happen on April 4th, 2020 (show hours in UTC):
  168. >>> get_events(date_type(2020, 4, 4))
  169. [<Event type=CONJUNCTION objects=[<Object type=planet name=Mercury />, <Object type=planet name=Neptune />] start=2020-04-04 01:14:39.063308+00:00 end=None details=None />]
  170. Find events that happen on April 4th, 2020 (show timezones in UTC+2):
  171. >>> get_events(date_type(2020, 4, 4), 2)
  172. [<Event type=CONJUNCTION objects=[<Object type=planet name=Mercury />, <Object type=planet name=Neptune />] start=2020-04-04 03:14:39.063267+02:00 end=None details=None />]
  173. Find events that happen on April 3rd, 2020 (show timezones in UTC-2):
  174. >>> get_events(date_type(2020, 4, 3), -2)
  175. [<Event type=CONJUNCTION objects=[<Object type=planet name=Mercury />, <Object type=planet name=Neptune />] start=2020-04-03 23:14:39.063388-02:00 end=None details=None />]
  176. :param date: the date for which the events must be calculated
  177. :param timezone: the timezone to adapt the results to. If not given, defaults to 0.
  178. :return: a list of events found for the given date.
  179. """
  180. start_time = get_timescale().utc(date.year, date.month, date.day, -timezone)
  181. end_time = get_timescale().utc(date.year, date.month, date.day + 1, -timezone)
  182. try:
  183. found_events = []
  184. for fun in [
  185. _search_oppositions,
  186. _search_conjunction,
  187. _search_maximal_elongations,
  188. _search_moon_apogee,
  189. _search_moon_perigee,
  190. ]:
  191. found_events.append(fun(start_time, end_time, timezone))
  192. return sorted(flatten_list(found_events), key=lambda event: event.start_time)
  193. except EphemerisRangeError as error:
  194. start_date = translate_to_timezone(error.start_time.utc_datetime(), timezone)
  195. end_date = translate_to_timezone(error.end_time.utc_datetime(), timezone)
  196. start_date = date_type(start_date.year, start_date.month, start_date.day)
  197. end_date = date_type(end_date.year, end_date.month, end_date.day)
  198. raise OutOfRangeDateError(start_date, end_date) from error