Browse Source

feat: add capability to search events

uncomment test

do not include duplicate events in search results

requested changes

improve readability of tests

add test for when start and end dates are the same

refactor tests
pull/44/head
nicfb 2 years ago
parent
commit
5d23947017
4 changed files with 133 additions and 2 deletions
  1. +1
    -1
      kosmorrolib/__init__.py
  2. +109
    -1
      kosmorrolib/events.py
  3. +13
    -0
      kosmorrolib/exceptions.py
  4. +10
    -0
      kosmorrolib/model.py

+ 1
- 1
kosmorrolib/__init__.py View File

@@ -18,5 +18,5 @@

from .model import Position, Event, AsterEphemerides, Object
from .ephemerides import get_ephemerides, get_moon_phase
from .events import get_events
from .events import get_events, search_events
from .enum import *

+ 109
- 1
kosmorrolib/events.py View File

@@ -36,7 +36,7 @@ from kosmorrolib.model import (
)
from kosmorrolib.dateutil import translate_to_timezone
from kosmorrolib.enum import EventType, ObjectIdentifier, SeasonType, LunarEclipseType
from kosmorrolib.exceptions import OutOfRangeDateError
from kosmorrolib.exceptions import InvalidDateRangeError, OutOfRangeDateError
from kosmorrolib.core import get_timescale, get_skf_objects, flatten_list


@@ -528,3 +528,111 @@ def get_events(for_date: date = date.today(), timezone: int = 0) -> [Event]:
end_date = date(end_date.year, end_date.month, end_date.day)

raise OutOfRangeDateError(start_date, end_date) from error


def search_events(
event_types: [EventType], end: date, start: date = date.today(), timezone: int = 0
) -> [Event]:
"""Search between `start` and `end` dates, and return a list of matching events for the given time range, adjusted to a given timezone.

Find all events between January 27th, 2020 and January 29th, 2020:

>>> event_types = [EventType.OPPOSITION, EventType.CONJUNCTION, EventType.OCCULTATION, EventType.MAXIMAL_ELONGATION, EventType.APOGEE, EventType.PERIGEE, EventType.SEASON_CHANGE, EventType.LUNAR_ECLIPSE]
>>> search_events(event_types, end=date(2020, 1, 29), start=date(2020, 1, 27)) # doctest: +NORMALIZE_WHITESPACE
[<Event type=CONJUNCTION objects=[<Object type=PLANET name=VENUS />, <Object type=PLANET name=NEPTUNE />] start=2020-01-27 20:00:23.242428+00:00 end=None details=None />,
<Event type=CONJUNCTION objects=[<Object type=SATELLITE name=MOON />, <Object type=PLANET name=NEPTUNE />] start=2020-01-28 09:33:45.000618+00:00 end=None details=None />,
<Event type=CONJUNCTION objects=[<Object type=SATELLITE name=MOON />, <Object type=PLANET name=VENUS />] start=2020-01-28 11:01:51.909499+00:00 end=None details=None />,
<Event type=APOGEE objects=[<Object type=SATELLITE name=MOON />] start=2020-01-29 21:32:13.884314+00:00 end=None details={'distance_km': 405426.4150890029} />]

Find Apogee events between January 27th, 2020 and January 29th, 2020:

>>> search_events([EventType.APOGEE], end=date(2020, 1, 29), start=date(2020, 1, 27))
[<Event type=APOGEE objects=[<Object type=SATELLITE name=MOON />] start=2020-01-29 21:32:13.884314+00:00 end=None details={'distance_km': 405426.4150890029} />]

Find Apogee events between January 27th, 2020 and January 29th, 2020 (show times in UTC-6):

>>> search_events([EventType.APOGEE], end=date(2020, 1, 29), start=date(2020, 1, 27), timezone=-6)
[<Event type=APOGEE objects=[<Object type=SATELLITE name=MOON />] start=2020-01-29 15:32:13.884314-06:00 end=None details={'distance_km': 405426.4150890029} />]

If no events occurred in the given time range, an empty list is returned.

>>> event_types = [EventType.OPPOSITION, EventType.CONJUNCTION, EventType.SEASON_CHANGE, EventType.LUNAR_ECLIPSE]
>>> search_events(event_types, end=date(2021, 5, 15), start=date(2021, 5, 14))
[]

Note that the events can only be found for a date range.
Asking for the events with an out of range date will result in an exception:

>>> event_types = [EventType.OPPOSITION, EventType.CONJUNCTION]
>>> search_events(event_types, end=date(1000, 1, 2), start=date(1000, 1, 1))
Traceback (most recent call last):
...
kosmorrolib.exceptions.OutOfRangeDateError: The date must be between 1899-07-28 and 2053-10-08

If the start date does not occur before the end date, an exception will be thrown

>>> event_types = [EventType.OPPOSITION, EventType.CONJUNCTION]
>>> search_events(event_types, end=date(2021, 1, 26), start=date(2021, 1, 28))
Traceback (most recent call last):
...
kosmorrolib.exceptions.InvalidDateRangeError: The start date (2021-01-28) must be before the end date (2021-01-26)

If the start and end dates are the same, then events for that one day will be returned.

>>> event_types = [EventType.OPPOSITION, EventType.CONJUNCTION]
>>> search_events(event_types, end=date(2021, 1, 28), start=date(2021, 1, 28))
[<Event type=CONJUNCTION objects=[<Object type=PLANET name=VENUS />, <Object type=PLANET name=PLUTO />] start=2021-01-28 16:18:05.483029+00:00 end=None details=None />]
"""

moon = ASTERS[1]
sun = ASTERS[0]

def search_all_apogee_events(start: date, end: date, timezone: int = 0) -> [Event]:
moon_apogee_events = _search_apogee(moon)(start, end, timezone)
earth_apogee_events = _search_apogee(EARTH, from_aster=sun)(
start, end, timezone
)
return moon_apogee_events + earth_apogee_events

def search_all_perigee_events(start: date, end: date, timezone: int = 0) -> [Event]:
moon_perigee_events = _search_perigee(moon)(start, end, timezone)
earth_perigee_events = _search_perigee(EARTH, from_aster=sun)(
start, end, timezone
)
return moon_perigee_events + earth_perigee_events

search_funcs = {
EventType.OPPOSITION: _search_oppositions,
EventType.CONJUNCTION: _search_conjunctions_occultations,
EventType.OCCULTATION: _search_conjunctions_occultations,
EventType.MAXIMAL_ELONGATION: _search_maximal_elongations,
EventType.APOGEE: search_all_apogee_events,
EventType.PERIGEE: search_all_perigee_events,
EventType.SEASON_CHANGE: _search_earth_season_change,
EventType.LUNAR_ECLIPSE: _search_lunar_eclipse,
}

if start > end:
raise InvalidDateRangeError(start, end)

start_time = get_timescale().utc(start.year, start.month, start.day, -timezone)
end_time = get_timescale().utc(end.year, end.month, end.day + 1, -timezone)

try:
found_events = []
for event_type in event_types:
fun = search_funcs[event_type]
events = fun(start_time, end_time, timezone)
for event in events:
if event not in found_events:
found_events.append(event)

return sorted(flatten_list(found_events), key=lambda event: event.start_time)
except EphemerisRangeError as error:
start_date = translate_to_timezone(error.start_time.utc_datetime(), timezone)
end_date = translate_to_timezone(error.end_time.utc_datetime(), timezone)

start_date = date(start_date.year, start_date.month, start_date.day)
end_date = date(end_date.year, end_date.month, end_date.day)

raise OutOfRangeDateError(start_date, end_date) from error

+ 13
- 0
kosmorrolib/exceptions.py View File

@@ -30,3 +30,16 @@ class OutOfRangeDateError(ValueError):
)
self.min_date = min_date
self.max_date = max_date


class InvalidDateRangeError(ValueError):
def __init__(self, start_date: date, end_date: date):
super().__init__(
"The start date (%s) must be before the end date (%s)"
% (
start_date.strftime("%Y-%m-%d"),
end_date.strftime("%Y-%m-%d"),
)
)
self.start_date = start_date
self.end_date = end_date

+ 10
- 0
kosmorrolib/model.py View File

@@ -239,6 +239,16 @@ class Event(Serializable):
self.details,
)

def __eq__(self, other: object) -> bool:
return (
isinstance(other, Event)
and self.event_type.name == other.event_type.name
and self.objects == other.objects
and self.start_time == other.start_time
and self.end_time == other.end_time
and self.details == other.details
)

@deprecated(
"kosmorrolib.Event.get_description method is deprecated since version 1.1 "
"and will be removed in version 2.0. "


Loading…
Cancel
Save