A library that computes the ephemerides.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

202 行
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