From b8d6ae2f510b384ecbc0a960bea764e96ec656a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Deuchnord?= Date: Sun, 29 Mar 2020 13:56:19 +0200 Subject: [PATCH] feat: add support for occultations --- kosmorrolib/data.py | 40 +++++++++++----- kosmorrolib/dumper.py | 1 + kosmorrolib/events.py | 13 +++++- kosmorrolib/locales/messages.pot | 79 +++++++++++++++++--------------- test/__init__.py | 4 +- test/data.py | 17 +++++++ test/events.py | 2 + 7 files changed, 105 insertions(+), 51 deletions(-) create mode 100644 test/data.py diff --git a/kosmorrolib/data.py b/kosmorrolib/data.py index ac50df7..8aee11f 100644 --- a/kosmorrolib/data.py +++ b/kosmorrolib/data.py @@ -20,6 +20,8 @@ from abc import ABC, abstractmethod from typing import Union from datetime import datetime +from numpy import pi, arcsin + from skyfield.api import Topos, Time from skyfield.vectorlib import VectorSum as SkfPlanet @@ -40,6 +42,7 @@ MOON_PHASES = { 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")} } @@ -106,16 +109,19 @@ class Object(ABC): def __init__(self, name: str, skyfield_name: str, - ephemerides: AsterEphemerides or None = None): + ephemerides: AsterEphemerides or None = None, + radius: float = None): """ Initialize an astronomical object :param str name: the official name of the object (may be internationalized) :param str skyfield_name: the internal name of the object in Skyfield library + :param float radius: the radius (in km) of the object :param AsterEphemerides ephemerides: the ephemerides associated to the object """ self.name = name self.skyfield_name = skyfield_name + self.radius = radius self.ephemerides = ephemerides def get_skyfield_object(self) -> SkfPlanet: @@ -125,6 +131,18 @@ class Object(ABC): def get_type(self) -> str: pass + def get_apparent_radius(self, time: Time, from_place) -> float: + """ + Calculate the apparent radius, in degrees, of the object from the given place at a given time. + :param time: + :param from_place: + :return: + """ + if self.radius is None: + raise ValueError('Missing radius for %s object' % self.name) + + return 360 / pi * arcsin(self.radius / from_place.at(time).observe(self.get_skyfield_object()).distance().km) + class Star(Object): def get_type(self) -> str: @@ -212,13 +230,13 @@ def skyfield_to_moon_phase(times: [Time], vals: [int], now: Time) -> Union[MoonP MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] -ASTERS = [Star(_('Sun'), 'SUN'), - Satellite(_('Moon'), 'MOON'), - Planet(_('Mercury'), 'MERCURY'), - Planet(_('Venus'), 'VENUS'), - Planet(_('Mars'), 'MARS'), - Planet(_('Jupiter'), 'JUPITER BARYCENTER'), - Planet(_('Saturn'), 'SATURN BARYCENTER'), - Planet(_('Uranus'), 'URANUS BARYCENTER'), - Planet(_('Neptune'), 'NEPTUNE BARYCENTER'), - Planet(_('Pluto'), 'PLUTO BARYCENTER')] +ASTERS = [Star(_('Sun'), 'SUN', radius=696342), + Satellite(_('Moon'), 'MOON', radius=1737.4), + Planet(_('Mercury'), 'MERCURY', radius=2439.7), + Planet(_('Venus'), 'VENUS', radius=6051.8), + Planet(_('Mars'), 'MARS', radius=3396.2), + Planet(_('Jupiter'), 'JUPITER BARYCENTER', radius=71492), + Planet(_('Saturn'), 'SATURN BARYCENTER', radius=60268), + Planet(_('Uranus'), 'URANUS BARYCENTER', radius=25559), + Planet(_('Neptune'), 'NEPTUNE BARYCENTER', radius=24764), + Planet(_('Pluto'), 'PLUTO BARYCENTER', radius=1185)] diff --git a/kosmorrolib/dumper.py b/kosmorrolib/dumper.py index 241df35..5280cf4 100644 --- a/kosmorrolib/dumper.py +++ b/kosmorrolib/dumper.py @@ -116,6 +116,7 @@ class JsonDumper(Dumper): if isinstance(obj, Object): obj = obj.__dict__ obj.pop('skyfield_name') + obj.pop('radius') obj['object'] = obj.pop('name') obj['details'] = obj.pop('ephemerides') return obj diff --git a/kosmorrolib/events.py b/kosmorrolib/events.py index e6d4c25..791fef5 100644 --- a/kosmorrolib/events.py +++ b/kosmorrolib/events.py @@ -56,7 +56,18 @@ def _search_conjunction(start_time: Time, end_time: Time) -> [Event]: for i, time in enumerate(times): if is_conjs[i]: - conjunctions.append(Event('CONJUNCTION', [aster1, aster2], time.utc_datetime())) + aster1_pos = (aster1.get_skyfield_object() - earth).at(time) + aster2_pos = (aster2.get_skyfield_object() - earth).at(time) + distance = aster1_pos.separation_from(aster2_pos).degrees + + if distance - aster2.get_apparent_radius(time, earth) < aster1.get_apparent_radius(time, earth): + occulting_aster = [aster1, + aster2] if aster1_pos.distance().km < aster2_pos.distance().km else [aster2, + aster1] + + conjunctions.append(Event('OCCULTATION', occulting_aster, time.utc_datetime())) + else: + conjunctions.append(Event('CONJUNCTION', [aster1, aster2], time.utc_datetime())) computed.append(aster1) diff --git a/kosmorrolib/locales/messages.pot b/kosmorrolib/locales/messages.pot index ebd3403..6b7e108 100644 --- a/kosmorrolib/locales/messages.pot +++ b/kosmorrolib/locales/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: kosmorro 0.6.2\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-03-24 13:44+0100\n" +"POT-Creation-Date: 2020-03-29 14:06+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,90 +17,95 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: kosmorrolib/data.py:30 +#: kosmorrolib/data.py:32 msgid "New Moon" msgstr "" -#: kosmorrolib/data.py:31 +#: kosmorrolib/data.py:33 msgid "Waxing crescent" msgstr "" -#: kosmorrolib/data.py:32 +#: kosmorrolib/data.py:34 msgid "First Quarter" msgstr "" -#: kosmorrolib/data.py:33 +#: kosmorrolib/data.py:35 msgid "Waxing gibbous" msgstr "" -#: kosmorrolib/data.py:34 +#: kosmorrolib/data.py:36 msgid "Full Moon" msgstr "" -#: kosmorrolib/data.py:35 +#: kosmorrolib/data.py:37 msgid "Waning gibbous" msgstr "" -#: kosmorrolib/data.py:36 +#: kosmorrolib/data.py:38 msgid "Last Quarter" msgstr "" -#: kosmorrolib/data.py:37 +#: kosmorrolib/data.py:39 msgid "Waning crescent" msgstr "" -#: kosmorrolib/data.py:41 +#: kosmorrolib/data.py:43 #, python-format msgid "%s is in opposition" msgstr "" -#: kosmorrolib/data.py:42 +#: kosmorrolib/data.py:44 #, python-format msgid "%s and %s are in conjunction" msgstr "" -#: kosmorrolib/data.py:43 +#: kosmorrolib/data.py:45 +#, python-format +msgid "%s occults %s" +msgstr "" + +#: kosmorrolib/data.py:46 #, python-format msgid "%s's largest elongation" msgstr "" -#: kosmorrolib/data.py:215 +#: kosmorrolib/data.py:233 msgid "Sun" msgstr "" -#: kosmorrolib/data.py:216 +#: kosmorrolib/data.py:234 msgid "Moon" msgstr "" -#: kosmorrolib/data.py:217 +#: kosmorrolib/data.py:235 msgid "Mercury" msgstr "" -#: kosmorrolib/data.py:218 +#: kosmorrolib/data.py:236 msgid "Venus" msgstr "" -#: kosmorrolib/data.py:219 +#: kosmorrolib/data.py:237 msgid "Mars" msgstr "" -#: kosmorrolib/data.py:220 +#: kosmorrolib/data.py:238 msgid "Jupiter" msgstr "" -#: kosmorrolib/data.py:221 +#: kosmorrolib/data.py:239 msgid "Saturn" msgstr "" -#: kosmorrolib/data.py:222 +#: kosmorrolib/data.py:240 msgid "Uranus" msgstr "" -#: kosmorrolib/data.py:223 +#: kosmorrolib/data.py:241 msgid "Neptune" msgstr "" -#: kosmorrolib/data.py:224 +#: kosmorrolib/data.py:242 msgid "Pluto" msgstr "" @@ -116,68 +121,68 @@ msgstr "" msgid "{hours}:{minutes}" msgstr "" -#: kosmorrolib/dumper.py:147 +#: kosmorrolib/dumper.py:148 msgid "Expected events:" msgstr "" -#: kosmorrolib/dumper.py:151 +#: kosmorrolib/dumper.py:152 msgid "Note: All the hours are given in UTC." msgstr "" -#: kosmorrolib/dumper.py:156 +#: kosmorrolib/dumper.py:157 msgid "Note: All the hours are given in the UTC{offset} timezone." msgstr "" -#: kosmorrolib/dumper.py:202 kosmorrolib/dumper.py:271 +#: kosmorrolib/dumper.py:203 kosmorrolib/dumper.py:272 msgid "Object" msgstr "" -#: kosmorrolib/dumper.py:203 kosmorrolib/dumper.py:272 +#: kosmorrolib/dumper.py:204 kosmorrolib/dumper.py:273 msgid "Rise time" msgstr "" -#: kosmorrolib/dumper.py:204 kosmorrolib/dumper.py:273 +#: kosmorrolib/dumper.py:205 kosmorrolib/dumper.py:274 msgid "Culmination time" msgstr "" -#: kosmorrolib/dumper.py:205 kosmorrolib/dumper.py:274 +#: kosmorrolib/dumper.py:206 kosmorrolib/dumper.py:275 msgid "Set time" msgstr "" -#: kosmorrolib/dumper.py:219 kosmorrolib/dumper.py:277 +#: kosmorrolib/dumper.py:220 kosmorrolib/dumper.py:278 msgid "Moon phase:" msgstr "" -#: kosmorrolib/dumper.py:220 +#: kosmorrolib/dumper.py:221 msgid "{next_moon_phase} on {next_moon_phase_date} at {next_moon_phase_time}" msgstr "" -#: kosmorrolib/dumper.py:258 +#: kosmorrolib/dumper.py:259 msgid "A Summary of your Sky" msgstr "" -#: kosmorrolib/dumper.py:262 +#: kosmorrolib/dumper.py:263 msgid "" "This document summarizes the ephemerides and the events of {date}. It " "aims to help you to prepare your observation session. All the hours are " "given in {timezone}." msgstr "" -#: kosmorrolib/dumper.py:268 +#: kosmorrolib/dumper.py:269 msgid "" "Don't forget to check the weather forecast before you go out with your " "material." msgstr "" -#: kosmorrolib/dumper.py:270 +#: kosmorrolib/dumper.py:271 msgid "Ephemerides of the day" msgstr "" -#: kosmorrolib/dumper.py:279 +#: kosmorrolib/dumper.py:280 msgid "Expected events" msgstr "" -#: kosmorrolib/dumper.py:354 +#: kosmorrolib/dumper.py:355 msgid "" "Building PDFs was not possible, because some dependencies are not " "installed.\n" diff --git a/test/__init__.py b/test/__init__.py index 9696019..cfb0205 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,5 +1,5 @@ +from .core import * +from .data import * from .dumper import * from .ephemerides import * from .events import * -from .core import * - diff --git a/test/data.py b/test/data.py new file mode 100644 index 0000000..d1e2103 --- /dev/null +++ b/test/data.py @@ -0,0 +1,17 @@ +import unittest + +from kosmorrolib import data, core + + +class DataTestCase(unittest.TestCase): + def test_object_radius_must_be_set_to_get_apparent_radius(self): + o = data.Planet('Saturn', 'SATURN') + + with self.assertRaises(ValueError) as context: + o.get_apparent_radius(core.get_timescale().now(), core.get_skf_objects()['earth']) + + self.assertEqual(('Missing radius for Saturn object',), context.exception.args) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/events.py b/test/events.py index 638c097..37db791 100644 --- a/test/events.py +++ b/test/events.py @@ -38,6 +38,8 @@ class EventTestCase(unittest.TestCase): (date(2020, 3, 24), [Event('MAXIMAL_ELONGATION', [ASTERS[2]], datetime(2020, 3, 24, 1, 56), details='27.8°'), 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))]) ) @data_provider(expected_events_provider)