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.
 
 
 
 

125 line
5.3 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 skyfield.searchlib import find_discrete, find_maxima
  19. from skyfield.timelib import Time
  20. from skyfield.constants import tau
  21. from skyfield.errors import EphemerisRangeError
  22. from .data import Position, AsterEphemerides, MoonPhase, Object, ASTERS, skyfield_to_moon_phase
  23. from .dateutil import translate_to_timezone
  24. from .core import get_skf_objects, get_timescale, get_iau2000b
  25. from .exceptions import OutOfRangeDateError
  26. RISEN_ANGLE = -0.8333
  27. def get_moon_phase(compute_date: datetime.date, timezone: int = 0) -> MoonPhase:
  28. earth = get_skf_objects()['earth']
  29. moon = get_skf_objects()['moon']
  30. sun = get_skf_objects()['sun']
  31. def moon_phase_at(time: Time):
  32. time._nutation_angles = get_iau2000b(time)
  33. current_earth = earth.at(time)
  34. _, mlon, _ = current_earth.observe(moon).apparent().ecliptic_latlon('date')
  35. _, slon, _ = current_earth.observe(sun).apparent().ecliptic_latlon('date')
  36. return (((mlon.radians - slon.radians) // (tau / 8)) % 8).astype(int)
  37. moon_phase_at.rough_period = 7.0 # one lunar phase per week
  38. today = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day)
  39. time1 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day - 10)
  40. time2 = get_timescale().utc(compute_date.year, compute_date.month, compute_date.day + 10)
  41. try:
  42. times, phase = find_discrete(time1, time2, moon_phase_at)
  43. except EphemerisRangeError as error:
  44. start = translate_to_timezone(error.start_time.utc_datetime(), timezone)
  45. end = translate_to_timezone(error.end_time.utc_datetime(), timezone)
  46. start = datetime.date(start.year, start.month, start.day) + datetime.timedelta(days=12)
  47. end = datetime.date(end.year, end.month, end.day) - datetime.timedelta(days=12)
  48. raise OutOfRangeDateError(start, end)
  49. return skyfield_to_moon_phase(times, phase, today)
  50. def get_ephemerides(date: datetime.date, position: Position, timezone: int = 0) -> [AsterEphemerides]:
  51. ephemerides = []
  52. def get_angle(for_aster: Object):
  53. def fun(time: Time) -> float:
  54. return position.get_planet_topos().at(time).observe(for_aster.get_skyfield_object()).apparent().altaz()[0]\
  55. .degrees
  56. fun.rough_period = 1.0
  57. return fun
  58. def is_risen(for_aster: Object):
  59. def fun(time: Time) -> bool:
  60. return get_angle(for_aster)(time) > RISEN_ANGLE
  61. fun.rough_period = 0.5
  62. return fun
  63. start_time = get_timescale().utc(date.year, date.month, date.day, -timezone)
  64. end_time = get_timescale().utc(date.year, date.month, date.day, 23 - timezone, 59, 59)
  65. try:
  66. for aster in ASTERS:
  67. rise_times, arr = find_discrete(start_time, end_time, is_risen(aster))
  68. try:
  69. culmination_time, _ = find_maxima(start_time, end_time, f=get_angle(aster), epsilon=1./3600/24, num=12)
  70. culmination_time = culmination_time[0] if len(culmination_time) > 0 else None
  71. except ValueError:
  72. culmination_time = None
  73. if len(rise_times) == 2:
  74. rise_time = rise_times[0 if arr[0] else 1]
  75. set_time = rise_times[1 if not arr[1] else 0]
  76. else:
  77. rise_time = rise_times[0] if arr[0] else None
  78. set_time = rise_times[0] if not arr[0] else None
  79. # Convert the Time instances to Python datetime objects
  80. if rise_time is not None:
  81. rise_time = translate_to_timezone(rise_time.utc_datetime().replace(microsecond=0),
  82. to_tz=timezone)
  83. if culmination_time is not None:
  84. culmination_time = translate_to_timezone(culmination_time.utc_datetime().replace(microsecond=0),
  85. to_tz=timezone)
  86. if set_time is not None:
  87. set_time = translate_to_timezone(set_time.utc_datetime().replace(microsecond=0),
  88. to_tz=timezone)
  89. ephemerides.append(AsterEphemerides(rise_time, culmination_time, set_time, aster=aster))
  90. except EphemerisRangeError as error:
  91. start = translate_to_timezone(error.start_time.utc_datetime(), timezone)
  92. end = translate_to_timezone(error.end_time.utc_datetime(), timezone)
  93. start = datetime.date(start.year, start.month, start.day + 1)
  94. end = datetime.date(end.year, end.month, end.day - 1)
  95. raise OutOfRangeDateError(start, end)
  96. return ephemerides