diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index c08708b..fc82123 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -16,6 +16,7 @@ jobs: - name: Prepare tests run: | + sudo apt update sudo apt install ruby sudo gem install ronn pip install -U setuptools pip requests wheel Babel diff --git a/kosmorrolib/data.py b/kosmorrolib/data.py index 3d3de80..655d075 100644 --- a/kosmorrolib/data.py +++ b/kosmorrolib/data.py @@ -44,7 +44,9 @@ EVENTS = { 'OPPOSITION': {'message': _('%s is in opposition')}, 'CONJUNCTION': {'message': _('%s and %s are in conjunction')}, 'OCCULTATION': {'message': _('%s occults %s')}, - 'MAXIMAL_ELONGATION': {'message': _("%s's largest elongation")} + 'MAXIMAL_ELONGATION': {'message': _("%s's largest elongation")}, + 'MOON_PERIGEE': {'message': _("%s is at its perigee")}, + 'MOON_APOGEE': {'message': _("%s is at its apogee")}, } @@ -115,6 +117,9 @@ class Object(Serializable): self.skyfield_name = skyfield_name self.radius = radius + def __repr__(self): + return '' % (self.get_type(), self.name) + def get_skyfield_object(self) -> SkfPlanet: return get_skf_objects()[self.skyfield_name] @@ -176,6 +181,13 @@ class Event(Serializable): self.end_time = end_time self.details = details + def __repr__(self): + return '' % (self.event_type, + self.objects, + self.start_time, + self.end_time, + self.details) + def get_description(self, show_details: bool = True) -> str: description = EVENTS[self.event_type]['message'] % self._get_objects_name() if show_details and self.details is not None: diff --git a/kosmorrolib/events.py b/kosmorrolib/events.py index cfbf87a..8e7f19b 100644 --- a/kosmorrolib/events.py +++ b/kosmorrolib/events.py @@ -20,7 +20,7 @@ from datetime import date as date_type from skyfield.errors import EphemerisRangeError from skyfield.timelib import Time -from skyfield.searchlib import find_discrete, find_maxima +from skyfield.searchlib import find_discrete, find_maxima, find_minima from numpy import pi from .data import Event, Star, Planet, ASTERS @@ -135,6 +135,45 @@ def _search_maximal_elongations(start_time: Time, end_time: Time, timezone: int) return events +def _get_moon_distance(): + earth = get_skf_objects()['earth'] + moon = get_skf_objects()['moon'] + + def get_distance(time: Time): + earth_pos = earth.at(time) + moon_pos = earth_pos.observe(moon).apparent() + + return moon_pos.distance().au + + 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 = [] + + times, _ = find_maxima(start_time, end_time, f=_get_moon_distance(), epsilon=1./24/60) + + for time in times: + events.append(Event('MOON_APOGEE', [moon], translate_to_timezone(time.utc_datetime(), timezone))) + + return events + + +def _search_moon_perigee(start_time: Time, end_time: Time, timezone: int) -> [Event]: + moon = ASTERS[1] + events = [] + + times, _ = find_minima(start_time, end_time, f=_get_moon_distance(), epsilon=1./24/60) + + for time in times: + events.append(Event('MOON_PERIGEE', [moon], translate_to_timezone(time.utc_datetime(), timezone))) + + return events + + def search_events(date: date_type, timezone: int = 0) -> [Event]: start_time = get_timescale().utc(date.year, date.month, date.day, -timezone) end_time = get_timescale().utc(date.year, date.month, date.day + 1, -timezone) @@ -143,7 +182,9 @@ def search_events(date: date_type, timezone: int = 0) -> [Event]: return sorted(flatten_list([ _search_oppositions(start_time, end_time, timezone), _search_conjunction(start_time, end_time, timezone), - _search_maximal_elongations(start_time, end_time, timezone) + _search_maximal_elongations(start_time, end_time, timezone), + _search_moon_apogee(start_time, end_time, timezone), + _search_moon_perigee(start_time, end_time, timezone), ]), key=lambda event: event.start_time) except EphemerisRangeError as error: start_date = translate_to_timezone(error.start_time.utc_datetime(), timezone) diff --git a/kosmorrolib/locales/messages.pot b/kosmorrolib/locales/messages.pot index 01b58dd..3481155 100644 --- a/kosmorrolib/locales/messages.pot +++ b/kosmorrolib/locales/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: kosmorro 0.8.1\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-06-07 11:13+0200\n" +"POT-Creation-Date: 2020-12-01 12:01+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -83,43 +83,53 @@ msgstr "" msgid "%s's largest elongation" msgstr "" -#: kosmorrolib/data.py:262 +#: kosmorrolib/data.py:48 +#, python-format +msgid "%s is at its perigee" +msgstr "" + +#: kosmorrolib/data.py:49 +#, python-format +msgid "%s is at its apogee" +msgstr "" + +#: kosmorrolib/data.py:274 msgid "Sun" msgstr "" -#: kosmorrolib/data.py:263 +#: kosmorrolib/data.py:275 msgid "Moon" msgstr "" -#: kosmorrolib/data.py:264 +#: kosmorrolib/data.py:276 msgid "Mercury" msgstr "" -#: kosmorrolib/data.py:265 +#: kosmorrolib/data.py:277 msgid "Venus" msgstr "" -#: kosmorrolib/data.py:266 +#: kosmorrolib/data.py:278 msgid "Mars" msgstr "" -#: kosmorrolib/data.py:267 +#: kosmorrolib/data.py:279 msgid "Jupiter" msgstr "" -#: kosmorrolib/data.py:268 +#: kosmorrolib/data.py:280 msgid "Saturn" msgstr "" -#: kosmorrolib/data.py:269 +#: kosmorrolib/data.py:281 msgid "Uranus" msgstr "" -#: kosmorrolib/data.py:270 +#: kosmorrolib/data.py:282 msgid "Neptune" msgstr "" -#: kosmorrolib/data.py:271 +#: kosmorrolib/data.py:283 msgid "Pluto" msgstr "" diff --git a/manpage/kosmorro.7.md b/manpage/kosmorro.7.md index 20fecef..27e468b 100644 --- a/manpage/kosmorro.7.md +++ b/manpage/kosmorro.7.md @@ -7,6 +7,10 @@ The terms are given in an alphabetically order. ## TERMS +### Apogee + +The Moon is told being at its apogee when it is at its furthest point from the Earth. + ### Conjunction From the point of view of the Earth, two asters are said in conjunction when they are close together. @@ -27,6 +31,10 @@ An aster is said in opposition when it is positionned at the exact opposite of t For all the superior planets, it is the best moment to observe them, because they will be at the smallest distance to the Earth. Plus, they will appear full. For instance, Mars is in opposition when the angle Mars-Earth-Sun is equal to 180 degrees. +### Perigee + +The Moon is told being at its apogee when it is at its nearest point from the Earth. + ### Planet A planet is an aster that orbits around a star, is not a star itself, is massive enough to maintain it nearly round, and has cleaned its orbit from other massive objects. diff --git a/test/events.py b/test/events.py index fc78e88..9a4ce9c 100644 --- a/test/events.py +++ b/test/events.py @@ -1,6 +1,7 @@ import unittest from datetime import date, datetime +from json import dumps from kosmorrolib import events from kosmorrolib.data import Event, ASTERS @@ -27,29 +28,38 @@ class EventTestCase(unittest.TestCase): (date(2025, 1, 16), [Event('OPPOSITION', [ASTERS[4]], datetime(2025, 1, 16, 2, 38))]), - (date(2027, 2, 19), [Event('OPPOSITION', [ASTERS[4]], datetime(2027, 2, 19, 15, 50))]), + (date(2027, 2, 19), [Event('MOON_PERIGEE', [ASTERS[1]], datetime(2027, 2, 19, 7, 38)), + Event('OPPOSITION', [ASTERS[4]], datetime(2027, 2, 19, 15, 50))]), - (date(2020, 1, 2), [Event('CONJUNCTION', [ASTERS[2], ASTERS[5]], datetime(2020, 1, 2, 16, 41))]), + (date(2020, 1, 2), [Event('MOON_APOGEE', [ASTERS[1]], datetime(2020, 1, 2, 1, 32)), + Event('CONJUNCTION', [ASTERS[2], ASTERS[5]], datetime(2020, 1, 2, 16, 41))]), (date(2020, 1, 12), [Event('CONJUNCTION', [ASTERS[2], ASTERS[6]], datetime(2020, 1, 12, 9, 51)), Event('CONJUNCTION', [ASTERS[2], ASTERS[9]], datetime(2020, 1, 12, 10, 13)), Event('CONJUNCTION', [ASTERS[6], ASTERS[9]], datetime(2020, 1, 12, 16, 57))]), - (date(2020, 2, 10), [Event('MAXIMAL_ELONGATION', [ASTERS[2]], datetime(2020, 2, 10, 13, 46), details='18.2°')]), + (date(2020, 2, 10), [Event('MAXIMAL_ELONGATION', [ASTERS[2]], datetime(2020, 2, 10, 13, 46), details='18.2°'), + Event('MOON_PERIGEE', [ASTERS[1]], datetime(2020, 2, 10, 20, 34))]), (date(2020, 3, 24), [Event('MAXIMAL_ELONGATION', [ASTERS[2]], datetime(2020, 3, 24, 1, 56), details='27.8°'), + Event('MOON_APOGEE', [ASTERS[1]], datetime(2020, 3, 24, 15, 39)), Event('MAXIMAL_ELONGATION', [ASTERS[3]], datetime(2020, 3, 24, 21, 58), details='46.1°')]), - (date(2005, 6, 16), [Event('OCCULTATION', [ASTERS[1], ASTERS[5]], datetime(2005, 6, 16, 6, 31))]) + (date(2005, 6, 16), [Event('OCCULTATION', [ASTERS[1], ASTERS[5]], datetime(2005, 6, 16, 6, 31))]), + + (date(2020, 4, 7), [Event('MOON_PERIGEE', [ASTERS[1]], datetime(2020, 4, 7, 18, 14))]), + + (date(2020, 1, 29), [Event('MOON_APOGEE', [ASTERS[1]], datetime(2020, 1, 29, 21, 32))]) ) @data_provider(expected_events_provider) def test_search_events(self, d: date, expected_events: [Event]): actual_events = events.search_events(d) self.assertEqual(len(expected_events), len(actual_events), - 'Expected %d elements, got %d for date %s.' % (len(expected_events), - len(actual_events), - d.isoformat())) + '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]