A library that computes the ephemerides.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

253 lines
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