Browse Source

feat: add support for Earth perihelion and aphelion (#30)

BREAKING CHANGE: EventType constants `MOON_APOGEE` and `MOON_PERIGEE` have been renamed to `APOGEE` and `PERIGEE`
tags/v1.0.0
Jérôme Deuchnord 3 years ago
committed by Jérôme Deuchnord
parent
commit
22a5ee0b03
No known key found for this signature in database GPG Key ID: 9F72B1EF93EDE1D4
4 changed files with 77 additions and 213 deletions
  1. +2
    -2
      kosmorrolib/enum.py
  2. +75
    -38
      kosmorrolib/events.py
  3. +0
    -1
      tests/__init__.py
  4. +0
    -172
      tests/events.py

+ 2
- 2
kosmorrolib/enum.py View File

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



+ 75
- 38
kosmorrolib/events.py View File

@@ -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)
[<Event type=APOGEE objects=[<Object type=SATELLITE name=MOON />] 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)
[<Event type=APOGEE objects=[<Object type=PLANET name=EARTH />] 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)
[<Event type=PERIGEE objects=[<Object type=SATELLITE name=MOON />] 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)
[<Event type=PERIGEE objects=[<Object type=PLANET name=EARTH />] 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,
]:


+ 0
- 1
tests/__init__.py View File

@@ -19,6 +19,5 @@
from .core import *
from .data import *
from .ephemerides import *
from .events import *
from .testutils import *
from .dateutil import *

+ 0
- 172
tests/events.py View File

@@ -1,172 +0,0 @@
#!/usr/bin/env python3

# Kosmorrolib - The Library To Compute Your Ephemerides
# Copyright (C) 2021 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# 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 <https://www.gnu.org/licenses/>.

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()

Loading…
Cancel
Save