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 | 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() | |||||