BREAKING CHANGE: EventType constants `MOON_APOGEE` and `MOON_PERIGEE` have been renamed to `APOGEE` and `PERIGEE`tags/v1.0.0
@@ -46,8 +46,8 @@ class EventType(Enum): | |||
CONJUNCTION = 2 | |||
OCCULTATION = 3 | |||
MAXIMAL_ELONGATION = 4 | |||
MOON_PERIGEE = 5 | |||
MOON_APOGEE = 6 | |||
PERIGEE = 5 | |||
APOGEE = 6 | |||
SEASON_CHANGE = 7 | |||
LUNAR_ECLIPSE = 8 | |||
@@ -25,7 +25,7 @@ from skyfield.units import Angle | |||
from skyfield import almanac, eclipselib | |||
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.enum import EventType, ObjectIdentifier, SeasonType, LunarEclipseType | |||
from kosmorrolib.exceptions import OutOfRangeDateError | |||
@@ -225,59 +225,94 @@ def _search_maximal_elongations( | |||
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): | |||
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 | |||
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( | |||
@@ -437,8 +472,10 @@ def get_events(for_date: date = date.today(), timezone: int = 0) -> [Event]: | |||
_search_oppositions, | |||
_search_conjunction, | |||
_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_lunar_eclipse, | |||
]: | |||
@@ -19,6 +19,5 @@ | |||
from .core import * | |||
from .data import * | |||
from .ephemerides import * | |||
from .events import * | |||
from .testutils 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() |