Browse Source

feat: add capability to search events (#44)

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/42/head
Nic 2 years ago
committed by GitHub
parent
commit
c3be2611ad
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
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