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.
 
 

271 lignes
18 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. from datetime import date, datetime, timedelta
  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. next_phase_time = None
  36. i = 0
  37. # Find the current moon phase:
  38. for i, time in enumerate(times):
  39. if now.utc_datetime() <= time.utc_datetime():
  40. if time.utc_datetime() >= tomorrow.utc_datetime():
  41. i -= 1
  42. break
  43. current_phase = MoonPhaseType(vals[i])
  44. if current_phase in [
  45. MoonPhaseType.NEW_MOON,
  46. MoonPhaseType.FIRST_QUARTER,
  47. MoonPhaseType.FULL_MOON,
  48. MoonPhaseType.LAST_QUARTER,
  49. ]:
  50. current_phase_time = translate_to_timezone(times[i].utc_datetime(), timezone)
  51. else:
  52. current_phase_time = None
  53. # Find the next moon phase
  54. for j in range(i + 1, len(times)):
  55. if vals[j] in [0, 2, 4, 6]:
  56. next_phase_time = translate_to_timezone(times[j].utc_datetime(), timezone)
  57. break
  58. return MoonPhase(current_phase, current_phase_time, next_phase_time)
  59. def get_moon_phase(for_date: date = date.today(), timezone: int = 0) -> MoonPhase:
  60. """Calculate and return the moon phase for the given date, adjusted to the given timezone if any.
  61. Get the moon phase for the 27 March, 2021:
  62. >>> get_moon_phase(date(2021, 3, 27))
  63. <MoonPhase phase_type=MoonPhaseType.WAXING_GIBBOUS time=None next_phase_date=2021-03-28 18:48:10.902298+00:00>
  64. When the moon phase is a new moon, a first quarter, a full moon or a last quarter, you get the exact time
  65. of its happening too:
  66. >>> get_moon_phase(datetime(2021, 3, 28))
  67. <MoonPhase phase_type=MoonPhaseType.FULL_MOON time=2021-03-28 18:48:10.902298+00:00 next_phase_date=2021-04-04 10:02:27.393689+00:00>
  68. Get the moon phase for the 27 March, 2021, in the UTC+2 timezone:
  69. >>> get_moon_phase(date(2021, 3, 27), timezone=2)
  70. <MoonPhase phase_type=MoonPhaseType.WAXING_GIBBOUS time=None next_phase_date=2021-03-28 20:48:10.902298+02:00>
  71. Note that the moon phase can only be computed for a date range.
  72. Asking for the moon phase with an out of range date will result in an exception:
  73. >>> get_moon_phase(date(1000, 1, 1))
  74. Traceback (most recent call last):
  75. ...
  76. kosmorrolib.exceptions.OutOfRangeDateError: The date must be between 1899-08-09 and 2053-09-26
  77. """
  78. earth = get_skf_objects()["earth"]
  79. moon = get_skf_objects()["moon"]
  80. sun = get_skf_objects()["sun"]
  81. def moon_phase_at(time: Time):
  82. time._nutation_angles = get_iau2000b(time)
  83. current_earth = earth.at(time)
  84. _, mlon, _ = current_earth.observe(moon).apparent().ecliptic_latlon("date")
  85. _, slon, _ = current_earth.observe(sun).apparent().ecliptic_latlon("date")
  86. return (((mlon.radians - slon.radians) // (tau / 8)) % 8).astype(int)
  87. moon_phase_at.rough_period = 7.0 # one lunar phase per week
  88. today = get_timescale().utc(for_date.year, for_date.month, for_date.day)
  89. start_time = get_timescale().utc(for_date.year, for_date.month, for_date.day - 10)
  90. end_time = get_timescale().utc(for_date.year, for_date.month, for_date.day + 10)
  91. try:
  92. times, phases = find_discrete(start_time, end_time, moon_phase_at)
  93. return _get_skyfield_to_moon_phase(times, phases, today, timezone)
  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 = date(start.year, start.month, start.day) + timedelta(days=12)
  98. end = date(end.year, end.month, end.day) - timedelta(days=12)
  99. raise OutOfRangeDateError(start, end) from error
  100. def get_ephemerides(
  101. position: Position, for_date: date = date.today(), timezone: int = 0
  102. ) -> [AsterEphemerides]:
  103. """Compute and return the ephemerides for the given position and date, adjusted to the given timezone if any.
  104. Compute the ephemerides for June 9th, 2021:
  105. >>> pos = Position(50.5824, 3.0624)
  106. >>> get_ephemerides(pos, date(2021, 6, 9))
  107. [<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 />>]
  108. Compute the ephemerides for June 9th, 2021:
  109. >>> get_ephemerides(pos, date(2021, 6, 9), timezone=2)
  110. [<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 />>]
  111. Objects may not rise or set on the given date (i.e. they rise the previous day or set the next day).
  112. When this happens, you will get `None` values on the rise or set time.
  113. Note that this is timezone-dependent:
  114. >>> get_ephemerides(Position(50.5876, 3.0624), date(2021, 9, 14), timezone=2)[1]
  115. <AsterEphemerides rise_time=2021-09-14 16:46:00 culmination_time=2021-09-14 20:29:00 set_time=None aster=<Object type=SATELLITE name=MOON />>
  116. If an objet does not rise nor set due to your latitude, then both rise and set will be `None`:
  117. >>> north_pole = Position(70, 20)
  118. >>> south_pole = Position(-70, 20)
  119. >>> get_ephemerides(north_pole, date(2021, 6, 20))
  120. [<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 />>]
  121. >>> get_ephemerides(north_pole, date(2021, 12, 21))
  122. [<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 />>]
  123. >>> get_ephemerides(south_pole, date(2021, 6, 20))
  124. [<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 />>]
  125. >>> get_ephemerides(south_pole, date(2021, 12, 22))
  126. [<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 />>]
  127. Note that the ephemerides can only be computed for a date range.
  128. Asking for the ephemerides with an out of range date will result in an exception:
  129. >>> get_ephemerides(pos, date(1000, 1, 1))
  130. Traceback (most recent call last):
  131. ...
  132. kosmorrolib.exceptions.OutOfRangeDateError: The date must be between 1899-07-29 and 2053-10-07
  133. """
  134. ephemerides = []
  135. def get_angle(for_aster: Object):
  136. def fun(time: Time) -> float:
  137. return (
  138. position.get_planet_topos()
  139. .at(time)
  140. .observe(for_aster.skyfield_object)
  141. .apparent()
  142. .altaz()[0]
  143. .degrees
  144. )
  145. fun.rough_period = 1.0
  146. return fun
  147. def is_risen(for_aster: Object):
  148. def fun(time: Time) -> bool:
  149. return get_angle(for_aster)(time) > RISEN_ANGLE
  150. fun.rough_period = 0.5
  151. return fun
  152. start_time = get_timescale().utc(
  153. for_date.year, for_date.month, for_date.day, -timezone
  154. )
  155. end_time = get_timescale().utc(
  156. for_date.year, for_date.month, for_date.day, 23 - timezone, 59, 59
  157. )
  158. try:
  159. for aster in ASTERS:
  160. rise_times, arr = find_discrete(start_time, end_time, is_risen(aster))
  161. culmination_time, _ = find_maxima(
  162. start_time,
  163. end_time,
  164. f=get_angle(aster),
  165. epsilon=1.0 / 3600 / 24,
  166. num=12,
  167. )
  168. if len(culmination_time) == 1:
  169. culmination_time = culmination_time[0]
  170. else:
  171. culmination_time = None
  172. rise_time, set_time = None, None
  173. if len(rise_times) == 2:
  174. rise_time = rise_times[0 if arr[0] else 1]
  175. set_time = rise_times[1 if not arr[1] else 0]
  176. elif len(rise_times) == 1:
  177. rise_time = rise_times[0] if arr[0] else None
  178. set_time = rise_times[0] if not arr[0] else None
  179. # Convert the Time instances to Python datetime objects
  180. if rise_time is not None:
  181. rise_time = normalize_datetime(
  182. translate_to_timezone(
  183. rise_time.utc_datetime().replace(microsecond=0), to_tz=timezone
  184. )
  185. )
  186. if culmination_time is not None:
  187. culmination_time = normalize_datetime(
  188. translate_to_timezone(
  189. culmination_time.utc_datetime().replace(microsecond=0),
  190. to_tz=timezone,
  191. )
  192. )
  193. if set_time is not None:
  194. set_time = normalize_datetime(
  195. translate_to_timezone(
  196. set_time.utc_datetime().replace(microsecond=0), to_tz=timezone
  197. )
  198. )
  199. ephemerides.append(
  200. AsterEphemerides(rise_time, culmination_time, set_time, aster=aster)
  201. )
  202. except EphemerisRangeError as error:
  203. start = translate_to_timezone(error.start_time.utc_datetime(), timezone)
  204. end = translate_to_timezone(error.end_time.utc_datetime(), timezone)
  205. start = date(start.year, start.month, start.day + 1)
  206. end = date(end.year, end.month, end.day - 1)
  207. raise OutOfRangeDateError(start, end) from error
  208. return ephemerides