BREAKING CHANGE: EventType constants `MOON_APOGEE` and `MOON_PERIGEE` have been renamed to `APOGEE` and `PERIGEE`pull/30/head
@@ -46,8 +46,8 @@ class EventType(Enum): | |||||
CONJUNCTION = 2 | CONJUNCTION = 2 | ||||
OCCULTATION = 3 | OCCULTATION = 3 | ||||
MAXIMAL_ELONGATION = 4 | MAXIMAL_ELONGATION = 4 | ||||
MOON_PERIGEE = 5 | |||||
MOON_APOGEE = 6 | |||||
PERIGEE = 5 | |||||
APOGEE = 6 | |||||
SEASON_CHANGE = 7 | SEASON_CHANGE = 7 | ||||
LUNAR_ECLIPSE = 8 | LUNAR_ECLIPSE = 8 | ||||
@@ -25,7 +25,7 @@ from skyfield.units import Angle | |||||
from skyfield import almanac, eclipselib | from skyfield import almanac, eclipselib | ||||
from numpy import pi | from numpy import pi | ||||
from kosmorrolib.model import Event, Star, Planet, ASTERS, EARTH | |||||
from kosmorrolib.model import Object, Event, Star, Planet, ASTERS, EARTH | |||||
from kosmorrolib.dateutil import translate_to_timezone | from kosmorrolib.dateutil import translate_to_timezone | ||||
from kosmorrolib.enum import EventType, ObjectIdentifier, SeasonType, LunarEclipseType | from kosmorrolib.enum import EventType, ObjectIdentifier, SeasonType, LunarEclipseType | ||||
from kosmorrolib.exceptions import OutOfRangeDateError | from kosmorrolib.exceptions import OutOfRangeDateError | ||||
@@ -225,59 +225,94 @@ def _search_maximal_elongations( | |||||
return events | return events | ||||
def _get_moon_distance(): | |||||
earth = get_skf_objects()["earth"] | |||||
moon = get_skf_objects()["moon"] | |||||
def _get_distance(to_aster: Object, from_aster: Object): | |||||
def get_distance(time: Time): | def get_distance(time: Time): | ||||
earth_pos = earth.at(time) | |||||
moon_pos = earth_pos.observe(moon).apparent() | |||||
from_pos = from_aster.get_skyfield_object().at(time) | |||||
to_pos = from_pos.observe(to_aster.get_skyfield_object()) | |||||
return moon_pos.distance().au | |||||
return to_pos.distance().km | |||||
get_distance.rough_period = 1.0 | get_distance.rough_period = 1.0 | ||||
return get_distance | return get_distance | ||||
def _search_moon_apogee(start_time: Time, end_time: Time, timezone: int) -> [Event]: | |||||
moon = ASTERS[1] | |||||
events = [] | |||||
def _search_apogee(to_aster: Object, from_aster: Object = EARTH) -> callable: | |||||
"""Search for moon apogee | |||||
times, _ = find_maxima( | |||||
start_time, end_time, f=_get_moon_distance(), epsilon=1.0 / 24 / 60 | |||||
) | |||||
**Warning:** this is an internal function, not intended for use by end-developers. | |||||
for time in times: | |||||
events.append( | |||||
Event( | |||||
EventType.MOON_APOGEE, | |||||
[moon], | |||||
translate_to_timezone(time.utc_datetime(), timezone), | |||||
) | |||||
Get the moon apogee: | |||||
>>> _search_apogee(ASTERS[1])(get_timescale().utc(2021, 6, 8), get_timescale().utc(2021, 6, 9), 0) | |||||
[<Event type=APOGEE objects=[<Object type=SATELLITE name=MOON />] start=2021-06-08 02:39:40.165271+00:00 end=None details={'distance_km': 406211.04850197025} />] | |||||
Get the Earth's apogee: | |||||
>>> _search_apogee(EARTH, from_aster=ASTERS[0])(get_timescale().utc(2021, 7, 5), get_timescale().utc(2021, 7, 6), 0) | |||||
[<Event type=APOGEE objects=[<Object type=PLANET name=EARTH />] start=2021-07-05 22:35:42.148792+00:00 end=None details={'distance_km': 152100521.91712126} />] | |||||
""" | |||||
def f(start_time: Time, end_time: Time, timezone: int) -> [Event]: | |||||
events = [] | |||||
times, distances = find_maxima( | |||||
start_time, | |||||
end_time, | |||||
f=_get_distance(to_aster, from_aster), | |||||
epsilon=1.0 / 24 / 60, | |||||
) | ) | ||||
return events | |||||
for i, time in enumerate(times): | |||||
events.append( | |||||
Event( | |||||
EventType.APOGEE, | |||||
[to_aster], | |||||
translate_to_timezone(time.utc_datetime(), timezone), | |||||
details={"distance_km": distances[i]}, | |||||
) | |||||
) | |||||
return events | |||||
def _search_moon_perigee(start_time: Time, end_time: Time, timezone: int) -> [Event]: | |||||
moon = ASTERS[1] | |||||
events = [] | |||||
return f | |||||
times, _ = find_minima( | |||||
start_time, end_time, f=_get_moon_distance(), epsilon=1.0 / 24 / 60 | |||||
) | |||||
for time in times: | |||||
events.append( | |||||
Event( | |||||
EventType.MOON_PERIGEE, | |||||
[moon], | |||||
translate_to_timezone(time.utc_datetime(), timezone), | |||||
) | |||||
def _search_perigee(aster: Object, from_aster: Object = EARTH) -> callable: | |||||
"""Search for moon perigee | |||||
**Warning:** this is an internal function, not intended for use by end-developers. | |||||
Get the moon perigee: | |||||
>>> _search_perigee(ASTERS[1])(get_timescale().utc(2021, 5, 26), get_timescale().utc(2021, 5, 27), 0) | |||||
[<Event type=PERIGEE objects=[<Object type=SATELLITE name=MOON />] start=2021-05-26 01:56:01.983455+00:00 end=None details={'distance_km': 357313.9680798693} />] | |||||
Get the Earth's perigee: | |||||
>>> _search_perigee(EARTH, from_aster=ASTERS[0])(get_timescale().utc(2021, 1, 2), get_timescale().utc(2021, 1, 3), 0) | |||||
[<Event type=PERIGEE objects=[<Object type=PLANET name=EARTH />] start=2021-01-02 13:59:00.495905+00:00 end=None details={'distance_km': 147093166.1686309} />] | |||||
""" | |||||
def f(start_time: Time, end_time: Time, timezone: int) -> [Event]: | |||||
events = [] | |||||
times, distances = find_minima( | |||||
start_time, | |||||
end_time, | |||||
f=_get_distance(aster, from_aster), | |||||
epsilon=1.0 / 24 / 60, | |||||
) | ) | ||||
return events | |||||
for i, time in enumerate(times): | |||||
events.append( | |||||
Event( | |||||
EventType.PERIGEE, | |||||
[aster], | |||||
translate_to_timezone(time.utc_datetime(), timezone), | |||||
details={"distance_km": distances[i]}, | |||||
) | |||||
) | |||||
return events | |||||
return f | |||||
def _search_earth_season_change( | def _search_earth_season_change( | ||||
@@ -437,8 +472,10 @@ def get_events(for_date: date = date.today(), timezone: int = 0) -> [Event]: | |||||
_search_oppositions, | _search_oppositions, | ||||
_search_conjunction, | _search_conjunction, | ||||
_search_maximal_elongations, | _search_maximal_elongations, | ||||
_search_moon_apogee, | |||||
_search_moon_perigee, | |||||
_search_apogee(ASTERS[1]), | |||||
_search_perigee(ASTERS[1]), | |||||
_search_apogee(EARTH, from_aster=ASTERS[0]), | |||||
_search_perigee(EARTH, from_aster=ASTERS[0]), | |||||
_search_earth_season_change, | _search_earth_season_change, | ||||
_search_lunar_eclipse, | _search_lunar_eclipse, | ||||
]: | ]: | ||||
@@ -19,6 +19,5 @@ | |||||
from .core import * | from .core import * | ||||
from .data import * | from .data import * | ||||
from .ephemerides import * | from .ephemerides import * | ||||
from .events import * | |||||
from .testutils import * | from .testutils import * | ||||
from .dateutil import * | from .dateutil import * |
@@ -1,172 +0,0 @@ | |||||
#!/usr/bin/env python3 | |||||
# Kosmorrolib - The Library To Compute Your Ephemerides | |||||
# Copyright (C) 2021 Jérôme Deuchnord <jerome@deuchnord.fr> | |||||
# | |||||
# This program is free software: you can redistribute it and/or modify | |||||
# it under the terms of the GNU Affero General Public License as | |||||
# published by the Free Software Foundation, either version 3 of the | |||||
# License, or (at your option) any later version. | |||||
# | |||||
# This program is distributed in the hope that it will be useful, | |||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
# GNU Affero General Public License for more details. | |||||
# | |||||
# You should have received a copy of the GNU Affero General Public License | |||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. | |||||
import unittest | |||||
from datetime import date, datetime | |||||
from parameterized import parameterized | |||||
from kosmorrolib import events | |||||
from kosmorrolib.model import Event, ASTERS | |||||
from kosmorrolib.enum import EventType | |||||
from kosmorrolib.exceptions import OutOfRangeDateError | |||||
EXPECTED_EVENTS = [ | |||||
(date(2020, 2, 7), []), | |||||
( | |||||
date(2020, 10, 13), | |||||
[Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2020, 10, 13, 23, 25))], | |||||
), | |||||
( | |||||
date(2022, 12, 8), | |||||
[ | |||||
Event( | |||||
EventType.CONJUNCTION, | |||||
[ASTERS[1], ASTERS[4]], | |||||
datetime(2022, 12, 8, 4, 18), | |||||
), | |||||
Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2022, 12, 8, 5, 41)), | |||||
], | |||||
), | |||||
( | |||||
date(2025, 1, 16), | |||||
[Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2025, 1, 16, 2, 38))], | |||||
), | |||||
( | |||||
date(2027, 2, 19), | |||||
[ | |||||
Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2027, 2, 19, 7, 38)), | |||||
Event(EventType.OPPOSITION, [ASTERS[4]], datetime(2027, 2, 19, 15, 50)), | |||||
], | |||||
), | |||||
( | |||||
date(2020, 1, 2), | |||||
[ | |||||
Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 1, 2, 1, 32)), | |||||
Event( | |||||
EventType.CONJUNCTION, | |||||
[ASTERS[2], ASTERS[5]], | |||||
datetime(2020, 1, 2, 16, 41), | |||||
), | |||||
], | |||||
), | |||||
( | |||||
date(2020, 1, 12), | |||||
[ | |||||
Event( | |||||
EventType.CONJUNCTION, | |||||
[ASTERS[2], ASTERS[6]], | |||||
datetime(2020, 1, 12, 9, 51), | |||||
), | |||||
Event( | |||||
EventType.CONJUNCTION, | |||||
[ASTERS[2], ASTERS[9]], | |||||
datetime(2020, 1, 12, 10, 13), | |||||
), | |||||
Event( | |||||
EventType.CONJUNCTION, | |||||
[ASTERS[6], ASTERS[9]], | |||||
datetime(2020, 1, 12, 16, 57), | |||||
), | |||||
], | |||||
), | |||||
( | |||||
date(2020, 2, 10), | |||||
[ | |||||
Event( | |||||
EventType.MAXIMAL_ELONGATION, | |||||
[ASTERS[2]], | |||||
datetime(2020, 2, 10, 13, 46), | |||||
details={"deg": 18.2}, | |||||
), | |||||
Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2020, 2, 10, 20, 34)), | |||||
], | |||||
), | |||||
( | |||||
date(2020, 3, 24), | |||||
[ | |||||
Event( | |||||
EventType.MAXIMAL_ELONGATION, | |||||
[ASTERS[2]], | |||||
datetime(2020, 3, 24, 1, 56), | |||||
details={"deg": 27.8}, | |||||
), | |||||
Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 3, 24, 15, 39)), | |||||
Event( | |||||
EventType.MAXIMAL_ELONGATION, | |||||
[ASTERS[3]], | |||||
datetime(2020, 3, 24, 21, 58), | |||||
details={"deg": 46.1}, | |||||
), | |||||
], | |||||
), | |||||
( | |||||
date(2005, 6, 16), | |||||
[ | |||||
Event( | |||||
EventType.OCCULTATION, | |||||
[ASTERS[1], ASTERS[5]], | |||||
datetime(2005, 6, 16, 6, 31), | |||||
) | |||||
], | |||||
), | |||||
( | |||||
date(2020, 4, 7), | |||||
[Event(EventType.MOON_PERIGEE, [ASTERS[1]], datetime(2020, 4, 7, 18, 14))], | |||||
), | |||||
( | |||||
date(2020, 1, 29), | |||||
[Event(EventType.MOON_APOGEE, [ASTERS[1]], datetime(2020, 1, 29, 21, 32))], | |||||
), | |||||
] | |||||
class EventTestCase(unittest.TestCase): | |||||
def setUp(self) -> None: | |||||
self.maxDiff = None | |||||
@parameterized.expand(EXPECTED_EVENTS) | |||||
def test_search_events(self, d: date, expected_events: [Event]): | |||||
actual_events = events.get_events(d) | |||||
self.assertEqual( | |||||
len(expected_events), | |||||
len(actual_events), | |||||
"Expected %d elements, got %d for date %s.\n%s" | |||||
% (len(expected_events), len(actual_events), d.isoformat(), actual_events), | |||||
) | |||||
for i, expected_event in enumerate(expected_events): | |||||
actual_event = actual_events[i] | |||||
# Remove unnecessary precision (seconds and microseconds) | |||||
actual_event.start_time = datetime( | |||||
actual_event.start_time.year, | |||||
actual_event.start_time.month, | |||||
actual_event.start_time.day, | |||||
actual_event.start_time.hour, | |||||
actual_event.start_time.minute, | |||||
) | |||||
self.assertEqual(expected_event.__dict__, actual_event.__dict__) | |||||
def test_get_events_raises_exception_on_out_of_date_range(self): | |||||
with self.assertRaises(OutOfRangeDateError): | |||||
events.get_events(date(1789, 5, 5)) | |||||
if __name__ == "__main__": | |||||
unittest.main() |