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.
 
 

202 lignes
7.0 KiB

  1. #!/usr/bin/env python3
  2. # Kosmorro - Compute The Next Ephemerides
  3. # Copyright (C) 2019 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 .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS
  24. from .dateutil import translate_to_timezone
  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
  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. current_phase_time.utc_datetime() if current_phase_time is not None else None,
  62. next_phase_time.utc_datetime() if next_phase_time is not None else None,
  63. )
  64. def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhase:
  65. earth = get_skf_objects()["earth"]
  66. moon = get_skf_objects()["moon"]
  67. sun = get_skf_objects()["sun"]
  68. def moon_phase_at(time: Time):
  69. time._nutation_angles = get_iau2000b(time)
  70. current_earth = earth.at(time)
  71. _, mlon, _ = current_earth.observe(moon).apparent().ecliptic_latlon("date")
  72. _, slon, _ = current_earth.observe(sun).apparent().ecliptic_latlon("date")
  73. return (((mlon.radians - slon.radians) // (tau / 8)) % 8).astype(int)
  74. moon_phase_at.rough_period = 7.0 # one lunar phase per week
  75. today = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day)
  76. time1 = get_timescale().utc(
  77. compute_date.year, compute_date.month, compute_date.day - 10
  78. )
  79. time2 = get_timescale().utc(
  80. compute_date.year, compute_date.month, compute_date.day + 10
  81. )
  82. try:
  83. times, phase = find_discrete(time1, time2, moon_phase_at)
  84. except EphemerisRangeError as error:
  85. start = translate_to_timezone(error.start_time.utc_datetime(), timezone)
  86. end = translate_to_timezone(error.end_time.utc_datetime(), timezone)
  87. start = datetime.date(start.year, start.month, start.day) + datetime.timedelta(
  88. days=12
  89. )
  90. end = datetime.date(end.year, end.month, end.day) - datetime.timedelta(days=12)
  91. raise OutOfRangeDateError(start, end) from error
  92. return _get_skyfield_to_moon_phase(times, phase, today)
  93. def get_ephemerides(
  94. date: datetime.date, position: Position, timezone: int = 0
  95. ) -> [AsterEphemerides]:
  96. ephemerides = []
  97. def get_angle(for_aster: Object):
  98. def fun(time: Time) -> float:
  99. return (
  100. position.get_planet_topos()
  101. .at(time)
  102. .observe(for_aster.get_skyfield_object())
  103. .apparent()
  104. .altaz()[0]
  105. .degrees
  106. )
  107. fun.rough_period = 1.0
  108. return fun
  109. def is_risen(for_aster: Object):
  110. def fun(time: Time) -> bool:
  111. return get_angle(for_aster)(time) > RISEN_ANGLE
  112. fun.rough_period = 0.5
  113. return fun
  114. start_time = get_timescale().utc(date.year, date.month, date.day, -timezone)
  115. end_time = get_timescale().utc(
  116. date.year, date.month, date.day, 23 - timezone, 59, 59
  117. )
  118. try:
  119. for aster in ASTERS:
  120. rise_times, arr = find_discrete(start_time, end_time, is_risen(aster))
  121. try:
  122. culmination_time, _ = find_maxima(
  123. start_time,
  124. end_time,
  125. f=get_angle(aster),
  126. epsilon=1.0 / 3600 / 24,
  127. num=12,
  128. )
  129. culmination_time = (
  130. culmination_time[0] if len(culmination_time) > 0 else None
  131. )
  132. except ValueError:
  133. culmination_time = None
  134. if len(rise_times) == 2:
  135. rise_time = rise_times[0 if arr[0] else 1]
  136. set_time = rise_times[1 if not arr[1] else 0]
  137. else:
  138. rise_time = rise_times[0] if arr[0] else None
  139. set_time = rise_times[0] if not arr[0] else None
  140. # Convert the Time instances to Python datetime objects
  141. if rise_time is not None:
  142. rise_time = translate_to_timezone(
  143. rise_time.utc_datetime().replace(microsecond=0), to_tz=timezone
  144. )
  145. if culmination_time is not None:
  146. culmination_time = translate_to_timezone(
  147. culmination_time.utc_datetime().replace(microsecond=0),
  148. to_tz=timezone,
  149. )
  150. if set_time is not None:
  151. set_time = translate_to_timezone(
  152. set_time.utc_datetime().replace(microsecond=0), to_tz=timezone
  153. )
  154. ephemerides.append(
  155. AsterEphemerides(rise_time, culmination_time, set_time, aster=aster)
  156. )
  157. except EphemerisRangeError as error:
  158. start = translate_to_timezone(error.start_time.utc_datetime(), timezone)
  159. end = translate_to_timezone(error.end_time.utc_datetime(), timezone)
  160. start = datetime.date(start.year, start.month, start.day + 1)
  161. end = datetime.date(end.year, end.month, end.day - 1)
  162. raise OutOfRangeDateError(start, end) from error
  163. return ephemerides