@@ -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] | ||||