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.
 
 

253 lignes
17 KiB

  1. #!/usr/bin/env python3
  2. # Kosmorrolib - The Library To Compute Your Ephemerides
  3. # Copyright (C) 2021 Jérôme Deuchnord <jerome@deuchnord.fr>
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU Affero General Public License as
  7. # published by the Free Software Foundation, either version 3 of the
  8. # License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU Affero General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Affero General Public License
  16. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. import datetime
  18. from typing import Union
  19. from skyfield.searchlib import find_discrete, find_maxima
  20. from skyfield.timelib import Time
  21. from skyfield.constants import tau
  22. from skyfield.errors import EphemerisRangeError
  23. from .model import Position, AsterEphemerides, MoonPhase, Object, ASTERS
  24. from .dateutil import translate_to_timezone, normalize_datetime
  25. from .core import get_skf_objects, get_timescale, get_iau2000b
  26. from .enum import MoonPhaseType
  27. from .exceptions import OutOfRangeDateError
  28. RISEN_ANGLE = -0.8333
  29. def _get_skyfield_to_moon_phase(
  30. times: [Time], vals: [int], now: Time, timezone: int
  31. ) -> Union[MoonPhase, None]:
  32. tomorrow = get_timescale().utc(
  33. now.utc_datetime().year, now.utc_datetime().month, now.utc_datetime().day + 1
  34. )
  35. phases = list(MoonPhaseType)
  36. current_phase = None
  37. current_phase_time = None
  38. next_phase_time = None
  39. i = 0
  40. if len(times) == 0:
  41. return None
  42. for i, time in enumerate(times):
  43. if now.utc_iso() <= time.utc_iso():
  44. if vals[i] in [0, 2, 4, 6]:
  45. if time.utc_datetime() < tomorrow.utc_datetime():
  46. current_phase_time = time
  47. current_phase = phases[vals[i]]
  48. else:
  49. i -= 1
  50. current_phase_time = None
  51. current_phase = phases[vals[i]]
  52. else:
  53. current_phase = phases[vals[i]]
  54. break
  55. for j in range(i + 1, len(times)):
  56. if vals[j] in [0, 2, 4, 6]:
  57. next_phase_time = times[j]
  58. break
  59. return MoonPhase(
  60. current_phase,
  61. translate_to_timezone(current_phase_time.utc_datetime(), timezone)
  62. if current_phase_time is not None
  63. else None,
  64. translate_to_timezone(next_phase_time.utc_datetime(), timezone)
  65. if next_phase_time is not None
  66. else None,
  67. )
  68. def get_moon_phase(
  69. for_date: datetime.date = datetime.date.today(), timezone: int = 0
  70. ) -> MoonPhase:
  71. """Calculate and return the moon phase for the given date, adjusted to the given timezone if any.
  72. Get the moon phase for the 27 March, 2021:
  73. >>> get_moon_phase(datetime.date.fromisoformat("2021-03-27"))
  74. <MoonPhase phase_type=MoonPhaseType.WAXING_GIBBOUS time=None next_phase_date=2021-03-28 18:48:10.902298+00:00>
  75. Get the moon phase for the 27 March, 2021, in the UTC+2 timezone:
  76. >>> get_moon_phase(datetime.date.fromisoformat("2021-03-27"), timezone=2)
  77. <MoonPhase phase_type=MoonPhaseType.WAXING_GIBBOUS time=None next_phase_date=2021-03-28 20:48:10.902298+02:00>
  78. """
  79. earth = get_skf_objects()["earth"]
  80. moon = get_skf_objects()["moon"]
  81. sun = get_skf_objects()["sun"]
  82. def moon_phase_at(time: Time):
  83. time._nutation_angles = get_iau2000b(time)
  84. current_earth = earth.at(time)
  85. _, mlon, _ = current_earth.observe(moon).apparent().ecliptic_latlon("date")
  86. _, slon, _ = current_earth.observe(sun).apparent().ecliptic_latlon("date")
  87. return (((mlon.radians - slon.radians) // (tau / 8)) % 8).astype(int)
  88. moon_phase_at.rough_period = 7.0 # one lunar phase per week
  89. today = get_timescale().utc(for_date.year, for_date.month, for_date.day)
  90. time1 = get_timescale().utc(for_date.year, for_date.month, for_date.day - 10)
  91. time2 = get_timescale().utc(for_date.year, for_date.month, for_date.day + 10)
  92. try:
  93. times, phase = find_discrete(time1, time2, moon_phase_at)
  94. except EphemerisRangeError as error:
  95. start = translate_to_timezone(error.start_time.utc_datetime(), timezone)
  96. end = translate_to_timezone(error.end_time.utc_datetime(), timezone)
  97. start = datetime.date(start.year, start.month, start.day) + datetime.timedelta(
  98. days=12
  99. )
  100. end = datetime.date(end.year, end.month, end.day) - datetime.timedelta(days=12)
  101. raise OutOfRangeDateError(start, end) from error
  102. return _get_skyfield_to_moon_phase(times, phase, today, timezone)
  103. def get_ephemerides(
  104. position: Position, date: datetime.date = datetime.date.today(), timezone: int = 0
  105. ) -> [AsterEphemerides]:
  106. """Compute and return the ephemerides for the given position and date, adjusted to the given timezone if any.
  107. Compute the ephemerides for June 9th, 2021:
  108. >>> pos = Position(50.5824, 3.0624)
  109. >>> get_ephemerides(pos, datetime.date(2021, 6, 9))
  110. [<AsterEphemerides rise_time=2021-06-09 03:36:00 culmination_time=2021-06-09 11:47:00 set_time=2021-06-09 19:58:00 aster=<Object type=STAR name=SUN />>, <AsterEphemerides rise_time=2021-06-09 02:59:00 culmination_time=2021-06-09 11:02:00 set_time=2021-06-09 19:16:00 aster=<Object type=SATELLITE name=MOON />>, <AsterEphemerides rise_time=2021-06-09 04:06:00 culmination_time=2021-06-09 11:58:00 set_time=2021-06-09 19:49:00 aster=<Object type=PLANET name=MERCURY />>, <AsterEphemerides rise_time=2021-06-09 04:52:00 culmination_time=2021-06-09 13:13:00 set_time=2021-06-09 21:34:00 aster=<Object type=PLANET name=VENUS />>, <AsterEphemerides rise_time=2021-06-09 06:38:00 culmination_time=2021-06-09 14:40:00 set_time=2021-06-09 22:41:00 aster=<Object type=PLANET name=MARS />>, <AsterEphemerides rise_time=2021-06-09 23:43:00 culmination_time=2021-06-09 04:54:00 set_time=2021-06-09 10:01:00 aster=<Object type=PLANET name=JUPITER />>, <AsterEphemerides rise_time=2021-06-09 23:02:00 culmination_time=2021-06-09 03:41:00 set_time=2021-06-09 08:16:00 aster=<Object type=PLANET name=SATURN />>, <AsterEphemerides rise_time=2021-06-09 01:56:00 culmination_time=2021-06-09 09:18:00 set_time=2021-06-09 16:40:00 aster=<Object type=PLANET name=URANUS />>, <AsterEphemerides rise_time=2021-06-09 00:27:00 culmination_time=2021-06-09 06:13:00 set_time=2021-06-09 11:59:00 aster=<Object type=PLANET name=NEPTUNE />>, <AsterEphemerides rise_time=2021-06-09 22:22:00 culmination_time=2021-06-09 02:32:00 set_time=2021-06-09 06:38:00 aster=<Object type=PLANET name=PLUTO />>]
  111. Compute the ephemerides for June 9th, 2021:
  112. >>> get_ephemerides(pos, datetime.date(2021, 6, 9), timezone=2)
  113. [<AsterEphemerides rise_time=2021-06-09 05:36:00 culmination_time=2021-06-09 13:47:00 set_time=2021-06-09 21:58:00 aster=<Object type=STAR name=SUN />>, <AsterEphemerides rise_time=2021-06-09 04:59:00 culmination_time=2021-06-09 13:02:00 set_time=2021-06-09 21:16:00 aster=<Object type=SATELLITE name=MOON />>, <AsterEphemerides rise_time=2021-06-09 06:06:00 culmination_time=2021-06-09 13:58:00 set_time=2021-06-09 21:49:00 aster=<Object type=PLANET name=MERCURY />>, <AsterEphemerides rise_time=2021-06-09 06:52:00 culmination_time=2021-06-09 15:13:00 set_time=2021-06-09 23:34:00 aster=<Object type=PLANET name=VENUS />>, <AsterEphemerides rise_time=2021-06-09 08:38:00 culmination_time=2021-06-09 16:40:00 set_time=2021-06-09 00:44:00 aster=<Object type=PLANET name=MARS />>, <AsterEphemerides rise_time=2021-06-09 01:47:00 culmination_time=2021-06-09 06:54:00 set_time=2021-06-09 12:01:00 aster=<Object type=PLANET name=JUPITER />>, <AsterEphemerides rise_time=2021-06-09 01:06:00 culmination_time=2021-06-09 05:41:00 set_time=2021-06-09 10:16:00 aster=<Object type=PLANET name=SATURN />>, <AsterEphemerides rise_time=2021-06-09 03:56:00 culmination_time=2021-06-09 11:18:00 set_time=2021-06-09 18:40:00 aster=<Object type=PLANET name=URANUS />>, <AsterEphemerides rise_time=2021-06-09 02:27:00 culmination_time=2021-06-09 08:13:00 set_time=2021-06-09 13:59:00 aster=<Object type=PLANET name=NEPTUNE />>, <AsterEphemerides rise_time=2021-06-09 00:26:00 culmination_time=2021-06-09 04:32:00 set_time=2021-06-09 08:38:00 aster=<Object type=PLANET name=PLUTO />>]
  114. If an objet does not rise nor set due to your latitude, then both rise and set will be `None`:
  115. >>> north_pole = Position(70, 20)
  116. >>> south_pole = Position(-70, 20)
  117. >>> get_ephemerides(north_pole, datetime.date(2021, 6, 20))
  118. [<AsterEphemerides rise_time=None culmination_time=2021-06-20 10:42:00 set_time=None aster=<Object type=STAR name=SUN />>, <AsterEphemerides rise_time=2021-06-20 14:30:00 culmination_time=2021-06-20 18:44:00 set_time=2021-06-20 22:53:00 aster=<Object type=SATELLITE name=MOON />>, <AsterEphemerides rise_time=2021-06-20 22:56:00 culmination_time=2021-06-20 09:47:00 set_time=2021-06-20 20:34:00 aster=<Object type=PLANET name=MERCURY />>, <AsterEphemerides rise_time=None culmination_time=2021-06-20 12:20:00 set_time=None aster=<Object type=PLANET name=VENUS />>, <AsterEphemerides rise_time=None culmination_time=2021-06-20 13:17:00 set_time=None aster=<Object type=PLANET name=MARS />>, <AsterEphemerides rise_time=2021-06-20 23:06:00 culmination_time=2021-06-20 03:04:00 set_time=2021-06-20 06:58:00 aster=<Object type=PLANET name=JUPITER />>, <AsterEphemerides rise_time=2021-06-20 23:28:00 culmination_time=2021-06-20 01:48:00 set_time=2021-06-20 04:05:00 aster=<Object type=PLANET name=SATURN />>, <AsterEphemerides rise_time=2021-06-20 21:53:00 culmination_time=2021-06-20 07:29:00 set_time=2021-06-20 17:02:00 aster=<Object type=PLANET name=URANUS />>, <AsterEphemerides rise_time=2021-06-20 22:51:00 culmination_time=2021-06-20 04:22:00 set_time=2021-06-20 09:50:00 aster=<Object type=PLANET name=NEPTUNE />>, <AsterEphemerides rise_time=None culmination_time=2021-06-20 00:40:00 set_time=None aster=<Object type=PLANET name=PLUTO />>]
  119. >>> get_ephemerides(north_pole, datetime.date(2021, 12, 21))
  120. [<AsterEphemerides rise_time=None culmination_time=2021-12-21 10:38:00 set_time=None aster=<Object type=STAR name=SUN />>, <AsterEphemerides rise_time=None culmination_time=2021-12-21 00:04:00 set_time=None aster=<Object type=SATELLITE name=MOON />>, <AsterEphemerides rise_time=None culmination_time=2021-12-21 11:33:00 set_time=None aster=<Object type=PLANET name=MERCURY />>, <AsterEphemerides rise_time=2021-12-21 11:58:00 culmination_time=2021-12-21 12:33:00 set_time=2021-12-21 13:08:00 aster=<Object type=PLANET name=VENUS />>, <AsterEphemerides rise_time=None culmination_time=2021-12-21 08:54:00 set_time=None aster=<Object type=PLANET name=MARS />>, <AsterEphemerides rise_time=2021-12-21 11:07:00 culmination_time=2021-12-21 14:43:00 set_time=2021-12-21 18:19:00 aster=<Object type=PLANET name=JUPITER />>, <AsterEphemerides rise_time=2021-12-21 11:32:00 culmination_time=2021-12-21 13:33:00 set_time=2021-12-21 15:33:00 aster=<Object type=PLANET name=SATURN />>, <AsterEphemerides rise_time=2021-12-21 09:54:00 culmination_time=2021-12-21 19:13:00 set_time=2021-12-21 04:37:00 aster=<Object type=PLANET name=URANUS />>, <AsterEphemerides rise_time=2021-12-21 10:49:00 culmination_time=2021-12-21 16:05:00 set_time=2021-12-21 21:21:00 aster=<Object type=PLANET name=NEPTUNE />>, <AsterEphemerides rise_time=None culmination_time=2021-12-21 12:31:00 set_time=None aster=<Object type=PLANET name=PLUTO />>]
  121. >>> get_ephemerides(south_pole, datetime.date(2021, 6, 20))
  122. [<AsterEphemerides rise_time=None culmination_time=2021-06-20 10:42:00 set_time=None aster=<Object type=STAR name=SUN />>, <AsterEphemerides rise_time=2021-06-20 11:10:00 culmination_time=2021-06-20 19:06:00 set_time=2021-06-20 01:20:00 aster=<Object type=SATELLITE name=MOON />>, <AsterEphemerides rise_time=2021-06-20 07:47:00 culmination_time=2021-06-20 09:47:00 set_time=2021-06-20 11:48:00 aster=<Object type=PLANET name=MERCURY />>, <AsterEphemerides rise_time=None culmination_time=2021-06-20 12:20:00 set_time=None aster=<Object type=PLANET name=VENUS />>, <AsterEphemerides rise_time=2021-06-20 12:14:00 culmination_time=2021-06-20 13:17:00 set_time=2021-06-20 14:21:00 aster=<Object type=PLANET name=MARS />>, <AsterEphemerides rise_time=2021-06-20 18:32:00 culmination_time=2021-06-20 03:04:00 set_time=2021-06-20 11:32:00 aster=<Object type=PLANET name=JUPITER />>, <AsterEphemerides rise_time=2021-06-20 15:20:00 culmination_time=2021-06-20 01:48:00 set_time=2021-06-20 12:12:00 aster=<Object type=PLANET name=SATURN />>, <AsterEphemerides rise_time=2021-06-20 04:32:00 culmination_time=2021-06-20 07:29:00 set_time=2021-06-20 10:26:00 aster=<Object type=PLANET name=URANUS />>, <AsterEphemerides rise_time=2021-06-20 21:28:00 culmination_time=2021-06-20 04:22:00 set_time=2021-06-20 11:13:00 aster=<Object type=PLANET name=NEPTUNE />>, <AsterEphemerides rise_time=None culmination_time=2021-06-20 00:40:00 set_time=None aster=<Object type=PLANET name=PLUTO />>]
  123. >>> get_ephemerides(south_pole, datetime.date(2021, 12, 22))
  124. [<AsterEphemerides rise_time=None culmination_time=2021-12-22 10:39:00 set_time=None aster=<Object type=STAR name=SUN />>, <AsterEphemerides rise_time=None culmination_time=2021-12-22 01:01:00 set_time=None aster=<Object type=SATELLITE name=MOON />>, <AsterEphemerides rise_time=None culmination_time=2021-12-22 11:35:00 set_time=None aster=<Object type=PLANET name=MERCURY />>, <AsterEphemerides rise_time=None culmination_time=2021-12-22 12:27:00 set_time=None aster=<Object type=PLANET name=VENUS />>, <AsterEphemerides rise_time=None culmination_time=2021-12-22 08:53:00 set_time=None aster=<Object type=PLANET name=MARS />>, <AsterEphemerides rise_time=2021-12-22 05:52:00 culmination_time=2021-12-22 14:40:00 set_time=2021-12-22 23:26:00 aster=<Object type=PLANET name=JUPITER />>, <AsterEphemerides rise_time=2021-12-22 02:41:00 culmination_time=2021-12-22 13:29:00 set_time=2021-12-22 00:21:00 aster=<Object type=PLANET name=SATURN />>, <AsterEphemerides rise_time=2021-12-22 16:01:00 culmination_time=2021-12-22 19:09:00 set_time=2021-12-22 22:17:00 aster=<Object type=PLANET name=URANUS />>, <AsterEphemerides rise_time=2021-12-22 08:59:00 culmination_time=2021-12-22 16:01:00 set_time=2021-12-22 23:04:00 aster=<Object type=PLANET name=NEPTUNE />>, <AsterEphemerides rise_time=None culmination_time=2021-12-22 12:27:00 set_time=None aster=<Object type=PLANET name=PLUTO />>]
  125. """
  126. ephemerides = []
  127. def get_angle(for_aster: Object):
  128. def fun(time: Time) -> float:
  129. return (
  130. position.get_planet_topos()
  131. .at(time)
  132. .observe(for_aster.get_skyfield_object())
  133. .apparent()
  134. .altaz()[0]
  135. .degrees
  136. )
  137. fun.rough_period = 1.0
  138. return fun
  139. def is_risen(for_aster: Object):
  140. def fun(time: Time) -> bool:
  141. return get_angle(for_aster)(time) > RISEN_ANGLE
  142. fun.rough_period = 0.5
  143. return fun
  144. start_time = get_timescale().utc(date.year, date.month, date.day, -timezone)
  145. end_time = get_timescale().utc(
  146. date.year, date.month, date.day, 23 - timezone, 59, 59
  147. )
  148. try:
  149. for aster in ASTERS:
  150. rise_times, arr = find_discrete(start_time, end_time, is_risen(aster))
  151. try:
  152. culmination_time, _ = find_maxima(
  153. start_time,
  154. end_time,
  155. f=get_angle(aster),
  156. epsilon=1.0 / 3600 / 24,
  157. num=12,
  158. )
  159. culmination_time = (
  160. culmination_time[0] if len(culmination_time) > 0 else None
  161. )
  162. except ValueError:
  163. culmination_time = None
  164. rise_time, set_time = None, None
  165. if len(rise_times) == 2:
  166. rise_time = rise_times[0 if arr[0] else 1]
  167. set_time = rise_times[1 if not arr[1] else 0]
  168. elif len(rise_times) == 1:
  169. rise_time = rise_times[0] if arr[0] else None
  170. set_time = rise_times[0] if not arr[0] else None
  171. # Convert the Time instances to Python datetime objects
  172. if rise_time is not None:
  173. rise_time = normalize_datetime(
  174. translate_to_timezone(
  175. rise_time.utc_datetime().replace(microsecond=0), to_tz=timezone
  176. )
  177. )
  178. if culmination_time is not None:
  179. culmination_time = normalize_datetime(
  180. translate_to_timezone(
  181. culmination_time.utc_datetime().replace(microsecond=0),
  182. to_tz=timezone,
  183. )
  184. )
  185. if set_time is not None:
  186. set_time = normalize_datetime(
  187. translate_to_timezone(
  188. set_time.utc_datetime().replace(microsecond=0), to_tz=timezone
  189. )
  190. )
  191. ephemerides.append(
  192. AsterEphemerides(rise_time, culmination_time, set_time, aster=aster)
  193. )
  194. except EphemerisRangeError as error:
  195. start = translate_to_timezone(error.start_time.utc_datetime(), timezone)
  196. end = translate_to_timezone(error.end_time.utc_datetime(), timezone)
  197. start = datetime.date(start.year, start.month, start.day + 1)
  198. end = datetime.date(end.year, end.month, end.day - 1)
  199. raise OutOfRangeDateError(start, end) from error
  200. return ephemerides