瀏覽代碼

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 年之前
committed by Jérôme Deuchnord
父節點
當前提交
22a5ee0b03
沒有發現已知的金鑰在資料庫的簽署中 GPG Key ID: 9F72B1EF93EDE1D4
共有 4 個文件被更改,包括 77 次插入213 次删除
  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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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…
取消
儲存