Add support for moon apogee and perigeepull/129/head
| @@ -16,6 +16,7 @@ jobs: | |||||
| - name: Prepare tests | - name: Prepare tests | ||||
| run: | | run: | | ||||
| sudo apt update | |||||
| sudo apt install ruby | sudo apt install ruby | ||||
| sudo gem install ronn | sudo gem install ronn | ||||
| pip install -U setuptools pip requests wheel Babel | pip install -U setuptools pip requests wheel Babel | ||||
| @@ -44,7 +44,9 @@ EVENTS = { | |||||
| 'OPPOSITION': {'message': _('%s is in opposition')}, | 'OPPOSITION': {'message': _('%s is in opposition')}, | ||||
| 'CONJUNCTION': {'message': _('%s and %s are in conjunction')}, | 'CONJUNCTION': {'message': _('%s and %s are in conjunction')}, | ||||
| 'OCCULTATION': {'message': _('%s occults %s')}, | '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.skyfield_name = skyfield_name | ||||
| self.radius = radius | self.radius = radius | ||||
| def __repr__(self): | |||||
| return '<Object type=%s name=%s />' % (self.get_type(), self.name) | |||||
| def get_skyfield_object(self) -> SkfPlanet: | def get_skyfield_object(self) -> SkfPlanet: | ||||
| return get_skf_objects()[self.skyfield_name] | return get_skf_objects()[self.skyfield_name] | ||||
| @@ -176,6 +181,13 @@ class Event(Serializable): | |||||
| self.end_time = end_time | self.end_time = end_time | ||||
| self.details = details | self.details = details | ||||
| def __repr__(self): | |||||
| return '<Event type=%s objects=[%s] start=%s end=%s details=%s>' % (self.event_type, | |||||
| self.objects, | |||||
| self.start_time, | |||||
| self.end_time, | |||||
| self.details) | |||||
| def get_description(self, show_details: bool = True) -> str: | def get_description(self, show_details: bool = True) -> str: | ||||
| description = EVENTS[self.event_type]['message'] % self._get_objects_name() | description = EVENTS[self.event_type]['message'] % self._get_objects_name() | ||||
| if show_details and self.details is not None: | if show_details and self.details is not None: | ||||
| @@ -20,7 +20,7 @@ from datetime import date as date_type | |||||
| from skyfield.errors import EphemerisRangeError | from skyfield.errors import EphemerisRangeError | ||||
| from skyfield.timelib import Time | 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 numpy import pi | ||||
| from .data import Event, Star, Planet, ASTERS | 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 | 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]: | def search_events(date: date_type, timezone: int = 0) -> [Event]: | ||||
| start_time = get_timescale().utc(date.year, date.month, date.day, -timezone) | 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) | 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([ | return sorted(flatten_list([ | ||||
| _search_oppositions(start_time, end_time, timezone), | _search_oppositions(start_time, end_time, timezone), | ||||
| _search_conjunction(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) | ]), key=lambda event: event.start_time) | ||||
| except EphemerisRangeError as error: | except EphemerisRangeError as error: | ||||
| start_date = translate_to_timezone(error.start_time.utc_datetime(), timezone) | start_date = translate_to_timezone(error.start_time.utc_datetime(), timezone) | ||||
| @@ -8,7 +8,7 @@ msgid "" | |||||
| msgstr "" | msgstr "" | ||||
| "Project-Id-Version: kosmorro 0.8.1\n" | "Project-Id-Version: kosmorro 0.8.1\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\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" | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
| @@ -83,43 +83,53 @@ msgstr "" | |||||
| msgid "%s's largest elongation" | msgid "%s's largest elongation" | ||||
| msgstr "" | 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" | msgid "Sun" | ||||
| msgstr "" | msgstr "" | ||||
| #: kosmorrolib/data.py:263 | |||||
| #: kosmorrolib/data.py:275 | |||||
| msgid "Moon" | msgid "Moon" | ||||
| msgstr "" | msgstr "" | ||||
| #: kosmorrolib/data.py:264 | |||||
| #: kosmorrolib/data.py:276 | |||||
| msgid "Mercury" | msgid "Mercury" | ||||
| msgstr "" | msgstr "" | ||||
| #: kosmorrolib/data.py:265 | |||||
| #: kosmorrolib/data.py:277 | |||||
| msgid "Venus" | msgid "Venus" | ||||
| msgstr "" | msgstr "" | ||||
| #: kosmorrolib/data.py:266 | |||||
| #: kosmorrolib/data.py:278 | |||||
| msgid "Mars" | msgid "Mars" | ||||
| msgstr "" | msgstr "" | ||||
| #: kosmorrolib/data.py:267 | |||||
| #: kosmorrolib/data.py:279 | |||||
| msgid "Jupiter" | msgid "Jupiter" | ||||
| msgstr "" | msgstr "" | ||||
| #: kosmorrolib/data.py:268 | |||||
| #: kosmorrolib/data.py:280 | |||||
| msgid "Saturn" | msgid "Saturn" | ||||
| msgstr "" | msgstr "" | ||||
| #: kosmorrolib/data.py:269 | |||||
| #: kosmorrolib/data.py:281 | |||||
| msgid "Uranus" | msgid "Uranus" | ||||
| msgstr "" | msgstr "" | ||||
| #: kosmorrolib/data.py:270 | |||||
| #: kosmorrolib/data.py:282 | |||||
| msgid "Neptune" | msgid "Neptune" | ||||
| msgstr "" | msgstr "" | ||||
| #: kosmorrolib/data.py:271 | |||||
| #: kosmorrolib/data.py:283 | |||||
| msgid "Pluto" | msgid "Pluto" | ||||
| msgstr "" | msgstr "" | ||||
| @@ -7,6 +7,10 @@ The terms are given in an alphabetically order. | |||||
| ## TERMS | ## TERMS | ||||
| ### Apogee | |||||
| The Moon is told being at its apogee when it is at its furthest point from the Earth. | |||||
| ### Conjunction | ### Conjunction | ||||
| From the point of view of the Earth, two asters are said in conjunction when they are close together. | 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 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. | 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 | ### 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. | 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. | ||||
| @@ -1,6 +1,7 @@ | |||||
| import unittest | import unittest | ||||
| from datetime import date, datetime | from datetime import date, datetime | ||||
| from json import dumps | |||||
| from kosmorrolib import events | from kosmorrolib import events | ||||
| from kosmorrolib.data import Event, ASTERS | 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(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)), | (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[2], ASTERS[9]], datetime(2020, 1, 12, 10, 13)), | ||||
| Event('CONJUNCTION', [ASTERS[6], ASTERS[9]], datetime(2020, 1, 12, 16, 57))]), | 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°'), | (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°')]), | 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) | @data_provider(expected_events_provider) | ||||
| def test_search_events(self, d: date, expected_events: [Event]): | def test_search_events(self, d: date, expected_events: [Event]): | ||||
| actual_events = events.search_events(d) | actual_events = events.search_events(d) | ||||
| self.assertEqual(len(expected_events), len(actual_events), | 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): | for i, expected_event in enumerate(expected_events): | ||||
| actual_event = actual_events[i] | actual_event = actual_events[i] | ||||