From aada26758659d4fc323df625d2e00fd7ee7932d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Deuchnord?= Date: Sun, 29 Aug 2021 17:06:38 +0200 Subject: [PATCH] feat: add support for Earth perihelion and aphelion BREAKING CHANGE: EventType constants `MOON_APOGEE` and `MOON_PERIGEE` have been renamed to `APOGEE` and `PERIGEE` --- kosmorrolib/enum.py | 4 +- kosmorrolib/events.py | 113 +++++++++++++++++---------- tests/__init__.py | 1 - tests/events.py | 172 ------------------------------------------ 4 files changed, 77 insertions(+), 213 deletions(-) delete mode 100644 tests/events.py diff --git a/kosmorrolib/enum.py b/kosmorrolib/enum.py index fb184d2..9695a07 100644 --- a/kosmorrolib/enum.py +++ b/kosmorrolib/enum.py @@ -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 diff --git a/kosmorrolib/events.py b/kosmorrolib/events.py index 1313a74..6b4f9c8 100644 --- a/kosmorrolib/events.py +++ b/kosmorrolib/events.py @@ -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) + [] 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) + [] 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) + [] 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) + [] 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, ]: diff --git a/tests/__init__.py b/tests/__init__.py index 8c85a0d..098ddc8 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -19,6 +19,5 @@ from .core import * from .data import * from .ephemerides import * -from .events import * from .testutils import * from .dateutil import * diff --git a/tests/events.py b/tests/events.py deleted file mode 100644 index 05ab272..0000000 --- a/tests/events.py +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env python3 - -# Kosmorrolib - The Library To Compute Your Ephemerides -# Copyright (C) 2021 Jérôme Deuchnord -# -# 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 . - -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()