diff --git a/pymeos/.gitignore b/pymeos/.gitignore index e7ba12db..9d22e161 100644 --- a/pymeos/.gitignore +++ b/pymeos/.gitignore @@ -3,4 +3,5 @@ build dist docs/_build -*.egg-info \ No newline at end of file +*.egg-info +.pytest_cache \ No newline at end of file diff --git a/pymeos/LICENSE b/pymeos/LICENSE index de243774..dac926cf 100644 --- a/pymeos/LICENSE +++ b/pymeos/LICENSE @@ -3,7 +3,7 @@ PostgreSQL License ------------------------------------------------------------------------------- This PyMEOS code is provided under The PostgreSQL License. -Copyright (c) 2020, Université libre de Bruxelles and MobilityDB contributors +Copyright (c) 2020, Université libre de Bruxelles and PyMEOS contributors Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. diff --git a/pymeos/pymeos/__init__.py b/pymeos/pymeos/__init__.py index af29d900..02043095 100644 --- a/pymeos/pymeos/__init__.py +++ b/pymeos/pymeos/__init__.py @@ -5,7 +5,7 @@ from .temporal import * from .time import * -__version__ = '1.1.2' +__version__ = '1.1.3a1' __all__ = [ # initialization 'pymeos_initialize', 'pymeos_finalize', diff --git a/pymeos/pymeos/aggregators/general_aggregators.py b/pymeos/pymeos/aggregators/general_aggregators.py index 2eced72d..6bede171 100644 --- a/pymeos/pymeos/aggregators/general_aggregators.py +++ b/pymeos/pymeos/aggregators/general_aggregators.py @@ -29,7 +29,7 @@ def _add(cls, state, temporal): if isinstance(temporal, datetime): state = timestamp_tcount_transfn(state, datetime_to_timestamptz(temporal)) elif isinstance(temporal, TimestampSet): - state = tstzset_tcount_transfn(state, temporal._inner) + state = timestampset_tcount_transfn(state, temporal._inner) elif isinstance(temporal, Temporal) and temporal.interpolation() == TInterpolation.DISCRETE: state = temporal_tcount_transfn(state, temporal._inner) else: diff --git a/pymeos/pymeos/boxes/stbox.py b/pymeos/pymeos/boxes/stbox.py index 17236b63..92c4ac31 100644 --- a/pymeos/pymeos/boxes/stbox.py +++ b/pymeos/pymeos/boxes/stbox.py @@ -39,6 +39,29 @@ class STBox: """ __slots__ = ['_inner'] + def _get_box(self, other: Union[Geometry, STBox, Temporal, Time], allow_space_only: bool = True, + allow_time_only: bool = False) -> STBox: + if allow_space_only and isinstance(other, get_args(Geometry)): + other_box = geo_to_stbox(geometry_to_gserialized(other, self.geodetic())) + elif isinstance(other, STBox): + other_box = other._inner + elif isinstance(other, TPoint): + other_box = tpoint_to_stbox(other._inner) + elif allow_time_only and isinstance(other, Temporal): + other_box = period_to_stbox(temporal_to_period(other._inner)) + elif allow_time_only and isinstance(other, datetime): + other_box = timestamp_to_stbox(datetime_to_timestamptz(other)) + elif allow_time_only and isinstance(other, TimestampSet): + other_box = timestampset_to_stbox(other._inner) + elif allow_time_only and isinstance(other, Period): + other_box = period_to_stbox(other._inner) + elif allow_time_only and isinstance(other, PeriodSet): + other_box = periodset_to_stbox(other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + return other_box + + # ------------------------- Constructors ---------------------------------- def __init__(self, string: Optional[str] = None, *, xmin: Optional[Union[str, float]] = None, xmax: Optional[Union[str, float]] = None, ymin: Optional[Union[str, float]] = None, ymax: Optional[Union[str, float]] = None, @@ -65,68 +88,64 @@ def __init__(self, string: Optional[str] = None, *, hasz = zmin is not None and zmax is not None if hast: period = Period(lower=tmin, upper=tmax, lower_inc=tmin_inc, upper_inc=tmax_inc)._inner - self._inner = stbox_make(period, hasx, hasz, geodetic, srid or 0, float(xmin or 0), float(xmax or 0), - float(ymin or 0), float(ymax or 0), float(zmin or 0), float(zmax or 0)) + self._inner = stbox_make(hasx, hasz, geodetic, srid or 0, float(xmin or 0), float(xmax or 0), + float(ymin or 0), float(ymax or 0), float(zmin or 0), float(zmax or 0), period) - @staticmethod - def _get_box(other: Union[Geometry, STBox, Temporal, Time], allow_space_only: bool = True, - allow_time_only: bool = False) -> STBox: - if allow_space_only and isinstance(other, get_args(Geometry)): - other_box = geo_to_stbox(geometry_to_gserialized(other)) - elif isinstance(other, STBox): - other_box = other._inner - elif isinstance(other, TPoint): - other_box = tpoint_to_stbox(other._inner) - elif allow_time_only and isinstance(other, Temporal): - other_box = period_to_stbox(temporal_to_period(other._inner)) - elif allow_time_only and isinstance(other, datetime): - other_box = timestamp_to_stbox(datetime_to_timestamptz(other)) - elif allow_time_only and isinstance(other, TimestampSet): - other_box = tstzset_to_stbox(other._inner) - elif allow_time_only and isinstance(other, Period): - other_box = period_to_stbox(other._inner) - elif allow_time_only and isinstance(other, PeriodSet): - other_box = periodset_to_stbox(other._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - return other_box + def __copy__(self) -> STBox: + """ + Returns a copy of ``self``. + + Returns: + A :class:`STBox` instance. + + MEOS Functions: + stbox_copy + """ + inner_copy = stbox_copy(self._inner) + return STBox(_inner=inner_copy) @staticmethod - def from_hexwkb(hexwkb: str) -> STBox: + def from_wkb(wkb: bytes) -> STBox: """ - Returns a `STBox` from its WKB representation in hex-encoded ASCII. + Returns a `STBox` from its WKB representation. Args: - hexwkb: WKB representation in hex-encoded ASCII + wkb: WKB representation Returns: A new :class:`STBox` instance MEOS Functions: - stbox_from_hexwkb + stbox_from_wkb """ - result = stbox_from_hexwkb(hexwkb) + result = stbox_from_wkb(wkb) return STBox(_inner=result) - def as_hexwkb(self) -> str: + @staticmethod + def from_hexwkb(hexwkb: str) -> STBox: """ - Returns the WKB representation of ``self`` in hex-encoded ASCII. + Returns a `STBox` from its WKB representation in hex-encoded ASCII. + + Args: + hexwkb: WKB representation in hex-encoded ASCII Returns: - A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. + A new :class:`STBox` instance MEOS Functions: - stbox_as_hexwkb + stbox_from_hexwkb """ - return stbox_as_hexwkb(self._inner, -1)[0] + result = stbox_from_hexwkb(hexwkb) + return STBox(_inner=result) @staticmethod - def from_geometry(geom: Geometry) -> STBox: + def from_geometry(geom: Geometry, geodetic: bool = False) -> STBox: """ Returns a `STBox` from a `Geometry`. Args: geom: A `Geometry` instance. + geodetic: Whether to create a geodetic or geometric `STBox`. Returns: A new :class:`STBox` instance. @@ -134,7 +153,7 @@ def from_geometry(geom: Geometry) -> STBox: MEOS Functions: gserialized_in, geo_to_stbox """ - gs = geometry_to_gserialized(geom) + gs = geometry_to_gserialized(geom, geodetic) return STBox(_inner=geo_to_stbox(gs)) @staticmethod @@ -149,54 +168,31 @@ def from_time(time: Time) -> STBox: A new :class:`STBox` instance. MEOS Functions: - timestamp_to_stbox, tstzset_to_stbox, period_to_stbox, periodset_to_stbox + timestamp_to_stbox, timestampset_to_stbox, period_to_stbox, periodset_to_stbox """ if isinstance(time, datetime): - result = timestamp_to_stbox(datetime_to_timestamptz(time)) + result = (datetime, datetime) elif isinstance(time, TimestampSet): - result = tstzset_to_stbox(time) + result = (time.start_timestamp(), time.end_timestamp()) elif isinstance(time, Period): - result = period_to_stbox(time) + result = (time.lower, time.upper, time.lower_inc, time.upper_inc) elif isinstance(time, PeriodSet): - result = periodset_to_stbox(time) + result = (time.start_period().lower, time.end_period().upper, + time.start_period().lower_inc, time.end_period().upper_inc) else: raise TypeError(f'Operation not supported with type {time.__class__}') return STBox(_inner=result) @staticmethod - def from_expanding_bounding_box(value: Union[Geometry, TPoint, STBox], expansion: float) -> STBox: - """ - Returns a `STBox` from a `Geometry`, `TPoint` or `STBox` instance, expanding its bounding box by the given amount. - - Args: - value: A `Geometry`, `TPoint` or `STBox` instance. - expansion: The amount to expand the bounding box. - - Returns: - A new :class:`STBox` instance. - - MEOS Functions: - geo_expand_space, tpoint_expand_space, stbox_expand_space - """ - if isinstance(value, get_args(Geometry)): - gs = geometry_to_gserialized(value) - result = geo_expand_space(gs, expansion) - elif isinstance(value, TPoint): - result = tpoint_expand_space(value._inner, expansion) - elif isinstance(value, STBox): - result = stbox_expand_space(value._inner, expansion) - else: - raise TypeError(f'Operation not supported with type {value.__class__}') - return STBox(_inner=result) - - @staticmethod - def from_geometry_time(geometry: Geometry, time: Time) -> STBox: + def from_geometry_time(geometry: Geometry, time: Union[datetime, Period], + geodetic: bool = False) -> STBox: """ Returns a `STBox` from a space and time dimension. Args: geometry: A `Geometry` instance representing the space dimension. time: A `Time` instance representing the time dimension. + geodetic: Whether to create a geodetic or geometric `STBox`. Returns: A new :class:`STBox` instance. @@ -204,15 +200,11 @@ def from_geometry_time(geometry: Geometry, time: Time) -> STBox: MEOS Functions: geo_timestamp_to_stbox, geo_period_to_stbox """ - gs = geometry_to_gserialized(geometry) + gs = geometry_to_gserialized(geometry, geodetic) if isinstance(time, datetime): result = geo_timestamp_to_stbox(gs, datetime_to_timestamptz(time)) - elif isinstance(time, TimestampSet): - result = geo_period_to_stbox(gs, set_to_span(time._inner)) elif isinstance(time, Period): result = geo_period_to_stbox(gs, time._inner) - elif isinstance(time, PeriodSet): - result = geo_period_to_stbox(gs, time._inner.span) else: raise TypeError(f'Operation not supported with types {geometry.__class__} and {time.__class__}') return STBox(_inner=result) @@ -233,142 +225,85 @@ def from_tpoint(temporal: TPoint) -> STBox: """ return STBox(_inner=tpoint_to_stbox(temporal._inner)) - def quad_split_flat(self) -> List[STBox]: + @staticmethod + def from_expanding_bounding_box(value: Union[Geometry, TPoint, STBox], expansion: float, + geodetic: Optional[bool] = False) -> STBox: """ - Returns a list of 4 (or 8 if `self`has Z dimension) :class:`STBox` instances resulting from the quad - split of ``self``. - - Indices of returned array are as follows (back only present if Z dimension is present): + Returns a `STBox` from a `Geometry`, `TPoint` or `STBox` instance, expanding its bounding box by the given amount. - >>> # (front) (back) - >>> # ------------- ------------- - >>> # | 2 | 3 | | 6 | 7 | - >>> # ------------- + ------------- - >>> # | 0 | 1 | | 4 | 5 | - >>> # ------------- ------------- + Args: + value: A `Geometry`, `TPoint` or `STBox` instance. + expansion: The amount to expand the bounding box. + geodetic: Whether to create a geodetic or geometric `STBox`. Only used when value is a `Geometry` instance. Returns: - A :class:`list` of :class:`STBox` instances. + A new :class:`STBox` instance. MEOS Functions: - stbox_quad_split + geo_expand_space, tpoint_expand_space, stbox_expand_space """ - boxes, count = stbox_quad_split(self._inner) - return [STBox(_inner=boxes + i) for i in range(count)] + if isinstance(value, get_args(Geometry)): + gs = geometry_to_gserialized(value, geodetic) + result = geo_expand_space(gs, expansion) + elif isinstance(value, TPoint): + result = tpoint_expand_space(value._inner, expansion) + elif isinstance(value, STBox): + result = stbox_expand_space(value._inner, expansion) + else: + raise TypeError(f'Operation not supported with type {value.__class__}') + return STBox(_inner=result) - def quad_split(self) -> Union[List[List[STBox]], List[List[List[STBox]]]]: + # ------------------------- Output ---------------------------------------- + def __str__(self, max_decimals: int = 15): """ - Returns a 2D (YxX) or 3D (ZxYxX) list of :class:`STBox` instances resulting from the quad split of ``self``. - - Indices of returned array are as follows: - - >>> # (front) - >>> # ------------------- - >>> # | [1][0] | [1][1] | - >>> # ------------------- - >>> # | [0][0] | [0][1] | - >>> # ------------------- + Returns a string representation of ``self``. - If Z dimension is present: + Returns: + A :class:`str` instance. - >>> # (front) (back) - >>> # ------------------------- ------------------------- - >>> # | [0][1][0] | [0][1][1] | | [1][1][0] | [1][1][1] | - >>> # ------------------------- + ------------------------- - >>> # | [0][0][0] | [0][0][1] | | [1][0][0] | [1][0][1] | - >>> # ------------------------- ------------------------- + MEOS Functions: + stbox_out + """ + return stbox_out(self._inner, max_decimals) + def __repr__(self): + """ + Returns a string representation of ``self``. Returns: - A 2D or 3D :class:`list` of :class:`STBox` instances. + A :class:`str` instance. MEOS Functions: - stbox_quad_split - + stbox_out """ - boxes, count = stbox_quad_split(self._inner) - if self.has_z(): - return [ - [[STBox(_inner=boxes + i) for i in range(2)], [STBox(_inner=boxes + i) for i in range(2, 4)]], - [[STBox(_inner=boxes + i) for i in range(4, 6)], [STBox(_inner=boxes + i) for i in range(6, 8)]] - ] - else: - return [[STBox(_inner=boxes + i) for i in range(2)], [STBox(_inner=boxes + i) for i in range(2, 4)]] + return (f'{self.__class__.__name__}' + f'({self})') - def tile(self, size: Optional[float] = None, duration: Optional[Union[timedelta, str]] = None, - origin: Optional[Geometry] = None, - start: Union[datetime, str, None] = None) -> List[List[List[List[STBox]]]]: + def as_wkb(self) -> bytes: """ - Returns a 4D matrix (XxYxZxT) of `STBox` instances representing the tiles of ``self``. - The resulting matrix has 4 dimensions regardless of the dimensionality of ``self``. If the ``self`` - is missing a dimension, the resulting matrix will have a size of 1 for that dimension. - - Args: - size: The size of the spatial tiles. If the `STBox` instance has a spatial dimension and this - argument is not provided, the tiling will be only temporal. - duration: The duration of the temporal tiles. If the `STBox` instance has a time dimension and this - argument is not provided, the tiling will be only spatial. - origin: The origin of the spatial tiling. If not provided, the origin will be (0, 0, 0). - start: The start time of the temporal tiling. If not provided, the start time will be the starting time of - the `STBox` time dimension. + Returns the WKB representation of ``self``. Returns: - A 4D matrix (XxYxZxT) of `STBox` instances. + A :class:`str` object with the WKB representation of ``self``. MEOS Functions: - stbox_tile_list + stbox_as_wkb """ - sz = size or (max(self.xmax() - self.xmin(), self.ymax() - self.ymin(), - (self.zmax() - self.zmin() if self.has_z() else 0)) + 1) - dt = timedelta_to_interval(duration) if isinstance(duration, timedelta) \ - else pg_interval_in(duration, -1) if isinstance(duration, str) \ - else None - st = datetime_to_timestamptz(start) if isinstance(start, datetime) \ - else pg_timestamptz_in(start, -1) if isinstance(start, str) \ - else datetime_to_timestamptz(self.tmin()) if self.has_t() \ - else 0 - gs = geometry_to_gserialized(origin) if origin is not None \ - else gserialized_in('Point(0 0 0)', -1) - tiles, dimensions = stbox_tile_list(self._inner, sz, dt, gs, st) - x_size = dimensions[0] or 1 - y_size = dimensions[1] or 1 - z_size = dimensions[2] or 1 - t_size = dimensions[3] or 1 - x_factor = y_size * z_size * t_size - y_factor = z_size * t_size - z_factor = t_size - return [[[[STBox(_inner=tiles + x * x_factor + y * y_factor + z * z_factor + t) for t in range(t_size)] - for z in range(z_size)] for y in range(y_size)] for x in range(x_size)] + return stbox_as_wkb(self._inner, 4) - def tile_flat(self, size: float, duration: Optional[Union[timedelta, str]] = None, - origin: Optional[Geometry] = None, - start: Union[datetime, str, None] = None) -> List[STBox]: + def as_hexwkb(self) -> str: """ - Returns a flat list of `STBox` instances representing the tiles of ``self``. - - Args: - size: The size of the spatial tiles. If the `STBox` instance has a spatial dimension and this - argument is not provided, the tiling will be only temporal. - duration: The duration of the temporal tiles. If the `STBox` instance has a time dimension and this - argument is not provided, the tiling will be only spatial. - origin: The origin of the spatial tiling. If not provided, the origin will be (0, 0, 0). - start: The start time of the temporal tiling. If not provided, the start time will be the starting time of - the `STBox` time dimension. + Returns the WKB representation of ``self`` in hex-encoded ASCII. Returns: - A flat list of `STBox` instances. + A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. MEOS Functions: - stbox_tile_list + stbox_as_hexwkb """ - boxes = self.tile(size, duration, origin, start) - return [b - for x in boxes - for y in x - for z in y - for b in z - ] + return stbox_as_hexwkb(self._inner, -1)[0] + # ------------------------- Conversions ---------------------------------- def to_geometry(self, precision: int = 15) -> shp.BaseGeometry: """ Returns the spatial dimension of ``self`` as a `shapely` :class:`~shapely.BaseGeometry` instance. @@ -396,6 +331,7 @@ def to_period(self) -> Period: """ return Period(_inner=stbox_to_period(self._inner)) + # ------------------------- Accessors ------------------------------------- def has_xy(self) -> bool: """ @@ -491,7 +427,22 @@ def tmin(self) -> datetime: MEOS Functions: stbox_tmin """ - return timestamptz_to_datetime(stbox_tmin(self._inner)) + result = stbox_tmin(self._inner) + if not result: + return None + return timestamptz_to_datetime(result) + + def tmin_inc(self) -> bool: + """ + Returns whether starting time of ``self`` is inclusive or not + + Returns: + True if the starting time of ``self`` is inclusive and False otherwise + + MEOS Functions: + stbox_tmin_inc + """ + return stbox_tmin_inc(self._inner) def xmax(self) -> float: """ @@ -539,8 +490,24 @@ def tmax(self) -> datetime: MEOS Functions: stbox_tmax """ - return timestamptz_to_datetime(stbox_tmax(self._inner)) + result = stbox_tmax(self._inner) + if not result: + return None + return timestamptz_to_datetime(result) + def tmax_inc(self) -> bool: + """ + Returns whether ending time of ``self`` is inclusive or not + + Returns: + True if the ending time of ``self`` is inclusive and False otherwise + + MEOS Functions: + stbox_tmax_inc + """ + return stbox_tmax_inc(self._inner) + + # ------------------------- Spatial Reference System ---------------------- def srid(self) -> int: """ Returns the SRID of ``self``. @@ -568,11 +535,11 @@ def set_srid(self, value: int) -> STBox: """ return STBox(_inner=stbox_set_srid(self._inner, value)) - def expand(self, other: Union[STBox, float, timedelta]) -> STBox: + # ------------------------- Transformations ------------------------------- + def expand(self, other: Union[int, float, timedelta]) -> STBox: """ Expands ``self`` with `other`. - If `other` is an :class:`STBox`, the result is the smallest spatio-temporal box that contains both ``self`` - and `other`. If `other` is a :class:`float`, the result is equal to ``self`` but with the spatial dimensions + If `other` is a :class:`int` or a :class:`float`, the result is equal to ``self`` but with the spatial dimensions expanded by `other` in all directions. If `other` is a :class:`timedelta`, the result is equal to ``self`` but with the temporal dimension expanded by `other` in both directions. @@ -583,13 +550,10 @@ def expand(self, other: Union[STBox, float, timedelta]) -> STBox: A new :class:`STBox` instance. MEOS Functions: - stbox_expand, stbox_expand_space, stbox_expand_time + stbox_expand_space, stbox_expand_time """ - if isinstance(other, STBox): - result = stbox_copy(self._inner) - stbox_expand(other._inner, result) - elif isinstance(other, float): - result = stbox_expand_space(self._inner, other) + if isinstance(other, int) or isinstance(other, float): + result = stbox_expand_space(self._inner, float(other)) elif isinstance(other, timedelta): result = stbox_expand_time(self._inner, timedelta_to_interval(other)) else: @@ -655,11 +619,28 @@ def shift_tscale(self, shift: Optional[timedelta] = None, duration: Optional[tim period_shift_tscale( new_period, timedelta_to_interval(shift) if shift else None, - timedelta_to_interval(duration) if duration else None, - None, None + timedelta_to_interval(duration) if duration else None ) return STBox(_inner=new_inner) + def round(self, maxdd : int = 0) -> STBox: + """ + Returns `self` rounded to the given number of decimal digits. + + Args: + maxdd: Maximum number of decimal digits. + + Returns: + A new :class:`STBox` instance + + MEOS Functions: + stbox_round + """ + new_inner = stbox_copy(self._inner) + stbox_round(new_inner, maxdd) + return STBox(_inner=new_inner) + + # ------------------------- Set Operations -------------------------------- def union(self, other: STBox, strict: bool = True) -> STBox: """ Returns the smallest spatio-temporal box that contains both ``self`` and `other`. @@ -676,6 +657,24 @@ def union(self, other: STBox, strict: bool = True) -> STBox: """ return STBox(_inner=union_stbox_stbox(self._inner, other._inner, strict)) + def __add__(self, other): + """ + Returns the non-strict union of ``self`` and `other`. + + Args: + other: The spatiotemporal object to union with ``self``. + + Returns: + An :class:`STBox` with the union of ``self`` and ``other``. + + MEOS Functions: + union_stbox_stbox + + See Also: + :meth:`STBox.union` + """ + return self.union(other, strict=False) + # TODO: Check returning None for empty intersection is the desired behaviour def intersection(self, other: STBox) -> Optional[STBox]: """ @@ -693,6 +692,25 @@ def intersection(self, other: STBox) -> Optional[STBox]: result = intersection_stbox_stbox(self._inner, other._inner) return STBox(_inner=result) if result else None + def __mul__(self, other): + """ + Returns the intersection of ``self`` and `other`. + + Args: + other: The spatiotemporal object to intersect with ``self``. + + Returns: + An :class:`STBox` with the intersection of ``self`` and ``other``. + + MEOS Functions: + intersection_stbox_stbox + + See Also: + :meth:`STBox.intersection` + """ + return self.intersection(other) + + # ------------------------- Topological Operations ------------------------ def is_adjacent(self, other: Union[Geometry, STBox, Temporal, Time]) -> bool: """ Returns whether ``self`` and `other` are adjacent. Two spatiotemporal boxes are adjacent if they share n @@ -742,6 +760,24 @@ def contains(self, content: Union[Geometry, STBox, Temporal, Time]) -> bool: """ return contains_stbox_stbox(self._inner, self._get_box(content, allow_time_only=True)) + def __contains__(self, item): + """ + Returns whether ``self`` contains `item`. + + Args: + item: The spatiotemporal object to check if it is contained in ``self``. + + Returns: + ``True`` if ``self`` contains ``item``, ``False`` otherwise. + + MEOS Functions: + contains_stbox_stbox + + See Also: + :meth:`STBox.contains` + """ + return self.contains(item) + def overlaps(self, other: Union[Geometry, STBox, Temporal, Time]) -> bool: """ Returns whether ``self`` overlaps `other`. Note that for `TPoint` instances, the bounding box of @@ -774,6 +810,7 @@ def is_same(self, other: Union[Geometry, STBox, Temporal, Time]) -> bool: """ return same_stbox_stbox(self._inner, self._get_box(other, allow_time_only=True)) + # ------------------------- Position Operations --------------------------- def is_left(self, other: Union[Geometry, STBox, TPoint]) -> bool: """ Returns whether ``self`` is strictly to the left of `other`. Checks the X dimension. @@ -1010,6 +1047,7 @@ def is_over_or_after(self, other: Union[Box, Temporal, Time]) -> bool: """ return self.to_period().is_over_or_after(other) + # ------------------------- Distance Operations --------------------------- def nearest_approach_distance(self, other: Union[Geometry, STBox, TPoint]) -> float: """ Returns the distance between the nearest points of ``self`` and `other`. @@ -1024,7 +1062,7 @@ def nearest_approach_distance(self, other: Union[Geometry, STBox, TPoint]) -> fl nad_stbox_geo, nad_stbox_stbox """ if isinstance(other, get_args(Geometry)): - gs = geometry_to_gserialized(other) + gs = geometry_to_gserialized(other, self.geodetic()) return nad_stbox_geo(self._inner, gs) elif isinstance(other, STBox): return nad_stbox_stbox(self._inner, other._inner) @@ -1033,60 +1071,144 @@ def nearest_approach_distance(self, other: Union[Geometry, STBox, TPoint]) -> fl else: raise TypeError(f'Operation not supported with type {other.__class__}') - def __add__(self, other): + # ------------------------- Splitting -------------------------------------- + def quad_split_flat(self) -> List[STBox]: """ - Returns the non-strict union of ``self`` and `other`. + Returns a list of 4 (or 8 if `self`has Z dimension) :class:`STBox` instances resulting from the quad + split of ``self``. - Args: - other: The spatiotemporal object to union with ``self``. + Indices of returned array are as follows (back only present if Z dimension is present): + + >>> # (front) (back) + >>> # ------------- ------------- + >>> # | 2 | 3 | | 6 | 7 | + >>> # ------------- + ------------- + >>> # | 0 | 1 | | 4 | 5 | + >>> # ------------- ------------- Returns: - An :class:`STBox` with the union of ``self`` and ``other``. + A :class:`list` of :class:`STBox` instances. MEOS Functions: - union_stbox_stbox - - See Also: - :meth:`STBox.union` + stbox_quad_split """ - return self.union(other, strict=False) + boxes, count = stbox_quad_split(self._inner) + return [STBox(_inner=boxes + i) for i in range(count)] - def __mul__(self, other): + def quad_split(self) -> Union[List[List[STBox]], List[List[List[STBox]]]]: """ - Returns the intersection of ``self`` and `other`. + Returns a 2D (YxX) or 3D (ZxYxX) list of :class:`STBox` instances resulting from the quad split of ``self``. + + Indices of returned array are as follows: + + >>> # (front) + >>> # ------------------- + >>> # | [1][0] | [1][1] | + >>> # ------------------- + >>> # | [0][0] | [0][1] | + >>> # ------------------- + + If Z dimension is present: + + >>> # (front) (back) + >>> # ------------------------- ------------------------- + >>> # | [0][1][0] | [0][1][1] | | [1][1][0] | [1][1][1] | + >>> # ------------------------- + ------------------------- + >>> # | [0][0][0] | [0][0][1] | | [1][0][0] | [1][0][1] | + >>> # ------------------------- ------------------------- - Args: - other: The spatiotemporal object to intersect with ``self``. Returns: - An :class:`STBox` with the intersection of ``self`` and ``other``. + A 2D or 3D :class:`list` of :class:`STBox` instances. MEOS Functions: - intersection_stbox_stbox + stbox_quad_split - See Also: - :meth:`STBox.intersection` """ - return self.intersection(other) + boxes, count = stbox_quad_split(self._inner) + if self.has_z(): + return [ + [[STBox(_inner=boxes + i) for i in range(2)], [STBox(_inner=boxes + i) for i in range(2, 4)]], + [[STBox(_inner=boxes + i) for i in range(4, 6)], [STBox(_inner=boxes + i) for i in range(6, 8)]] + ] + else: + return [[STBox(_inner=boxes + i) for i in range(2)], [STBox(_inner=boxes + i) for i in range(2, 4)]] - def __contains__(self, item): + def tile(self, size: Optional[float] = None, duration: Optional[Union[timedelta, str]] = None, + origin: Optional[Geometry] = None, + start: Union[datetime, str, None] = None) -> List[List[List[List[STBox]]]]: """ - Returns whether ``self`` contains `item`. + Returns a 4D matrix (XxYxZxT) of `STBox` instances representing the tiles of ``self``. + The resulting matrix has 4 dimensions regardless of the dimensionality of ``self``. If the ``self`` + is missing a dimension, the resulting matrix will have a size of 1 for that dimension. Args: - item: The spatiotemporal object to check if it is contained in ``self``. + size: The size of the spatial tiles. If the `STBox` instance has a spatial dimension and this + argument is not provided, the tiling will be only temporal. + duration: The duration of the temporal tiles. If the `STBox` instance has a time dimension and this + argument is not provided, the tiling will be only spatial. + origin: The origin of the spatial tiling. If not provided, the origin will be (0, 0, 0). + start: The start time of the temporal tiling. If not provided, the start time will be the starting time of + the `STBox` time dimension. Returns: - ``True`` if ``self`` contains ``item``, ``False`` otherwise. + A 4D matrix (XxYxZxT) of `STBox` instances. MEOS Functions: - contains_stbox_stbox + stbox_tile_list + """ + sz = size or (max(self.xmax() - self.xmin(), self.ymax() - self.ymin(), + (self.zmax() - self.zmin() if self.has_z() else 0)) + 1) + dt = timedelta_to_interval(duration) if isinstance(duration, timedelta) \ + else pg_interval_in(duration, -1) if isinstance(duration, str) \ + else None + st = datetime_to_timestamptz(start) if isinstance(start, datetime) \ + else pg_timestamptz_in(start, -1) if isinstance(start, str) \ + else datetime_to_timestamptz(self.tmin()) if self.has_t() \ + else 0 + gs = geometry_to_gserialized(origin) if origin is not None \ + else gserialized_in('Point(0 0 0)', -1) + tiles, dimensions = stbox_tile_list(self._inner, sz, sz, sz, dt, gs, st) + x_size = dimensions[0] or 1 + y_size = dimensions[1] or 1 + z_size = dimensions[2] or 1 + t_size = dimensions[3] or 1 + x_factor = y_size * z_size * t_size + y_factor = z_size * t_size + z_factor = t_size + return [[[[STBox(_inner=tiles + x * x_factor + y * y_factor + z * z_factor + t) for t in range(t_size)] + for z in range(z_size)] for y in range(y_size)] for x in range(x_size)] - See Also: - :meth:`STBox.contains` + def tile_flat(self, size: float, duration: Optional[Union[timedelta, str]] = None, + origin: Optional[Geometry] = None, + start: Union[datetime, str, None] = None) -> List[STBox]: """ - return self.contains(item) + Returns a flat list of `STBox` instances representing the tiles of ``self``. + + Args: + size: The size of the spatial tiles. If the `STBox` instance has a spatial dimension and this + argument is not provided, the tiling will be only temporal. + duration: The duration of the temporal tiles. If the `STBox` instance has a time dimension and this + argument is not provided, the tiling will be only spatial. + origin: The origin of the spatial tiling. If not provided, the origin will be (0, 0, 0). + start: The start time of the temporal tiling. If not provided, the start time will be the starting time of + the `STBox` time dimension. + + Returns: + A flat list of `STBox` instances. + + MEOS Functions: + stbox_tile_list + """ + boxes = self.tile(size, duration, origin, start) + return [b + for x in boxes + for y in x + for z in y + for b in z + ] + # ------------------------- Comparisons ----------------------------------- def __eq__(self, other): """ Returns whether ``self`` is equal to `other`. @@ -1121,25 +1243,6 @@ def __ne__(self, other): return stbox_ne(self._inner, other._inner) return True - def __cmp__(self, other): - """ - Returns the result of comparing ``self`` to ``other``. Compares first the SRID, then the time dimension, - and finally the spatial dimension (X, then Y then Z lower bounds and then the upper bounds). - - Args: - other: The spatiotemporal object to compare with ``self``. - - Returns: - -1 if ``self`` is less than ``other``, 0 if ``self`` is equal to ``other``, 1 if ``self`` is greater than - ``other``. - - MEOS Functions: - stbox_cmp - """ - if isinstance(other, self.__class__): - return stbox_cmp(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - def __lt__(self, other): """ Returns whether ``self`` is less than `other`. Compares first the SRID, then the time dimension, @@ -1212,44 +1315,7 @@ def __ge__(self, other): return stbox_ge(self._inner, other._inner) raise TypeError(f'Operation not supported with type {other.__class__}') - def __copy__(self) -> STBox: - """ - Returns a copy of ``self``. - - Returns: - A :class:`STBox` instance. - - MEOS Functions: - stbox_copy - """ - inner_copy = stbox_copy(self._inner) - return STBox(_inner=inner_copy) - - def __str__(self): - """ - Returns a string representation of ``self``. - - Returns: - A :class:`str` instance. - - MEOS Functions: - stbox_out - """ - return stbox_out(self._inner, 15) - - def __repr__(self): - """ - Returns a string representation of ``self``. - - Returns: - A :class:`str` instance. - - MEOS Functions: - stbox_out - """ - return (f'{self.__class__.__name__}' - f'({self})') - + # ------------------------- Plot Operations ------------------------------- def plot_xy(self, *args, **kwargs): """ Plots the spatial dimension (XY) of ``self``. @@ -1280,6 +1346,7 @@ def plot_yt(self, *args, **kwargs): from ..plotters import BoxPlotter return BoxPlotter.plot_stbox_yt(self, *args, **kwargs) + # ------------------------- Database Operations --------------------------- @staticmethod def read_from_cursor(value, _=None): """ diff --git a/pymeos/pymeos/boxes/tbox.py b/pymeos/pymeos/boxes/tbox.py index d20f7778..783923f6 100644 --- a/pymeos/pymeos/boxes/tbox.py +++ b/pymeos/pymeos/boxes/tbox.py @@ -31,15 +31,24 @@ class TBox: """ __slots__ = ['_inner'] + def _inner_period(self): + from pymeos_cffi.functions import _ffi + return _ffi.addressof(self._inner.period) + + def _inner_span(self): + from pymeos_cffi.functions import _ffi + return _ffi.addressof(self._inner.span) + + # ------------------------- Constructors ---------------------------------- def __init__(self, string: Optional[str] = None, *, xmin: Optional[Union[str, float]] = None, xmax: Optional[Union[str, float]] = None, tmin: Optional[Union[str, datetime]] = None, tmax: Optional[Union[str, datetime]] = None, - xmin_inc: bool = True, - xmax_inc: bool = True, - tmin_inc: bool = True, - tmax_inc: bool = True, + xmin_inc: Optional[bool] = True, + xmax_inc: Optional[bool] = False, + tmin_inc: Optional[bool] = True, + tmax_inc: Optional[bool] = False, _inner=None): assert (_inner is not None) or (string is not None) != ( (xmin is not None and xmax is not None) or (tmin is not None and tmax is not None)), \ @@ -57,42 +66,52 @@ def __init__(self, string: Optional[str] = None, *, period = Period(lower=tmin, upper=tmax, lower_inc=tmin_inc, upper_inc=tmax_inc)._inner self._inner = tbox_make(period, span) - def _inner_period(self): - from pymeos_cffi.functions import _ffi - return _ffi.addressof(self._inner.period) + def __copy__(self) -> TBox: + """ + Returns a copy of ``self``. - def _inner_span(self): - from pymeos_cffi.functions import _ffi - return _ffi.addressof(self._inner.span) + Returns: + A :class:`TBox` instance. + + MEOS Functions: + tbox_copy + """ + inner_copy = tbox_copy(self._inner) + return TBox(_inner=inner_copy) @staticmethod - def from_hexwkb(hexwkb: str) -> TBox: + def from_wkb(wkb: bytes) -> TBox: """ - Returns a `TBox` from its WKB representation in hex-encoded ASCII. + Returns a `TBox` from its WKB representation. Args: - hexwkb: WKB representation in hex-encoded ASCII + wkb: WKB representation Returns: A new :class:`TBox` instance MEOS Functions: - tbox_from_hexwkb + tbox_from_wkb """ - result = tbox_from_hexwkb(hexwkb) + result = tbox_from_wkb(wkb) return TBox(_inner=result) - def as_hexwkb(self) -> str: + @staticmethod + def from_hexwkb(hexwkb: str) -> TBox: """ - Returns the WKB representation of ``self`` in hex-encoded ASCII. + Returns a `TBox` from its WKB representation in hex-encoded ASCII. + + Args: + hexwkb: WKB representation in hex-encoded ASCII Returns: - A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. + A new :class:`TBox` instance MEOS Functions: - tbox_as_hexwkb + tbox_from_hexwkb """ - return tbox_as_hexwkb(self._inner, -1)[0] + result = tbox_from_hexwkb(hexwkb) + return TBox(_inner=result) @staticmethod def from_value(value: Union[int, float, intrange, floatrange]) -> TBox: @@ -137,13 +156,14 @@ def from_time(time: Time) -> TBox: timestamp_to_tbox, timestampset_to_tbox, period_to_tbox, periodset_to_tbox """ if isinstance(time, datetime): - result = timestamp_to_tbox(datetime_to_timestamptz(time)) + result = (datetime, datetime) elif isinstance(time, TimestampSet): - result = tstzset_to_tbox(time) + result = (time.start_timestamp(), time.end_timestamp()) elif isinstance(time, Period): - result = period_to_tbox(time) + result = (time.lower, time.upper, time.lower_inc, time.upper_inc) elif isinstance(time, PeriodSet): - result = periodset_to_tbox(time) + result = (time.start_period().lower, time.end_period().upper, + time.start_period().lower_inc, time.end_period().upper_inc) else: raise TypeError(f'Operation not supported with type {time.__class__}') return TBox(_inner=result) @@ -201,6 +221,72 @@ def from_tnumber(temporal: TNumber) -> TBox: """ return TBox(_inner=tnumber_to_tbox(temporal._inner)) + # ------------------------- Output ---------------------------------------- + def to_str(self, max_decimals=15) -> str: + """ + Returns a string representation of `self` with a maximum number of decimals. + + Args: + max_decimals: The maximum number of decimals. + + Returns: + A string representation of `self`. + + MEOS Functions: + tbox_out + """ + return tbox_out(self._inner, max_decimals) + + def __str__(self, max_decimals: int = 15): + """ + Returns a string representation of ``self``. + + Returns: + A :class:`str` instance. + + MEOS Functions: + tbox_out + """ + return tbox_out(self._inner, max_decimals) + + def __repr__(self, max_decimals=15): + """ + Returns a string representation of ``self``. + + Returns: + A :class:`str` instance. + + MEOS Functions: + tbox_out + """ + return (f'{self.__class__.__name__}' + f'({self})') + + def as_wkb(self) -> str: + """ + Returns the WKB representation of ``self``. + + Returns: + A :class:`str` object with the WKB representation of ``self``. + + MEOS Functions: + tbox_as_wkb + """ + return tbox_as_wkb(self._inner, 4) + + def as_hexwkb(self) -> str: + """ + Returns the WKB representation of ``self`` in hex-encoded ASCII. + + Returns: + A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. + + MEOS Functions: + tbox_as_hexwkb + """ + return tbox_as_hexwkb(self._inner, -1)[0] + + # ------------------------- Conversions ---------------------------------- def to_floatrange(self) -> floatrange: """ Returns the numeric span of ``self``. @@ -225,6 +311,7 @@ def to_period(self) -> Period: """ return Period(_inner=tbox_to_period(self._inner)) + # ------------------------- Accessors ------------------------------------- def has_x(self) -> bool: """ Returns whether ``self`` has a numeric dimension. @@ -261,6 +348,18 @@ def xmin(self) -> float: """ return tbox_xmin(self._inner) + def xmin_inc(self) -> bool: + """ + Returns whether the xmin value of the tbox is inclusive or not + + Returns: + True if the xmin value of the tbox is inclusive and False otherwise + + MEOS Functions: + tbox_xmin_inc + """ + return tbox_xmin_inc(self._inner) + def xmax(self) -> float: """ Returns the numeric upper bound of ``self``. @@ -273,6 +372,18 @@ def xmax(self) -> float: """ return tbox_xmax(self._inner) + def xmax_inc(self) -> bool: + """ + Returns whether the xmax value of the tbox is inclusive or not + + Returns: + True if the xmax value of the tbox is inclusive and False otherwise + + MEOS Functions: + tbox_xmax_inc + """ + return tbox_xmax_inc(self._inner) + def tmin(self): """ Returns the temporal lower bound of ``self``. @@ -283,65 +394,52 @@ def tmin(self): MEOS Functions: tbox_tmin """ - return timestamptz_to_datetime(tbox_tmin(self._inner)) + result = tbox_tmin(self._inner) + if not result: + return None + return timestamptz_to_datetime(result) - def tmax(self): + def tmin_inc(self) -> bool: """ - Returns the temporal upper bound of ``self``. + Returns whether the tmin value of the tbox is inclusive or not Returns: - The temporal upper bound of the `TBox` as a :class:`~datetime.datetime` + True if the tmin value of the tbox is inclusive and False otherwise MEOS Functions: - tbox_tmax + tbox_tmin_inc """ - return timestamptz_to_datetime(tbox_tmax(self._inner)) + return tbox_tmin_inc(self._inner) - def tile(self, size: float, duration: Union[timedelta, str], - origin: float = 0.0, start: Union[datetime, str, None] = None) -> List[List[TBox]]: + def tmax(self): """ - Returns 2d matrix of TBoxes resulting of tiling ``self``. - - Args: - size: size of the numeric dimension of the tiles - duration: size of the temporal dimenstion of the tiles - origin: origin of the numeric dimension of the tiles - start: origin of the temporal dimension of the tiles + Returns the temporal upper bound of ``self``. Returns: - A 2d array of :class:`TBox` instances. + The temporal upper bound of the `TBox` as a :class:`~datetime.datetime` MEOS Functions: - tbox_tile_list + tbox_tmax """ - dt = timedelta_to_interval(duration) if isinstance(duration, timedelta) else pg_interval_in(duration, -1) - st = datetime_to_timestamptz(start) if isinstance(start, datetime) \ - else pg_timestamptz_in(start, -1) if isinstance(start, str) \ - else tbox_tmin(self._inner) - tiles, rows, columns = tbox_tile_list(self._inner, size, dt, origin, st) - return [[TBox(_inner=tiles + (c * rows + r)) for c in range(columns)] for r in range(rows)] + result = tbox_tmax(self._inner) + if not result: + return None + return timestamptz_to_datetime(result) - def tile_flat(self, size: float, duration: Union[timedelta, str], - origin: float = 0.0, start: Union[datetime, str, None] = None) -> List[TBox]: + def tmax_inc(self) -> bool: """ - Returns an array of TBoxes resulting of tiling ``self``. - - Args: - size: size of the numeric dimension of the tiles - duration: size of the temporal dimenstion of the tiles - origin: origin of the numeric dimension of the tiles - start: origin of the temporal dimension of the tiles + Returns whether the tmax value of the tbox is inclusive or not Returns: - An array of :class:`TBox` instances. + True if the tmax value of the tbox is inclusive and False otherwise MEOS Functions: - tbox_tile_list + tbox_tmax_inc """ - tiles = self.tile(size, duration, origin, start) - return [box for row in tiles for box in row] + return tbox_tmax_inc(self._inner) - def expand(self, other: Union[TBox, float, timedelta]) -> TBox: + # ------------------------- Transformation -------------------------------- + def expand(self, other: Union[TBox, int, float, timedelta]) -> TBox: """ Returns the result of expanding ``self`` with the ``other``. Depending on the type of ``other``, the expansion will be of the numeric dimension (:class:`float`), temporal (:class:`~datetime.timedelta`) or both @@ -359,14 +457,50 @@ def expand(self, other: Union[TBox, float, timedelta]) -> TBox: if isinstance(other, TBox): result = tbox_copy(self._inner) tbox_expand(other._inner, result) - elif isinstance(other, float): - result = tbox_expand_value(self._inner, other) + elif isinstance(other, int) or isinstance(other, float): + result = tbox_expand_value(self._inner, float(other)) elif isinstance(other, timedelta): result = tbox_expand_time(self._inner, timedelta_to_interval(other)) else: raise TypeError(f'Operation not supported with type {other.__class__}') return TBox(_inner=result) + def shift(self, delta: timedelta) -> TBox: + """ + Returns a new `TBox` with the time dimension shifted by `delta`. + + Args: + delta: :class:`datetime.timedelta` instance to shift + + Returns: + A new :class:`TBox` instance + + MEOS Functions: + period_shift_tscale + + See Also: + :meth:`Period.shift` + """ + return self.shift_tscale(shift=delta) + + def tscale(self, duration: timedelta) -> TBox: + """ + Returns a new `TBox` with the time dimension having duration `duration`. + + Args: + duration: :class:`datetime.timedelta` instance with new duration + + Returns: + A new :class:`TBox` instance + + MEOS Functions: + period_shift_tscale + + See Also: + :meth:`Period.tscale` + """ + return self.shift_tscale(duration=duration) + def shift_tscale(self, shift: Optional[timedelta] = None, duration: Optional[timedelta] = None) -> TBox: """ Returns a new TBox with the temporal span shifted by `shift` and duration `duration`. @@ -396,42 +530,27 @@ def shift_tscale(self, shift: Optional[timedelta] = None, duration: Optional[tim new_period, timedelta_to_interval(shift) if shift else None, timedelta_to_interval(duration) if duration else None, - None, None ) return TBox(_inner=new_inner) - def union(self, other: TBox) -> TBox: + def round(self, maxdd : int = 0) -> STBox: """ - Returns the union of `self` with `other`. Fails if the union is not contiguous. + Returns `self` rounded to the given number of decimal digits. Args: - other: temporal object to merge with + maxdd: Maximum number of decimal digits. Returns: - A :class:`TBox` instance. - - MEOS Functions: - union_tbox_tbox - """ - return TBox(_inner=union_tbox_tbox(self._inner, other._inner)) - - # TODO: Check returning None for empty intersection is the desired behaviour - def intersection(self, other: TBox) -> Optional[TBox]: - """ - Returns the intersection of `self` with `other`. - - Args: - other: temporal object to merge with - - Returns: - A :class:`TBox` instance if the instersection is not empty, `None` otherwise. + A new :class:`TBox` instance MEOS Functions: - intersection_tbox_tbox + tbox_round """ - result = intersection_tbox_tbox(self._inner, other._inner) - return TBox(_inner=result) if result else None + new_inner = tbox_copy(self._inner) + tbox_round(new_inner, maxdd) + return TBox(_inner=new_inner) + # ------------------------- Topological Operations ------------------------ def is_adjacent(self, other: Union[int, float, intrange, floatrange, TBox, TNumber]) -> bool: """ Returns whether ``self`` is adjacent to ``other``. That is, they share only the temporal or numerical bound @@ -526,6 +645,29 @@ def contains(self, content: Union[TBox, TNumber]) -> bool: else: raise TypeError(f'Operation not supported with type {content.__class__}') + def __contains__(self, item): + """ + Returns whether ``self`` temporally contains ``item``. + + Examples: + >>> TBox('TBox XT([2, 3], [2012-01-02, 2012-01-03]') in TBox('TBox XT([1, 4], [2012-01-01, 2012-01-04]') + >>> True + >>> TBox('TBox XT((1, 2), (2012-01-01, 2012-01-02)') in TBox('TBox XT([1, 2], [2012-01-01, 2012-01-02]') + >>> True + >>> TBox('TBox XT([1, 2], [2012-01-01, 2012-01-02]') in TBox('TBox XT((1, 2), (2012-01-01, 2012-01-02)') + >>> False + + Args: + item: temporal object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_tbox_tbox, tnumber_to_tbox + """ + return self.contains(item) + def overlaps(self, other: Union[TBox, TNumber]) -> bool: """ Returns whether ``self`` overlaps ``other``. That is, both share at least an instant or a value. @@ -546,6 +688,7 @@ def overlaps(self, other: Union[TBox, TNumber]) -> bool: else: raise TypeError(f'Operation not supported with type {other.__class__}') + # ------------------------- Position Operations --------------------------- def is_same(self, other: Union[TBox, TNumber]) -> bool: """ Returns whether ``self`` is the same as ``other``. @@ -731,25 +874,21 @@ def is_over_or_after(self, other: Union[TBox, TNumber]) -> bool: else: raise TypeError(f'Operation not supported with type {other.__class__}') - def nearest_approach_distance(self, other: Union[TBox, TNumber]) -> float: + # ------------------------- Set Operations -------------------------------- + def union(self, other: TBox) -> TBox: """ - Returns the distance between the nearest points of ``self`` and ``other``. + Returns the union of `self` with `other`. Fails if the union is not contiguous. Args: - other: temporal object to compare with + other: temporal object to merge with Returns: - A :class:`float` with the distance between the nearest points of ``self`` and ``other``. + A :class:`TBox` instance. MEOS Functions: - nad_tbox_tbox + union_tbox_tbox """ - if isinstance(other, TBox): - return nad_tbox_tbox(self._inner, other._inner) - elif isinstance(other, TNumber): - return nad_tnumber_tbox(other._inner, self._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') + return TBox(_inner=union_tbox_tbox(self._inner, other._inner)) def __add__(self, other): """ @@ -766,6 +905,23 @@ def __add__(self, other): """ return self.union(other) + # TODO: Check returning None for empty intersection is the desired behaviour + def intersection(self, other: TBox) -> Optional[TBox]: + """ + Returns the intersection of `self` with `other`. + + Args: + other: temporal object to merge with + + Returns: + A :class:`TBox` instance if the instersection is not empty, `None` otherwise. + + MEOS Functions: + intersection_tbox_tbox + """ + result = intersection_tbox_tbox(self._inner, other._inner) + return TBox(_inner=result) if result else None + def __mul__(self, other): """ Returns the intersection of `self` with `other`. @@ -781,29 +937,73 @@ def __mul__(self, other): """ return self.intersection(other) - def __contains__(self, item): + # ------------------------- Distance Operations --------------------------- + def nearest_approach_distance(self, other: Union[TBox, TNumber]) -> float: """ - Returns whether ``self`` temporally contains ``item``. + Returns the distance between the nearest points of ``self`` and ``other``. - Examples: - >>> TBox('TBox XT([2, 3], [2012-01-02, 2012-01-03]') in TBox('TBox XT([1, 4], [2012-01-01, 2012-01-04]') - >>> True - >>> TBox('TBox XT((1, 2), (2012-01-01, 2012-01-02)') in TBox('TBox XT([1, 2], [2012-01-01, 2012-01-02]') - >>> True - >>> TBox('TBox XT([1, 2], [2012-01-01, 2012-01-02]') in TBox('TBox XT((1, 2), (2012-01-01, 2012-01-02)') - >>> False + Args: + other: temporal object to compare with + + Returns: + A :class:`float` with the distance between the nearest points of ``self`` and ``other``. + + MEOS Functions: + nad_tbox_tbox + """ + if isinstance(other, TBox): + return nad_tbox_tbox(self._inner, other._inner) + elif isinstance(other, TNumber): + return nad_tnumber_tbox(other._inner, self._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + # ------------------------- Splitting -------------------------------------- + def tile(self, size: float, duration: Union[timedelta, str], + origin: float = 0.0, start: Union[datetime, str, None] = None) -> List[List[TBox]]: + """ + Returns 2d matrix of TBoxes resulting of tiling ``self``. Args: - item: temporal object to compare with + size: size of the numeric dimension of the tiles + duration: size of the temporal dimenstion of the tiles + origin: origin of the numeric dimension of the tiles + start: origin of the temporal dimension of the tiles Returns: - True if contains, False otherwise + A 2d array of :class:`TBox` instances. MEOS Functions: - contains_tbox_tbox, tnumber_to_tbox + tbox_tile_list """ - return self.contains(item) + dt = timedelta_to_interval(duration) if isinstance(duration, timedelta) else pg_interval_in(duration, -1) + st = datetime_to_timestamptz(start) if isinstance(start, datetime) \ + else pg_timestamptz_in(start, -1) if isinstance(start, str) \ + else tbox_tmin(self._inner) + tiles, rows, columns = tbox_tile_list(self._inner, size, dt, origin, st) + return [[TBox(_inner=tiles + (c * rows + r)) for c in range(columns)] for r in range(rows)] + + def tile_flat(self, size: float, duration: Union[timedelta, str], + origin: float = 0.0, start: Union[datetime, str, None] = None) -> List[TBox]: + """ + Returns an array of TBoxes resulting of tiling ``self``. + + Args: + size: size of the numeric dimension of the tiles + duration: size of the temporal dimenstion of the tiles + origin: origin of the numeric dimension of the tiles + start: origin of the temporal dimension of the tiles + Returns: + An array of :class:`TBox` instances. + + MEOS Functions: + tbox_tile_list + """ + tiles = self.tile(size, duration, origin, start) + return [box for row in tiles for box in row] + + # ------------------------- Comparisons ----------------------------------- def __eq__(self, other): """ Returns whether ``self`` is equal to ``other``. @@ -838,25 +1038,6 @@ def __ne__(self, other): return tbox_ne(self._inner, other._inner) return True - def __cmp__(self, other) -> int: - """ - Returns the result of comparing ``self`` to ``other``. The time dimension is compared first, then the space - dimension. - - Args: - other: temporal object to compare with - - Returns: - -1 if ``self`` is less than ``other``, 0 if ``self`` is equal to ``other``, 1 if ``self`` is greater than - ``other``. - - MEOS Functions: - tbox_cmp - """ - if isinstance(other, self.__class__): - return tbox_cmp(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - def __lt__(self, other): """ Returns whether ``self`` is less than ``other``. The time dimension is compared first, then the space dimension. @@ -928,44 +1109,7 @@ def __ge__(self, other): return tbox_ge(self._inner, other._inner) raise TypeError(f'Operation not supported with type {other.__class__}') - def __copy__(self) -> TBox: - """ - Returns a copy of ``self``. - - Returns: - A :class:`TBox` instance. - - MEOS Functions: - tbox_copy - """ - inner_copy = tbox_copy(self._inner) - return TBox(_inner=inner_copy) - - def __str__(self): - """ - Returns a string representation of ``self``. - - Returns: - A :class:`str` instance. - - MEOS Functions: - tbox_out - """ - return tbox_out(self._inner, 15) - - def __repr__(self): - """ - Returns a string representation of ``self``. - - Returns: - A :class:`str` instance. - - MEOS Functions: - tbox_out - """ - return (f'{self.__class__.__name__}' - f'({self})') - + # ------------------------- Plot Operations ------------------------------- def plot(self, *args, **kwargs): """ Plots ``self``. @@ -976,6 +1120,7 @@ def plot(self, *args, **kwargs): from ..plotters import BoxPlotter return BoxPlotter.plot_tbox(self, *args, **kwargs) + # ------------------------- Database Operations --------------------------- @staticmethod def read_from_cursor(value, _=None): """ diff --git a/pymeos/pymeos/main/tbool.py b/pymeos/pymeos/main/tbool.py index 3ac597a1..f4b063dd 100644 --- a/pymeos/pymeos/main/tbool.py +++ b/pymeos/pymeos/main/tbool.py @@ -1,6 +1,7 @@ from __future__ import annotations from abc import ABC +from functools import reduce from typing import Optional, Union, List, Set, overload from pymeos_cffi import * @@ -19,62 +20,23 @@ class TBool(Temporal[bool, 'TBool', 'TBoolInst', 'TBoolSeq', 'TBoolSeqSet'], ABC def __init__(self, _inner) -> None: super().__init__() - def at(self, other: Union[bool, Time]) -> TBool: - """ - Returns a new temporal boolean with the values of `self` restricted to the time or value `other`. - - Args: - other: Time or value to restrict to. - - Returns: - A new temporal boolean. - - MEOS Functions: - tbool_at_value, temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset - """ - if isinstance(other, bool): - result = tbool_at_value(self._inner, other) - else: - return super().at(other) - return Temporal._factory(result) - - def minus(self, other: Union[bool, Time]) -> TBool: - """ - Returns a new temporal boolean with the values of `self` restricted to the complement of the time or value - `other`. - - Args: - other: Time or value to restrict to the complement of. - - Returns: - A new temporal boolean. - - MEOS Functions: - tbool_minus_value, temporal_minus_timestamp, temporal_minus_timestampset, temporal_minus_period, - temporal_minus_periodset - """ - if isinstance(other, bool): - result = tbool_minus_value(self._inner, other) - else: - return super().minus(other) - return Temporal._factory(result) - + # ------------------------- Constructors ---------------------------------- @staticmethod - def from_base(value: bool, base: Temporal) -> TBool: + def from_base_temporal(value: bool, base: Temporal) -> TBool: """ - Create a temporal boolean from a boolean value and the time frame of another temporal object. + Create a temporal Boolean from a Boolean value and the time frame of another temporal object. Args: value: Boolean value. base: Temporal object to use as time frame. Returns: - A new temporal boolean. + A new :class:`TBool` object. MEOS Functions: - tbool_from_base + tbool_from_base_temp """ - result = tbool_from_base(value, base._inner) + result = tbool_from_base_temp(value, base._inner) return Temporal._factory(result) @staticmethod @@ -105,19 +67,39 @@ def from_base_time(value: bool, base: Time) -> TBool: A new temporal boolean. MEOS Functions: - tboolinst_make, tbooldiscseq_from_base_time, tboolseq_from_base_time, tboolseqset_from_base_time - + tboolinst_make, tboolseq_from_base_timestampset, + tboolseq_from_base_period, tboolseqset_from_base_periodset """ if isinstance(base, datetime): return TBoolInst(_inner=tboolinst_make(value, datetime_to_timestamptz(base))) elif isinstance(base, TimestampSet): - return TBoolSeq(_inner=tbooldiscseq_from_base_time(value, base._inner)) + return TBoolSeq(_inner=tboolseq_from_base_timestampset(value, base._inner)) elif isinstance(base, Period): - return TBoolSeq(_inner=tboolseq_from_base_time(value, base._inner)) + return TBoolSeq(_inner=tboolseq_from_base_period(value, base._inner)) elif isinstance(base, PeriodSet): - return TBoolSeqSet(_inner=tboolseqset_from_base_time(value, base._inner)) + return TBoolSeqSet(_inner=tboolseqset_from_base_periodset(value, base._inner)) raise TypeError(f'Operation not supported with type {base.__class__}') + # ------------------------- Output ---------------------------------------- + def __str__(self): + """ + Returns the string representation of `self`. + + MEOS Function: + tbool_out + """ + return tbool_out(self._inner) + + def as_wkt(self): + """ + Returns the string representation of `self` in WKT format. + + MEOS Function: + tbool_out + """ + return tbool_out(self._inner) + + # ------------------------- Accessors ------------------------------------- def value_set(self) -> Set[bool]: """ Returns the unique values in `self`. @@ -161,7 +143,8 @@ def value_at_timestamp(self, timestamp) -> bool: """ return tbool_value_at_timestamp(self._inner, datetime_to_timestamptz(timestamp), True) - def always(self, value: bool) -> bool: + # ------------------------- Ever and Always Comparisons ------------------- + def always_eq(self, value: bool) -> bool: """ Returns whether `self` is always equal to `value`. @@ -176,7 +159,7 @@ def always(self, value: bool) -> bool: """ return tbool_always_eq(self._inner, value) - def ever(self, value: bool) -> bool: + def ever_eq(self, value: bool) -> bool: """ Returns whether `self` is ever equal to `value`. @@ -191,7 +174,7 @@ def ever(self, value: bool) -> bool: """ return tbool_ever_eq(self._inner, value) - def never(self, value: bool) -> bool: + def never_eq(self, value: bool) -> bool: """ Returns whether `self` is never equal to `value`. @@ -206,30 +189,7 @@ def never(self, value: bool) -> bool: """ return not tbool_ever_eq(self._inner, value) - def when_true(self) -> PeriodSet: - """ - Returns a period set with the periods where `self` is True. - - Returns: - A :class:`PeriodSet` with the periods where `self` is True. - - MEOS Function: - tbool_when_true - """ - return PeriodSet(_inner=tbool_when_true(self._inner)) - - def when_false(self) -> PeriodSet: - """ - Returns a period set with the periods where `self` is False. - - Returns: - A :class:`PeriodSet` with the periods where `self` is False. - - MEOS Function: - tbool_when_true, tnot_tbool - """ - return PeriodSet(_inner=tbool_when_true(tnot_tbool(self._inner))) - + # ------------------------- Temporal Comparisons -------------------------- def temporal_equal(self, other: Union[bool, Temporal]) -> TBool: """ Returns the temporal equality relation between `self` and `other`. @@ -268,18 +228,48 @@ def temporal_not_equal(self, other: Union[bool, Temporal]) -> TBool: return super().temporal_not_equal(other) return Temporal._factory(result) - def temporal_not(self) -> TBool: + # ------------------------- Restrictions ---------------------------------- + def at(self, other: Union[bool, Time]) -> TBool: """ - Returns the temporal negation of `self`. + Returns a new temporal boolean with the values of `self` restricted to the time or value `other`. + + Args: + other: Time or value to restrict to. Returns: - A :class:`TBool` with the temporal negation of `self`. + A new temporal boolean. - MEOS Function: - tnot_tbool + MEOS Functions: + tbool_at_value, temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset """ - return self.__class__(_inner=tnot_tbool(self._inner)) + if isinstance(other, bool): + result = tbool_at_value(self._inner, other) + else: + return super().at(other) + return Temporal._factory(result) + def minus(self, other: Union[bool, Time]) -> TBool: + """ + Returns a new temporal boolean with the values of `self` restricted to the complement of the time or value + `other`. + + Args: + other: Time or value to restrict to the complement of. + + Returns: + A new temporal boolean. + + MEOS Functions: + tbool_minus_value, temporal_minus_timestamp, temporal_minus_timestampset, temporal_minus_period, + temporal_minus_periodset + """ + if isinstance(other, bool): + result = tbool_minus_value(self._inner, other) + else: + return super().minus(other) + return Temporal._factory(result) + + # ------------------------- Boolean Operations ---------------------------- def temporal_and(self, other: Union[bool, TBool]) -> TBool: """ Returns the temporal conjunction of `self` and `other`. @@ -299,6 +289,21 @@ def temporal_and(self, other: Union[bool, TBool]) -> TBool: return self.__class__(_inner=tand_tbool_tbool(self._inner, other._inner)) raise TypeError(f'Operation not supported with type {other.__class__}') + def __and__(self, other): + """ + Returns the temporal conjunction of `self` and `other`. + + Args: + other: A temporal or boolean object to combine with `self`. + + Returns: + A :class:`TBool` with the temporal conjunction of `self` and `other`. + + MEOS Functions: + tand_tbool_bool, tand_tbool_tbool + """ + return self.temporal_and(other) + def temporal_or(self, other: Union[bool, TBool]) -> TBool: """ Returns the temporal disjunction of `self` and `other`. @@ -318,7 +323,22 @@ def temporal_or(self, other: Union[bool, TBool]) -> TBool: return self.__class__(_inner=tor_tbool_tbool(self._inner, other._inner)) raise TypeError(f'Operation not supported with type {other.__class__}') - def __neg__(self): + def __or__(self, other): + """ + Returns the temporal disjunction of `self` and `other`. + + Args: + other: A temporal or boolean object to combine with `self`. + + Returns: + A :class:`TBool` with the temporal disjunction of `self` and `other`. + + MEOS Functions: + tor_tbool_bool, tor_tbool_tbool + """ + return self.temporal_or(other) + + def temporal_not(self) -> TBool: """ Returns the temporal negation of `self`. @@ -328,9 +348,9 @@ def __neg__(self): MEOS Function: tnot_tbool """ - return self.temporal_not() + return self.__class__(_inner=tnot_tbool(self._inner)) - def __invert__(self): + def __neg__(self): """ Returns the temporal negation of `self`. @@ -342,54 +362,45 @@ def __invert__(self): """ return self.temporal_not() - def __and__(self, other): + def __invert__(self): """ - Returns the temporal conjunction of `self` and `other`. - - Args: - other: A temporal or boolean object to combine with `self`. + Returns the temporal negation of `self`. Returns: - A :class:`TBool` with the temporal conjunction of `self` and `other`. + A :class:`TBool` with the temporal negation of `self`. - MEOS Functions: - tand_tbool_bool, tand_tbool_tbool + MEOS Function: + tnot_tbool """ - return self.temporal_and(other) + return self.temporal_not() - def __or__(self, other): + def when_true(self) -> Optional[PeriodSet]: """ - Returns the temporal disjunction of `self` and `other`. - - Args: - other: A temporal or boolean object to combine with `self`. + Returns a period set with the periods where `self` is True. Returns: - A :class:`TBool` with the temporal disjunction of `self` and `other`. - - MEOS Functions: - tor_tbool_bool, tor_tbool_tbool - """ - return self.temporal_or(other) - - def __str__(self): - """ - Returns the string representation of `self`. + A :class:`PeriodSet` with the periods where `self` is True. MEOS Function: - tbool_out + tbool_when_true """ - return tbool_out(self._inner) + result = tbool_when_true(self._inner) + return PeriodSet(_inner=result) if result is not None else None - def as_wkt(self): + def when_false(self) -> Optional[PeriodSet]: """ - Returns the string representation of `self` in WKT format. + Returns a period set with the periods where `self` is False. + + Returns: + A :class:`PeriodSet` with the periods where `self` is False. MEOS Function: - tbool_out + tbool_when_true, tnot_tbool """ - return tbool_out(self._inner) + result = tbool_when_true(tnot_tbool(self._inner)) + return PeriodSet(_inner=result) if result is not None else None + # ------------------------- Database Operations --------------------------- @staticmethod def read_from_cursor(value, _=None): """ @@ -410,6 +421,7 @@ def read_from_cursor(value, _=None): raise Exception("ERROR: Could not parse temporal boolean value") + class TBoolInst(TInstant[bool, 'TBool', 'TBoolInst', 'TBoolSeq', 'TBoolSeqSet'], TBool): """ Class for representing temporal boolean values at a single instant. diff --git a/pymeos/pymeos/main/tfloat.py b/pymeos/pymeos/main/tfloat.py index bcbb87c0..2d009f44 100644 --- a/pymeos/pymeos/main/tfloat.py +++ b/pymeos/pymeos/main/tfloat.py @@ -2,7 +2,7 @@ from abc import ABC from functools import reduce -from typing import Optional, List, Union, TYPE_CHECKING, Set +from typing import Optional, List, Union, TYPE_CHECKING, Set, overload from pymeos_cffi import * from spans.types import floatrange, intrange @@ -20,36 +20,200 @@ class TFloat(TNumber[float, 'TFloat', 'TFloatInst', 'TFloatSeq', 'TFloatSeqSet'] BaseClass = float _parse_function = tfloat_in - def always_less(self, value: float) -> bool: + # ------------------------- Constructors ---------------------------------- + @staticmethod + def from_base_temporal(value: float, base: Temporal, interpolation: TInterpolation = TInterpolation.LINEAR) -> TFloat: """ - Returns whether the values of `self` are always less than `value`. + Returns a new temporal float with the value `value` and the temporal frame of `base`. Args: - value: :class:`float` to compare. + value: Value of the temporal float. + base: Temporal object to get the temporal frame from. + interpolation: Interpolation of the temporal float. Returns: - `True` if the values of `self` are always less than `value`, `False` otherwise. + A new :class:`TFloat` object. MEOS Functions: - tfloat_always_lt + tfloat_from_base_temp """ - return tfloat_always_lt(self._inner, value) + result = tfloat_from_base_temp(value, base._inner) + return Temporal._factory(result) - def always_less_or_equal(self, value: float) -> bool: + @staticmethod + @overload + def from_base_time(value: float, base: datetime) -> TFloatInst: + ... + + @staticmethod + @overload + def from_base_time(value: float, base: Union[TimestampSet, Period]) -> TFloatSeq: + ... + + @staticmethod + @overload + def from_base_time(value: float, base: PeriodSet) -> TFloatSeqSet: + ... + + @staticmethod + def from_base_time(value: float, base: Time, interpolation: TInterpolation = None) -> TFloat: """ - Returns whether the values of `self` are always less than or equal to `value`. + Returns a new temporal float with the value `value` and the temporal frame of `base`. Args: - value: :class:`float` to compare. + value: Value of the temporal float. + base: Time object to get the temporal frame from. + interpolation: Interpolation of the temporal float. Returns: - `True` if the values of `self` are always less than or equal to `value`, `False` otherwise. + A new temporal float. MEOS Functions: - tfloat_always_le + tfloatinst_make, tfloatseq_from_base_timestampset, tfloatseq_from_base_time, tfloatseqset_from_base_time """ - return tfloat_always_le(self._inner, value) + if isinstance(base, datetime): + return TFloatInst(_inner=tfloatinst_make(value, datetime_to_timestamptz(base))) + elif isinstance(base, TimestampSet): + return TFloatSeq(_inner=tfloatseq_from_base_timestampset(value, base._inner)) + elif isinstance(base, Period): + return TFloatSeq(_inner=tfloatseq_from_base_period(value, base._inner, interpolation)) + elif isinstance(base, PeriodSet): + return TFloatSeqSet(_inner=tfloatseqset_from_base_periodset(value, base._inner, interpolation)) + raise TypeError(f'Operation not supported with type {base.__class__}') + + # ------------------------- Output ---------------------------------------- + def __str__(self, max_decimals=15): + """ + Returns a string representation of `self`. + + Returns: + A string representation of `self`. + + MEOS Functions: + tfloat_out + """ + return tfloat_out(self._inner, max_decimals) + + def to_str(self, max_decimals=15) -> str: + """ + Returns a string representation of `self` with a maximum number of decimals. + + Args: + max_decimals: The maximum number of decimals. + + Returns: + A string representation of `self`. + + MEOS Functions: + tfloat_out + """ + return tfloat_out(self._inner, max_decimals) + + def as_wkt(self, precision: int = 15) -> str: + """ + Returns a WKT representation of `self`. + + Args: + precision: The number of decimals to use. + + Returns: + A WKT representation of `self`. + + MEOS Functions: + tfloat_out + """ + return tfloat_out(self._inner, precision) + + # ------------------------- Accessors ------------------------------------- + def value_range(self) -> floatrange: + """ + Returns the value span of `self`. + + Returns: + An :class:`floatrange` with the value span of `self`. + + MEOS Functions: + tnumber_to_span + """ + return self.to_floatrange() + + def value_ranges(self) -> List[floatrange]: + """ + Returns the value spans of `self` taking into account gaps. + + Returns: + A list of :class:`floatrange` with the value spans of `self`. + + MEOS Functions: + tfloat_spanset + """ + spanset = tnumber_values(self._inner) + spans, count = spanset_spans(spanset) + return [floatspan_to_floatrange(spans[i]) for i in range(count)] + + def start_value(self) -> float: + """ + Returns the start value of `self`. + + Returns: + A :class:`float` with the start value. + + MEOS Functions: + tfloat_start_value + """ + return tfloat_start_value(self._inner) + + def end_value(self) -> float: + """ + Returns the end value of `self`. + + Returns: + A :class:`float` with the end value. + + MEOS Functions: + tfloat_end_value + """ + return tfloat_end_value(self._inner) + def value_set(self) -> Set[float]: + """ + Returns the set of values of `self`. + Note that when the interpolation is linear, the set will contain only the waypoints. + + Returns: + A :class:`set` with the values of `self`. + + MEOS Functions: + tint_values + """ + values, count = tfloat_values(self._inner) + return {values[i] for i in range(count)} + + def min_value(self) -> float: + """ + Returns the minimum value of the `self`. + + Returns: + A :class:`float` with the minimum value. + + MEOS Functions: + tfloat_min_value + """ + return tfloat_min_value(self._inner) + + def max_value(self) -> float: + """ + Returns the maximum value of the `self`. + + Returns: + A :class:`float` with the maximum value. + + MEOS Functions: + tfloat_max_value + """ + return tfloat_max_value(self._inner) + + # ------------------------- Ever and Always Comparisons ------------------- def always_equal(self, value: float) -> bool: """ Returns whether the values of `self` are always equal to `value`. @@ -80,6 +244,36 @@ def always_not_equal(self, value: float) -> bool: """ return not tfloat_ever_eq(self._inner, value) + def always_less(self, value: float) -> bool: + """ + Returns whether the values of `self` are always less than `value`. + + Args: + value: :class:`float` to compare. + + Returns: + `True` if the values of `self` are always less than `value`, `False` otherwise. + + MEOS Functions: + tfloat_always_lt + """ + return tfloat_always_lt(self._inner, value) + + def always_less_or_equal(self, value: float) -> bool: + """ + Returns whether the values of `self` are always less than or equal to `value`. + + Args: + value: :class:`float` to compare. + + Returns: + `True` if the values of `self` are always less than or equal to `value`, `False` otherwise. + + MEOS Functions: + tfloat_always_le + """ + return tfloat_always_le(self._inner, value) + def always_greater_or_equal(self, value: float) -> bool: """ Returns whether the values of `self` are always greater than or equal to `value`. @@ -200,65 +394,65 @@ def ever_greater(self, value: float) -> bool: """ return not tfloat_always_le(self._inner, value) - def never_less(self, value: float) -> bool: + def never_equal(self, value: float) -> bool: """ - Returns whether the values of `self` are never less than `value`. + Returns whether the values of `self` are never equal to `value`. Args: value: :class:`float` value to compare. Returns: - `True` if the values of `self` are never less than `value`, `False` otherwise. + `True` if the values of `self` are never equal to `value`, `False` otherwise. MEOS Functions: - tfloat_ever_lt + tfloat_ever_eq """ - return not tfloat_ever_lt(self._inner, value) + return not tfloat_ever_eq(self._inner, value) - def never_less_or_equal(self, value: float) -> bool: + def never_not_equal(self, value: float) -> bool: """ - Returns whether the values of `self` are never less than or equal to `value`. + Returns whether the values of `self` are never not equal to `value`. Args: value: :class:`float` value to compare. Returns: - `True` if the values of `self` are never less than or equal to `value`, `False` otherwise. + `True` if the values of `self` are never not equal to `value`, `False` otherwise. MEOS Functions: - tfloat_ever_le + tfloat_always_eq """ - return not tfloat_ever_le(self._inner, value) + return tfloat_always_eq(self._inner, value) - def never_equal(self, value: float) -> bool: + def never_less(self, value: float) -> bool: """ - Returns whether the values of `self` are never equal to `value`. + Returns whether the values of `self` are never less than `value`. Args: value: :class:`float` value to compare. Returns: - `True` if the values of `self` are never equal to `value`, `False` otherwise. + `True` if the values of `self` are never less than `value`, `False` otherwise. MEOS Functions: - tfloat_ever_eq + tfloat_ever_lt """ - return not tfloat_ever_eq(self._inner, value) + return not tfloat_ever_lt(self._inner, value) - def never_not_equal(self, value: float) -> bool: + def never_less_or_equal(self, value: float) -> bool: """ - Returns whether the values of `self` are never not equal to `value`. + Returns whether the values of `self` are never less than or equal to `value`. Args: value: :class:`float` value to compare. Returns: - `True` if the values of `self` are never not equal to `value`, `False` otherwise. + `True` if the values of `self` are never less than or equal to `value`, `False` otherwise. MEOS Functions: - tfloat_always_eq + tfloat_ever_le """ - return tfloat_always_eq(self._inner, value) + return not tfloat_ever_le(self._inner, value) def never_greater_or_equal(self, value: float) -> bool: """ @@ -290,80 +484,81 @@ def never_greater(self, value: float) -> bool: """ return tfloat_always_le(self._inner, value) - def temporal_less(self, other: Union[int, float, Temporal]) -> Temporal: + # ------------------------- Temporal Comparisons -------------------------- + def temporal_equal(self, other: Union[int, float, Temporal]) -> Temporal: """ - Returns the temporal less than relation between `self` and `other`. + Returns the temporal equality relation between `self` and `other`. Args: other: An :class:`int`, :class:`float` or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal less than relation. + A :class:`TBool` with the result of the temporal equality relation. MEOS Functions: - tlt_tfloat_float, tlt_temporal_temporal + teq_tfloat_float, teq_temporal_temporal """ if isinstance(other, int) or isinstance(other, float): - result = tlt_tfloat_float(self._inner, float(other)) + result = teq_tfloat_float(self._inner, float(other)) else: - return super().temporal_less(other) + return super().temporal_equal(other) return Temporal._factory(result) - def temporal_less_or_equal(self, other: Union[int, float, Temporal]) -> Temporal: + def temporal_not_equal(self, other: Union[int, float, Temporal]) -> Temporal: """ - Returns the temporal less or equal relation between `self` and `other`. + Returns the temporal not equal relation between `self` and `other`. Args: other: An :class:`int`, :class:`float` or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal less or equal relation. + A :class:`TBool` with the result of the temporal not equal relation. MEOS Functions: - tle_tfloat_float, tle_temporal_temporal + tne_tfloat_float, tne_temporal_temporal """ if isinstance(other, int) or isinstance(other, float): - result = tle_tfloat_float(self._inner, float(other)) + result = tne_tfloat_float(self._inner, float(other)) else: - return super().temporal_less_or_equal(other) + return super().temporal_not_equal(other) return Temporal._factory(result) - def temporal_equal(self, other: Union[int, float, Temporal]) -> Temporal: + def temporal_less(self, other: Union[int, float, Temporal]) -> Temporal: """ - Returns the temporal equality relation between `self` and `other`. + Returns the temporal less than relation between `self` and `other`. Args: other: An :class:`int`, :class:`float` or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal equality relation. + A :class:`TBool` with the result of the temporal less than relation. MEOS Functions: - teq_tfloat_float, teq_temporal_temporal + tlt_tfloat_float, tlt_temporal_temporal """ if isinstance(other, int) or isinstance(other, float): - result = teq_tfloat_float(self._inner, float(other)) + result = tlt_tfloat_float(self._inner, float(other)) else: - return super().temporal_equal(other) + return super().temporal_less(other) return Temporal._factory(result) - def temporal_not_equal(self, other: Union[int, float, Temporal]) -> Temporal: + def temporal_less_or_equal(self, other: Union[int, float, Temporal]) -> Temporal: """ - Returns the temporal not equal relation between `self` and `other`. + Returns the temporal less or equal relation between `self` and `other`. Args: other: An :class:`int`, :class:`float` or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal not equal relation. + A :class:`TBool` with the result of the temporal less or equal relation. MEOS Functions: - tne_tfloat_float, tne_temporal_temporal + tle_tfloat_float, tle_temporal_temporal """ if isinstance(other, int) or isinstance(other, float): - result = tne_tfloat_float(self._inner, float(other)) + result = tle_tfloat_float(self._inner, float(other)) else: - return super().temporal_not_equal(other) + return super().temporal_less_or_equal(other) return Temporal._factory(result) def temporal_greater_or_equal(self, other: Union[int, float, Temporal]) -> Temporal: @@ -404,8 +599,9 @@ def temporal_greater(self, other: Union[int, float, Temporal]) -> Temporal: return super().temporal_greater(other) return Temporal._factory(result) - def at(self, other: Union[int, float, List[float], List[int], intrange, floatrange, List[intrange], List[ - floatrange], TBox, Time]) -> TFloat: + # ------------------------- Restrictions ---------------------------------- + def at(self, other: Union[int, float, List[int], List[float], + intrange, floatrange, List[intrange], List[floatrange], TBox, Time]) -> TFloat: """ Returns a new temporal float with the values of `self` restricted to the time or value `other`. @@ -418,18 +614,18 @@ def at(self, other: Union[int, float, List[float], List[int], intrange, floatran MEOS Functions: tfloat_at_value, temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset """ - if isinstance(other, float) or isinstance(other, int): + if isinstance(other, int) or isinstance(other, float): result = tfloat_at_value(self._inner, float(other)) elif isinstance(other, list) and (isinstance(other[0], float) or isinstance(other[0], int)): # result = tfloat_at_values(self._inner, [float(x) for x in other]) - results = [tfloat_at_value(self._inner, float(value)) for value in other] + results = [tfloat_at_value(self._inner, float(value)) for value in other if other is not None] result = temporal_merge_array(results, len(results)) else: return super().at(other) return Temporal._factory(result) def minus(self, other: Union[ - float, List[float], intrange, floatrange, List[intrange], List[floatrange], TBox, Time]) -> Temporal: + int, float, List[float], intrange, floatrange, List[intrange], List[floatrange], TBox, Time]) -> Temporal: """ Returns a new temporal float with the values of `self` restricted to the complement of the time or value `other`. @@ -444,118 +640,65 @@ def minus(self, other: Union[ tfloat_minus_value, temporal_minus_timestamp, temporal_minus_timestampset, temporal_minus_period, temporal_minus_periodset """ - if isinstance(other, float): - result = tfloat_minus_value(self._inner, other) + if isinstance(other, int) or isinstance(other, float): + result = tfloat_minus_value(self._inner, float(other)) elif isinstance(other, list) and isinstance(other[0], float): result = reduce(tfloat_minus_value, other, self._inner) else: return super().minus(other) return Temporal._factory(result) - def to_tint(self) -> TInt: - """ - Returns a new temporal integer with the values of `self` floored. - This operation can only be performed when the interpolation is stepwise or discrete. - - Returns: - A new temporal integer. - - MEOS Functions: - tfloat_to_tint - - Raises: - ValueError: If the interpolation is linear. + def value_at_timestamp(self, timestamp) -> float: """ - from ..factory import _TemporalFactory - if self.interpolation() == TInterpolation.LINEAR: - raise ValueError("Cannot convert a temporal float with linear interpolation to a temporal integer") - return _TemporalFactory.create_temporal(tfloat_to_tint(self._inner)) + Returns the value that `self` takes at a certain moment. - def to_floatrange(self) -> floatrange: - """ - Returns value span of `self`. + Args: + timestamp: The moment to get the value. Returns: - An :class:`floatrange` with the value span of `self`. + A class:`float` with the value of `self` at `timestamp`. MEOS Functions: - tnumber_to_span + tfloat_value_at_timestamp """ - return floatspan_to_floatrange(tnumber_to_span(self._inner)) + return tfloat_value_at_timestamp(self._inner, datetime_to_timestamptz(timestamp), True) - @staticmethod - def from_base(value: float, base: Temporal, interpolation: TInterpolation = TInterpolation.LINEAR) -> TFloat: + def derivative(self) -> TFloat: """ - Returns a new temporal float with the value `value` and the temporal frame of `base`. - - Args: - value: Value of the temporal float. - base: Temporal object to get the temporal frame from. - interpolation: Interpolation of the temporal float. + Returns the derivative of `self`. Returns: - A new temporal float. + A new :class:`TFloat` instance. MEOS Functions: - tfloat_from_base + tfloat_derivative """ - result = tfloat_from_base(value, base._inner, interpolation) - return Temporal._factory(result) + from ..factory import _TemporalFactory + return _TemporalFactory.create_temporal(tfloat_derivative(self._inner)) - @staticmethod - def from_base_time(value: float, base: Time, interpolation: TInterpolation = None) -> TFloat: + # ------------------------- Conversions ---------------------------------- + def to_tint(self) -> TInt: """ - Returns a new temporal float with the value `value` and the temporal frame of `base`. - - Args: - value: Value of the temporal float. - base: Time object to get the temporal frame from. - interpolation: Interpolation of the temporal float. + Returns a new temporal integer with the values of `self` floored. + This operation can only be performed when the interpolation is stepwise or discrete. Returns: - A new temporal float. + A new temporal integer. MEOS Functions: - tfloatinst_make, tfloatdiscseq_from_base_time, tfloatseq_from_base_time, tfloatseqset_from_base_time - """ - if isinstance(base, datetime): - return TFloatInst(_inner=tfloatinst_make(value, datetime_to_timestamptz(base))) - elif isinstance(base, TimestampSet): - return TFloatSeq(_inner=tfloatdiscseq_from_base_time(value, base._inner)) - elif isinstance(base, Period): - return TFloatSeq(_inner=tfloatseq_from_base_time(value, base._inner, interpolation)) - elif isinstance(base, PeriodSet): - return TFloatSeqSet(_inner=tfloatseqset_from_base_time(value, base._inner, interpolation)) - raise TypeError(f'Operation not supported with type {base.__class__}') + tfloat_to_tint - @staticmethod - def read_from_cursor(value, _=None): - """ - Reads a :class:`TFloat` from a database cursor. Used when automatically loading objects from the database. - Users should use the class constructor instead. + Raises: + ValueError: If the interpolation is linear. """ - if not value: - return None - if value.startswith('Interp=Stepwise;'): - value1 = value.replace('Interp=Stepwise;', '') - if value1[0] == '{': - return TFloatSeqSet(string=value) - else: - return TFloatSeq(string=value) - elif value[0] != '{' and value[0] != '[' and value[0] != '(': - return TFloatInst(string=value) - elif value[0] == '[' or value[0] == '(': - return TFloatSeq(string=value) - elif value[0] == '{': - if value[1] == '[' or value[1] == '(': - return TFloatSeqSet(string=value) - else: - return TFloatSeq(string=value) - raise Exception("ERROR: Could not parse temporal float value") + from ..factory import _TemporalFactory + if self.interpolation() == TInterpolation.LINEAR: + raise ValueError("Cannot convert a temporal float with linear interpolation to a temporal integer") + return _TemporalFactory.create_temporal(tfloat_to_tint(self._inner)) - def value_range(self) -> floatrange: + def to_floatrange(self) -> floatrange: """ - Returns the value span of `self`. + Returns value span of `self`. Returns: An :class:`floatrange` with the value span of `self`. @@ -563,100 +706,55 @@ def value_range(self) -> floatrange: MEOS Functions: tnumber_to_span """ - return self.to_floatrange() - - def value_ranges(self) -> List[floatrange]: - """ - Returns the value spans of `self` taking into account gaps. - - Returns: - A list of :class:`floatrange` with the value spans of `self`. - - MEOS Functions: - tfloat_spanset - """ - spanset = tnumber_values(self._inner) - spans, count = spanset_spans(spanset) - return [floatspan_to_floatrange(spans[i]) for i in range(count)] - - def start_value(self) -> float: - """ - Returns the start value of `self`. - - Returns: - A :class:`float` with the start value. - - MEOS Functions: - tfloat_start_value - """ - return tfloat_start_value(self._inner) - - def end_value(self) -> float: - """ - Returns the end value of `self`. - - Returns: - A :class:`float` with the end value. - - MEOS Functions: - tfloat_end_value - """ - return tfloat_end_value(self._inner) - - def value_set(self) -> Set[float]: - """ - Returns the set of values of `self`. - Note that when the interpolation is linear, the set will contain only the waypoints. - - Returns: - A :class:`set` with the values of `self`. + return floatspan_to_floatrange(tnumber_to_span(self._inner)) - MEOS Functions: - tint_values + def to_degrees(self, normalize: bool = True) -> TFloat: """ - values, count = tfloat_values(self._inner) - return {values[i] for i in range(count)} + Returns a copy of `self` converted from radians to degrees. - def min_value(self) -> float: - """ - Returns the minimum value of the `self`. + Args: + normalize: If True, the result will be normalized to the range [0, 360). Returns: - A :class:`float` with the minimum value. + A new :class:`TFloat` instance. MEOS Functions: - tfloat_min_value + tfloat_degrees """ - return tfloat_min_value(self._inner) + from ..factory import _TemporalFactory + return _TemporalFactory.create_temporal(tfloat_degrees(self._inner, normalize)) - def max_value(self) -> float: + def to_radians(self) -> TFloat: """ - Returns the maximum value of the `self`. + Returns a copy of `self` converted from degrees to radians. Returns: - A :class:`float` with the maximum value. + A new :class:`TFloat` instance. MEOS Functions: - tfloat_max_value + tfloat_radians """ - return tfloat_max_value(self._inner) + from ..factory import _TemporalFactory + return _TemporalFactory.create_temporal(tfloat_radians(self._inner)) - def value_at_timestamp(self, timestamp) -> float: + def round(self, maxdd : int = 0) -> TFloat: """ - Returns the value that `self` takes at a certain moment. + Returns `self` rounded to the given number of decimal digits. Args: - timestamp: The moment to get the value. + maxdd: Maximum number of decimal digits. Returns: - A class:`float` with the value of `self` at `timestamp`. + A new :class:`TFloat` instance. MEOS Functions: - tfloat_value_at_timestamp + tfloat_round """ - return tfloat_value_at_timestamp(self._inner, datetime_to_timestamptz(timestamp), True) + from ..factory import _TemporalFactory + return _TemporalFactory.create_temporal(tfloat_round(self._inner, maxdd)) - def value_split(self, start: float, size: float) -> List[Temporal]: + # ------------------------- Split Operations ------------------------------ + def value_split(self, size: float, start: Optional[float] = 0) -> List[Temporal]: """ Splits `self` into fragments with respect to value buckets @@ -697,89 +795,31 @@ def time_value_split(self, value_start: float, value_size: float, time_start: Un tiles, new_count = tfloat_value_time_split(self._inner, value_size, value_start, dt, st) return [Temporal._factory(tiles[i]) for i in range(new_count)] - def to_degrees(self, normalize: bool = True) -> TFloat: - """ - Returns a copy of `self` converted from radians to degrees. - - Args: - normalize: If True, the result will be normalized to the range [0, 360). - - Returns: - A new :class:`TFloat` instance. - - MEOS Functions: - tfloat_degrees - """ - from ..factory import _TemporalFactory - return _TemporalFactory.create_temporal(tfloat_degrees(self._inner, normalize)) - - def to_radians(self) -> TFloat: - """ - Returns a copy of `self` converted from degrees to radians. - - Returns: - A new :class:`TFloat` instance. - - MEOS Functions: - tfloat_radians - """ - from ..factory import _TemporalFactory - return _TemporalFactory.create_temporal(tfloat_radians(self._inner)) - - def derivative(self) -> TFloat: - """ - Returns the derivative of `self`. - - Returns: - A new :class:`TFloat` instance. - - MEOS Functions: - tfloat_derivative - """ - from ..factory import _TemporalFactory - return _TemporalFactory.create_temporal(tfloat_derivative(self._inner)) - - def to_str(self, max_decimals=15) -> str: - """ - Returns a string representation of `self` with a maximum number of decimals. - - Args: - max_decimals: The maximum number of decimals. - - Returns: - A string representation of `self`. - - MEOS Functions: - tfloat_out - """ - return tfloat_out(self._inner, max_decimals) - - def __str__(self): - """ - Returns a string representation of `self`. - - Returns: - A string representation of `self`. - - MEOS Functions: - tfloat_out - """ - return tfloat_out(self._inner, 15) - - def as_wkt(self, precision: int = 15) -> str: + # ------------------------- Database Operations --------------------------- + @staticmethod + def read_from_cursor(value, _=None): """ - Returns a WKT representation of `self`. - - Args: - precision: The number of decimals to use. - - Returns: - A WKT representation of `self`. - - MEOS Functions: - tfloat_out + Reads a :class:`TFloat` from a database cursor. Used when automatically loading objects from the database. + Users should use the class constructor instead. """ - return tfloat_out(self._inner, precision) + if not value: + return None + if value.startswith('Interp=Stepwise;'): + value1 = value.replace('Interp=Stepwise;', '') + if value1[0] == '{': + return TFloatSeqSet(string=value) + else: + return TFloatSeq(string=value) + elif value[0] != '{' and value[0] != '[' and value[0] != '(': + return TFloatInst(string=value) + elif value[0] == '[' or value[0] == '(': + return TFloatSeq(string=value) + elif value[0] == '{': + if value[1] == '[' or value[1] == '(': + return TFloatSeqSet(string=value) + else: + return TFloatSeq(string=value) + raise Exception("ERROR: Could not parse temporal float value") class TFloatInst(TInstant[float, 'TFloat', 'TFloatInst', 'TFloatSeq', 'TFloatSeqSet'], TFloat): @@ -787,7 +827,7 @@ class TFloatInst(TInstant[float, 'TFloat', 'TFloatInst', 'TFloatSeq', 'TFloatSeq Class for representing temporal floats at a single instant. """ _make_function = tfloatinst_make - _cast_function = int + _cast_function = float def __init__(self, string: Optional[str] = None, *, value: Optional[Union[str, float]] = None, timestamp: Optional[Union[str, datetime]] = None, _inner=None): @@ -801,7 +841,7 @@ class TFloatSeq(TSequence[float, 'TFloat', 'TFloatInst', 'TFloatSeq', 'TFloatSeq ComponentClass = TFloatInst def __init__(self, string: Optional[str] = None, *, instant_list: Optional[List[Union[str, TFloatInst]]] = None, - lower_inc: bool = True, upper_inc: bool = False, expandable: Union[bool, int] = False, + lower_inc: bool = True, upper_inc: bool = False, expandable: Union[bool, float] = False, interpolation: TInterpolation = TInterpolation.LINEAR, normalize: bool = True, _inner=None): super().__init__(string=string, instant_list=instant_list, lower_inc=lower_inc, upper_inc=upper_inc, expandable=expandable, interpolation=interpolation, normalize=normalize, _inner=_inner) diff --git a/pymeos/pymeos/main/tint.py b/pymeos/pymeos/main/tint.py index 852a7c68..6b027ed1 100644 --- a/pymeos/pymeos/main/tint.py +++ b/pymeos/pymeos/main/tint.py @@ -2,7 +2,7 @@ from abc import ABC from functools import reduce -from typing import Optional, Union, List, TYPE_CHECKING, Set +from typing import Optional, Union, List, TYPE_CHECKING, Set, overload from pymeos_cffi import * from spans.types import intrange, floatrange @@ -20,6 +20,181 @@ class TInt(TNumber[int, 'TInt', 'TIntInst', 'TIntSeq', 'TIntSeqSet'], ABC): BaseClass = int _parse_function = tint_in + # ------------------------- Constructors ---------------------------------- + @staticmethod + def from_base_temporal(value: int, base: Temporal) -> TInt: + """ + Returns a new temporal integer with the value `value` and the temporal frame of `base`. + + Args: + value: Value of the temporal integer. + base: Temporal object to get the temporal frame from. + + Returns: + A new :class:`TInt` object. + + MEOS Functions: + tint_from_base_temp + """ + result = tint_from_base_temp(value, base._inner) + return Temporal._factory(result) + + @staticmethod + @overload + def from_base_time(value: int, base: datetime) -> TIntInst: + ... + + @staticmethod + @overload + def from_base_time(value: int, base: Union[TimestampSet, Period]) -> TIntSeq: + ... + + @staticmethod + @overload + def from_base_time(value: int, base: PeriodSet) -> TIntSeqSet: + ... + + @staticmethod + def from_base_time(value: int, base: Time) -> TInt: + """ + Returns a new temporal int with the value `value` and the temporal frame of `base`. + + Args: + value: Value of the temporal int. + base: Time object to get the temporal frame from. + + Returns: + A new temporal int. + + MEOS Functions: + tintinst_make, tintseq_from_base_timestampset, + tintseq_from_base_period, tintseqset_from_base_periodset + """ + if isinstance(base, datetime): + return TIntInst(_inner=tintinst_make(value, datetime_to_timestamptz(base))) + elif isinstance(base, TimestampSet): + return TIntSeq(_inner=tintseq_from_base_timestampset(value, base._inner)) + elif isinstance(base, Period): + return TIntSeq(_inner=tintseq_from_base_period(value, base._inner)) + elif isinstance(base, PeriodSet): + return TIntSeqSet(_inner=tintseqset_from_base_periodset(value, base._inner)) + raise TypeError(f'Operation not supported with type {base.__class__}') + + # ------------------------- Output ---------------------------------------- + def __str__(self): + """ + Returns a string representation of `self`. + + Returns: + A string representation of `self`. + + MEOS Functions: + tint_out + """ + return tint_out(self._inner) + + def as_wkt(self): + """ + Returns a WKT representation of `self`. + + Returns: + A WKT representation of `self`. + + MEOS Functions: + tint_as_wkt + """ + return tint_out(self._inner) + + # ------------------------- Accessors ------------------------------------- + def value_range(self) -> intrange: + """ + Returns the value span of `self`. + + Returns: + An :class:`intrange` with the value span of `self`. + + MEOS Functions: + tnumber_to_span + """ + return self.to_intrange() + + def start_value(self) -> int: + """ + Returns the start value of `self`. + + Returns: + A :class:`int` with the start value. + + MEOS Functions: + tint_start_value + """ + return tint_start_value(self._inner) + + def end_value(self) -> int: + """ + Returns the end value of `self`. + + Returns: + A :class:`int` with the end value. + + MEOS Functions: + tint_end_value + """ + return tint_end_value(self._inner) + + def value_set(self) -> Set[int]: + """ + Returns the set of values of `self`. + + Returns: + A :class:`set` with the values of `self`. + + MEOS Functions: + tint_values + """ + values, count = tint_values(self._inner) + return {values[i] for i in range(count)} + + def min_value(self) -> int: + """ + Returns the minimum value of the temporal int. + + Returns: + A :class:`int` with the minimum value. + + MEOS Functions: + tint_min_value + """ + return tint_min_value(self._inner) + + def max_value(self) -> int: + """ + Returns the maximum value of the temporal int. + + Returns: + A :class:`int` with the maximum value. + + MEOS Functions: + tint_max_value + """ + return tint_max_value(self._inner) + + def value_at_timestamp(self, timestamp) -> int: + """ + Returns the value that `self` takes at a certain moment. + + Args: + timestamp: The moment to get the value. + + Returns: + An :class:`int` with the value of `self` at `timestamp`. + + MEOS Functions: + tint_value_at_timestamp + """ + return tint_value_at_timestamp(self._inner, datetime_to_timestamptz(timestamp), True) + + # ------------------------- Ever and Always Comparisons ------------------- def always_less(self, value: int) -> bool: """ Returns whether the values of `self` are always less than `value`. @@ -273,7 +448,7 @@ def never_greater_or_equal(self, value: int) -> bool: MEOS Functions: tint_always_lt """ - return tint_always_lt(self._inner, value) + return (self._inner, value) def never_greater(self, value: int) -> bool: """ @@ -290,80 +465,81 @@ def never_greater(self, value: int) -> bool: """ return tint_always_le(self._inner, value) - def temporal_less(self, other: Union[int, Temporal]) -> Temporal: + # ------------------------- Temporal Comparisons -------------------------- + def temporal_equal(self, other: Union[int, Temporal]) -> Temporal: """ - Returns the temporal less than relation between `self` and `other`. + Returns the temporal equality relation between `self` and `other`. Args: other: A :class:`int` or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal less than relation. + A :class:`TBool` with the result of the temporal equality relation. MEOS Functions: - tlt_tint_int, tlt_temporal_temporal + teq_tint_int, teq_temporal_temporal """ if isinstance(other, int): - result = tlt_tint_int(self._inner, other) + result = teq_tint_int(self._inner, other) else: - return super().temporal_less(other) + return super().temporal_equal(other) return Temporal._factory(result) - def temporal_less_or_equal(self, other: Union[int, Temporal]) -> Temporal: + def temporal_not_equal(self, other: Union[int, Temporal]) -> Temporal: """ - Returns the temporal less or equal relation between `self` and `other`. + Returns the temporal not equal relation between `self` and `other`. Args: other: A :class:`int` or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal less or equal relation. + A :class:`TBool` with the result of the temporal not equal relation. MEOS Functions: - tle_tint_int, tle_temporal_temporal + tne_tint_int, tne_temporal_temporal """ if isinstance(other, int): - result = tle_tint_int(self._inner, other) + result = tne_tint_int(self._inner, other) else: - return super().temporal_less_or_equal(other) + return super().temporal_not_equal(other) return Temporal._factory(result) - def temporal_equal(self, other: Union[int, Temporal]) -> Temporal: + def temporal_less(self, other: Union[int, Temporal]) -> Temporal: """ - Returns the temporal equality relation between `self` and `other`. + Returns the temporal less than relation between `self` and `other`. Args: other: A :class:`int` or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal equality relation. + A :class:`TBool` with the result of the temporal less than relation. MEOS Functions: - teq_tint_int, teq_temporal_temporal + tlt_tint_int, tlt_temporal_temporal """ if isinstance(other, int): - result = teq_tint_int(self._inner, other) + result = tlt_tint_int(self._inner, other) else: - return super().temporal_equal(other) + return super().temporal_less(other) return Temporal._factory(result) - def temporal_not_equal(self, other: Union[int, Temporal]) -> Temporal: + def temporal_less_or_equal(self, other: Union[int, Temporal]) -> Temporal: """ - Returns the temporal not equal relation between `self` and `other`. + Returns the temporal less or equal relation between `self` and `other`. Args: other: A :class:`int` or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal not equal relation. + A :class:`TBool` with the result of the temporal less or equal relation. MEOS Functions: - tne_tint_int, tne_temporal_temporal + tle_tint_int, tle_temporal_temporal """ if isinstance(other, int): - result = tne_tint_int(self._inner, other) + result = tle_tint_int(self._inner, other) else: - return super().temporal_not_equal(other) + return super().temporal_less_or_equal(other) return Temporal._factory(result) def temporal_greater_or_equal(self, other: Union[int, Temporal]) -> Temporal: @@ -404,11 +580,12 @@ def temporal_greater(self, other: Union[int, Temporal]) -> Temporal: return super().temporal_greater(other) return Temporal._factory(result) + # ------------------------- Restrictions ---------------------------------- def at(self, other: Union[int, List[int], intrange, floatrange, List[intrange], List[floatrange], TBox, datetime, TimestampSet, Period, PeriodSet]) -> Temporal: """ - Returns a new temporal int with the values of `self` restricted to the time or value `other`. + Returns a new temporal int with th e values of `self` restricted to the time or value `other`. Args: other: Time or value to restrict to. @@ -423,7 +600,7 @@ def at(self, other: Union[int, List[int], result = tint_at_value(self._inner, other) elif isinstance(other, list) and isinstance(other[0], int): # result = tint_at_values(self._inner, other) - results = [tint_at_value(self._inner, value) for value in other] + results = [tint_at_value(self._inner, value) for value in other if other is not None] result = temporal_merge_array(results, len(results)) else: return super().at(other) @@ -449,11 +626,18 @@ def minus(self, other: Union[int, List[int], if isinstance(other, int): result = tint_minus_value(self._inner, other) elif isinstance(other, list) and isinstance(other[0], int): - result = reduce(ttext_minus_value, other, self._inner) + # result = reduce(tint_minus_value, other, self._inner) + # result = tint_minus_values(self._inner, other) + # results = [tint_minus_value(self._inner, value) for value in other if other is not None] + # result = temporal_merge_array(results, len(results)) + result = tint_minus_value(self._inner, other) + for i in 1..len(other): + result = result.minus_value(other[i]) else: return super().minus(other) return Temporal._factory(result) + # ------------------------- Distance -------------------------------------- def nearest_approach_distance(self, other: Union[int, float, TNumber, TBox]) -> float: """ Returns the nearest approach distance between `self` and `other`. @@ -474,6 +658,7 @@ def nearest_approach_distance(self, other: Union[int, float, TNumber, TBox]) -> else: return super().nearest_approach_distance(other) + # ------------------------- Conversions ---------------------------------- def to_tfloat(self) -> TFloat: """ Returns a new temporal float with the values of `self`. @@ -499,157 +684,8 @@ def to_intrange(self) -> intrange: """ return intspan_to_intrange(tnumber_to_span(self._inner)) - @staticmethod - def from_base(value: int, base: Temporal) -> TInt: - """ - Returns a new temporal int with the value `value` and the temporal frame of `base`. - - Args: - value: Value of the temporal int. - base: Temporal object to get the temporal frame from. - - Returns: - A new temporal int. - - MEOS Functions: - tint_from_base - """ - result = tint_from_base(value, base._inner) - return Temporal._factory(result) - - @staticmethod - def from_base_time(value: int, base: Time) -> TInt: - """ - Returns a new temporal int with the value `value` and the temporal frame of `base`. - - Args: - value: Value of the temporal int. - base: Time object to get the temporal frame from. - - Returns: - A new temporal int. - - MEOS Functions: - tintinst_make, tintdiscseq_from_base_time, tintseq_from_base_time, tintseqset_from_base_time - """ - if isinstance(base, datetime): - return TIntInst(_inner=tintinst_make(value, datetime_to_timestamptz(base))) - elif isinstance(base, TimestampSet): - return TIntSeq(_inner=tintdiscseq_from_base_time(value, base._inner)) - elif isinstance(base, Period): - return TIntSeq(_inner=tintseq_from_base_time(value, base._inner)) - elif isinstance(base, PeriodSet): - return TIntSeqSet(_inner=tintseqset_from_base_time(value, base._inner)) - raise TypeError(f'Operation not supported with type {base.__class__}') - - @staticmethod - def read_from_cursor(value, _=None): - """ - Reads a :class:`TInt` from a database cursor. Used when automatically loading objects from the database. - Users should use the class constructor instead. - """ - if not value: - return None - if value[0] != '{' and value[0] != '[' and value[0] != '(': - return TIntInst(string=value) - elif value[0] == '[' or value[0] == '(': - return TIntSeq(string=value) - elif value[0] == '{': - if value[1] == '[' or value[1] == '(': - return TIntSeqSet(string=value) - else: - return TIntSeq(string=value) - raise Exception("ERROR: Could not parse temporal integer value") - - def value_range(self) -> intrange: - """ - Returns the value span of `self`. - - Returns: - An :class:`intrange` with the value span of `self`. - - MEOS Functions: - tnumber_to_span - """ - return self.to_intrange() - - def start_value(self) -> int: - """ - Returns the start value of `self`. - - Returns: - A :class:`int` with the start value. - - MEOS Functions: - tint_start_value - """ - return tint_start_value(self._inner) - - def end_value(self) -> int: - """ - Returns the end value of `self`. - - Returns: - A :class:`int` with the end value. - - MEOS Functions: - tint_end_value - """ - return tint_end_value(self._inner) - - def value_set(self) -> Set[int]: - """ - Returns the set of values of `self`. - - Returns: - A :class:`set` with the values of `self`. - - MEOS Functions: - tint_values - """ - values, count = tint_values(self._inner) - return {values[i] for i in range(count)} - - def min_value(self) -> int: - """ - Returns the minimum value of the temporal int. - - Returns: - A :class:`int` with the minimum value. - - MEOS Functions: - tint_min_value - """ - return tint_min_value(self._inner) - - def max_value(self) -> int: - """ - Returns the maximum value of the temporal int. - - Returns: - A :class:`int` with the maximum value. - - MEOS Functions: - tint_max_value - """ - return tint_max_value(self._inner) - - def value_at_timestamp(self, timestamp) -> int: - """ - Returns the value that `self` takes at a certain moment. - - Args: - timestamp: The moment to get the value. - - Returns: - An :class:`int` with the value of `self` at `timestamp`. - - MEOS Functions: - tint_value_at_timestamp - """ - return tint_value_at_timestamp(self._inner, datetime_to_timestamptz(timestamp), True) - - def value_split(self, start: int, size: int) -> List[TInt]: + # ------------------------- Split Operations ------------------------------ + def value_split(self, size: int, start: Optional[int] = 0) -> List[TInt]: """ Splits `self` into fragments with respect to value buckets @@ -689,29 +725,25 @@ def time_value_split(self, value_start: int, value_size: int, time_start: Union[ tiles, new_count = tint_value_time_split(self._inner, value_size, value_start, dt, st) return [Temporal._factory(tiles[i]) for i in range(new_count)] - def __str__(self): - """ - Returns a string representation of `self`. - - Returns: - A string representation of `self`. - - MEOS Functions: - tint_out - """ - return tint_out(self._inner) - - def as_wkt(self): + # ------------------------- Database Operations --------------------------- + @staticmethod + def read_from_cursor(value, _=None): """ - Returns a WKT representation of `self`. - - Returns: - A WKT representation of `self`. - - MEOS Functions: - tint_as_wkt + Reads a :class:`TInt` from a database cursor. Used when automatically loading objects from the database. + Users should use the class constructor instead. """ - return tint_out(self._inner) + if not value: + return None + if value[0] != '{' and value[0] != '[' and value[0] != '(': + return TIntInst(string=value) + elif value[0] == '[' or value[0] == '(': + return TIntSeq(string=value) + elif value[0] == '{': + if value[1] == '[' or value[1] == '(': + return TIntSeqSet(string=value) + else: + return TIntSeq(string=value) + raise Exception("ERROR: Could not parse temporal integer value") class TIntInst(TInstant[int, 'TInt', 'TIntInst', 'TIntSeq', 'TIntSeqSet'], TInt): diff --git a/pymeos/pymeos/main/tnumber.py b/pymeos/pymeos/main/tnumber.py index c2ed78b2..87e640b6 100644 --- a/pymeos/pymeos/main/tnumber.py +++ b/pymeos/pymeos/main/tnumber.py @@ -22,6 +22,7 @@ class TNumber(Temporal[TBase, TG, TI, TS, TSS], ABC): + # ------------------------- Accessors ------------------------------------- def bounding_box(self) -> TBox: """ Returns the bounding box of `self`. @@ -35,6 +36,7 @@ def bounding_box(self) -> TBox: from ..boxes import TBox return TBox(_inner=tnumber_to_tbox(self._inner)) + # ------------------------- Restrictions ---------------------------------- def at(self, other: Union[intrange, floatrange, List[intrange], List[floatrange], TBox, Time]) -> TG: """ Returns a new temporal object with the values of `self` restricted to the value or time `other`. @@ -83,6 +85,7 @@ def minus(self, other: Union[intrange, floatrange, List[intrange], List[floatran tnumber_minus_span, tnumber_minus_spanset, tnumber_minus_tbox, temporal_minus_timestamp, temporal_minus_timestampset, temporal_minus_period, temporal_minus_periodset """ + from ..boxes import TBox if isinstance(other, intrange): result = tnumber_minus_span(self._inner, intrange_to_intspan(other)) elif isinstance(other, floatrange): @@ -101,53 +104,68 @@ def minus(self, other: Union[intrange, floatrange, List[intrange], List[floatran return super().minus(other) return Temporal._factory(result) - def distance(self, other: Union[int, float, TNumber]) -> TFloat: + # ------------------------- Position Operations --------------------------- + def is_left(self, other: Union[Temporal, Box]) -> bool: """ - Returns the temporal distance between `self` and `other`. + Returns whether the bounding box of `self` is left to the bounding box of `other`. Args: - other: A :class:`int`, :class:`float` or :class:`TNumber` to compare to `self`. + other: A box or a temporal object to compare to `self`. Returns: - A :class:`TFloat` with the distance between `self` and `other`. + True if left, False otherwise. - MEOS Functions: - distance_tfloat_float, distance_tnumber_tnumber + See Also: + :meth:`Period.is_before` """ - if isinstance(other, int): - result = distance_tfloat_float(self._inner, float(other)) - elif isinstance(other, float): - result = distance_tfloat_float(self._inner, other) - elif isinstance(other, TNumber): - result = distance_tnumber_tnumber(self._inner, other._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - return Temporal._factory(result) + return self.bounding_box().is_left(other) - def nearest_approach_distance(self, other: Union[int, float, TNumber, TBox]) -> float: + def is_over_or_left(self, other: Union[Temporal, Box]) -> bool: """ - Returns the nearest approach distance between `self` and `other`. + Returns whether the bounding box of `self` is over or left to the bounding box of `other`. Args: - other: A :class:`int`, :class:`float`, :class:`TNumber` or :class:`TBox` to compare to `self`. + other: A box or a temporal object to compare to `self`. Returns: - A :class:`float` with the nearest approach distance between `self` and `other`. + True if over or left, False otherwise. - MEOS Functions: - nad_tfloat_float, nad_tfloat_tfloat, nad_tnumber_tbox + See Also: + :meth:`Period.is_over_or_before` """ - if isinstance(other, int): - return nad_tfloat_float(self._inner, float(other)) - elif isinstance(other, float): - return nad_tfloat_float(self._inner, other) - elif isinstance(other, TNumber): - return nad_tfloat_tfloat(self._inner, other._inner) - elif isinstance(other, TBox): - return nad_tnumber_tbox(self._inner, other._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') + return self.bounding_box().is_over_or_left(other) + + def is_right(self, other: Union[Temporal, Box]) -> bool: + """ + Returns whether the bounding box of `self` is right to the bounding box of `other`. + + Args: + other: A box or a temporal object to compare to `self`. + + Returns: + True if right, False otherwise. + + See Also: + :meth:`Period.is_after` + """ + return self.bounding_box().is_right(other) + def is_over_or_right(self, other: Union[Temporal, Box]) -> bool: + """ + Returns whether the bounding box of `self` is over or right to the bounding box of `other`. + + Args: + other: A box or a temporal object to compare to `self`. + + Returns: + True if over or right, False otherwise. + + See Also: + :meth:`Period.is_over_or_before` + """ + return self.bounding_box().is_over_or_right(other) + + # ------------------------- Mathematical Operations ------------------------- def add(self, other: Union[int, float, TNumber]) -> TNumber: """ Returns a new temporal object with the values of `self` plus `other`. @@ -159,9 +177,9 @@ def add(self, other: Union[int, float, TNumber]) -> TNumber: A new temporal object of the same subtype as `self`. """ if isinstance(other, int): - result = add_tint_int(self._inner, other) + result = add_tnumber_int(self._inner, other) elif isinstance(other, float): - result = add_tfloat_float(self._inner, other) + result = add_tnumber_float(self._inner, other) elif isinstance(other, TNumber): result = add_tnumber_tnumber(self._inner, other._inner) else: @@ -182,9 +200,9 @@ def radd(self, other: Union[int, float]) -> TNumber: add_int_tint, add_float_tfloat """ if isinstance(other, int): - result = add_int_tint(other, self._inner) + result = add_int_tnumber(other, self._inner) elif isinstance(other, float): - result = add_float_tfloat(other, self._inner) + result = add_float_tnumber(other, self._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) @@ -203,9 +221,9 @@ def sub(self, other: Union[int, float, TNumber]) -> TNumber: sub_tint_int, sub_tfloat_float, sub_tnumber_tnumber """ if isinstance(other, int): - result = sub_tint_int(self._inner, other) + result = sub_tnumber_int(self._inner, other) elif isinstance(other, float): - result = sub_tfloat_float(self._inner, other) + result = sub_tnumber_float(self._inner, other) elif isinstance(other, TNumber): result = sub_tnumber_tnumber(self._inner, other._inner) else: @@ -226,9 +244,9 @@ def rsub(self, other: Union[int, float]) -> TNumber: sub_int_tint, sub_float_tfloat """ if isinstance(other, int): - result = sub_int_tint(other, self._inner) + result = sub_int_tnumber(other, self._inner) elif isinstance(other, float): - result = sub_float_tfloat(other, self._inner) + result = sub_float_tnumber(other, self._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) @@ -247,9 +265,9 @@ def mul(self, other: Union[int, float, TNumber]) -> TNumber: mult_tint_int, mult_tfloat_float, mult_tnumber_tnumber """ if isinstance(other, int): - result = mult_tint_int(self._inner, other) + result = mult_tnumber_int(self._inner, other) elif isinstance(other, float): - result = mult_tfloat_float(self._inner, other) + result = mult_tnumber_float(self._inner, other) elif isinstance(other, TNumber): result = mult_tnumber_tnumber(self._inner, other._inner) else: @@ -270,9 +288,9 @@ def rmul(self, other: Union[int, float]) -> TNumber: mult_int_tint, mult_float_tfloat """ if isinstance(other, int): - result = mult_int_tint(other, self._inner) + result = mult_int_tnumber(other, self._inner) elif isinstance(other, float): - result = mult_float_tfloat(other, self._inner) + result = mult_float_tnumber(other, self._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) @@ -291,9 +309,9 @@ def div(self, other: Union[int, float, TNumber]) -> TNumber: div_tint_int, div_tfloat_float, div_tnumber_tnumber """ if isinstance(other, int): - result = div_tint_int(self._inner, other) + result = div_tnumber_int(self._inner, other) elif isinstance(other, float): - result = div_tfloat_float(self._inner, other) + result = div_tnumber_float(self._inner, other) elif isinstance(other, TNumber): result = div_tnumber_tnumber(self._inner, other._inner) else: @@ -314,37 +332,13 @@ def rdiv(self, other: Union[int, float]) -> TNumber: div_int_tint, div_float_tfloat """ if isinstance(other, int): - result = div_int_tint(other, self._inner) + result = div_int_tnumber(other, self._inner) elif isinstance(other, float): - result = div_float_tfloat(other, self._inner) + result = div_float_tnumber(other, self._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) - def integral(self) -> float: - """ - Returns the integral of `self`. - - Returns: - The integral of `self`. - - MEOS Function: - tnumber_integral - """ - return tnumber_integral(self._inner) - - def time_weighted_average(self) -> float: - """ - Returns the time weighted average of `self`. - - Returns: - The time weighted average of `self`. - - MEOS Function: - tnumber_twavg - """ - return tnumber_twavg(self._inner) - def __add__(self, other): """ Returns a new temporal object with the values of `self` plus `other`. @@ -461,3 +455,103 @@ def __rtruediv__(self, other): div_int_tint, div_float_tfloat """ return self.rdiv(other) + + def abs(self) -> TNumber: + """ + Returns the absolute value of `self`. + + Returns: + A new :class:`TNumber` instance. + + MEOS Functions: + tnumber_abs + """ + from ..factory import _TemporalFactory + return _TemporalFactory.create_temporal(tnumber_abs(self._inner)) + + def delta_value(self) -> TNumber: + """ + Returns the value difference between consecutive instants of `self`. + + Returns: + A new :class:`TNumber` instance. + + MEOS Functions: + tnumber_delta_value + """ + from ..factory import _TemporalFactory + return _TemporalFactory.create_temporal(tnumber_delta_value(self._inner)) + + # ------------------------- Distance Operations -------------------------- + def distance(self, other: Union[int, float, TNumber]) -> TFloat: + """ + Returns the temporal distance between `self` and `other`. + + Args: + other: A :class:`int`, :class:`float` or :class:`TNumber` to compare to `self`. + + Returns: + A :class:`TFloat` with the distance between `self` and `other`. + + MEOS Functions: + distance_tfloat_float, distance_tnumber_tnumber + """ + if isinstance(other, int): + result = distance_tfloat_float(self._inner, float(other)) + elif isinstance(other, float): + result = distance_tfloat_float(self._inner, other) + elif isinstance(other, TNumber): + result = distance_tnumber_tnumber(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + return Temporal._factory(result) + + def nearest_approach_distance(self, other: Union[int, float, TNumber, TBox]) -> float: + """ + Returns the nearest approach distance between `self` and `other`. + + Args: + other: A :class:`int`, :class:`float`, :class:`TNumber` or :class:`TBox` to compare to `self`. + + Returns: + A :class:`float` with the nearest approach distance between `self` and `other`. + + MEOS Functions: + nad_tfloat_float, nad_tfloat_tfloat, nad_tnumber_tbox + """ + if isinstance(other, int): + return nad_tfloat_float(self._inner, float(other)) + elif isinstance(other, float): + return nad_tfloat_float(self._inner, other) + elif isinstance(other, TNumber): + return nad_tfloat_tfloat(self._inner, other._inner) + elif isinstance(other, TBox): + return nad_tnumber_tbox(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + + # ------------------------- Aggregate Operations -------------------------- + def integral(self) -> float: + """ + Returns the integral of `self`. + + Returns: + The integral of `self`. + + MEOS Function: + tnumber_integral + """ + return tnumber_integral(self._inner) + + def time_weighted_average(self) -> float: + """ + Returns the time weighted average of `self`. + + Returns: + The time weighted average of `self`. + + MEOS Function: + tnumber_twavg + """ + return tnumber_twavg(self._inner) + diff --git a/pymeos/pymeos/main/tpoint.py b/pymeos/pymeos/main/tpoint.py index 21e63051..d34bcda1 100644 --- a/pymeos/pymeos/main/tpoint.py +++ b/pymeos/pymeos/main/tpoint.py @@ -2,7 +2,7 @@ from abc import ABC from functools import reduce -from typing import Optional, List, TYPE_CHECKING, Set, Tuple, Union, TypeVar +from typing import Optional, List, TYPE_CHECKING, Set, Tuple, Union, TypeVar, Type, overload import postgis as pg import shapely.geometry as shp @@ -11,7 +11,7 @@ from pymeos_cffi import * from .tbool import TBool -from .tfloat import TFloatSeqSet, TFloat +from .tfloat import TFloat, TFloatSeqSet from ..temporal import Temporal, TInstant, TSequence, TSequenceSet, TInterpolation from ..time import * @@ -23,36 +23,83 @@ TS = TypeVar('TS', bound='TPointSeq') TSS = TypeVar('TSS', bound='TPointSeqSet') Self = TypeVar('Self', bound='TPoint') - + class TPoint(Temporal[shp.Point, TG, TI, TS, TSS], ABC): """ Abstract class for temporal points. """ + # ------------------------- Constructors ---------------------------------- def __init__(self, _inner) -> None: super().__init__() - def srid(self) -> int: + @classmethod + def from_hexwkb(cls: Type[Self], hexwkb: str, srid: Optional[int] = None) -> Self: + result = super().from_hexwkb(hexwkb) + return result.set_srid(srid) if srid is not None else result + + # ------------------------- Output ---------------------------------------- + def __str__(self): """ - Returns the SRID. + Returns the string representation of the temporal point. Returns: - An :class:`int` representing the SRID. + A new :class:`str` representing the temporal point. MEOS Functions: - tpoint_srid + tpoint_out """ - return tpoint_srid(self._inner) + return tpoint_as_text(self._inner, 15) - def set_srid(self: Self, srid: int) -> Self: + def as_wkt(self, precision: int = 15) -> str: """ - Returns a new TPoint with the given SRID. + Returns the temporal point as a WKT string. + + Args: + precision: The precision of the returned geometry. + Returns: + A new :class:`str` representing the temporal point. + MEOS Functions: + tpoint_out """ - return self.__class__(_inner=tpoint_set_srid(self._inner, srid)) + return tpoint_as_text(self._inner, precision) + + def as_ewkt(self, precision: int = 15) -> str: + """ + Returns the temporal point as an EWKT string. + + Args: + precision: The precision of the returned geometry. + + Returns: + A new :class:`str` representing the temporal point . + + MEOS Functions: + tpoint_as_ewkt + """ + return tpoint_as_ewkt(self._inner, precision) + + def as_geojson(self, option: int = 1, precision: int = 15, srs: Optional[str] = None) -> str: + """ + Returns the trajectory of the temporal point as a GeoJSON string. + + Args: + option: The option to use when serializing the trajectory. + precision: The precision of the returned geometry. + srs: The spatial reference system of the returned geometry. + + Returns: + A new GeoJSON string representing the trajectory of the temporal point. + MEOS Functions: + gserialized_as_geojson + """ + return gserialized_as_geojson(tpoint_trajectory(self._inner), option, precision, srs) + + # ------------------------- Accessors ------------------------------------- def bounding_box(self) -> STBox: """ Returns the bounding box of the `self`. @@ -76,7 +123,8 @@ def values(self, precision: int = 15) -> List[shp.Point]: MEOS Functions: tpoint_values """ - return [i.value(precision=precision) for i in self.instants()] + result, count = tpoint_values(self._inner) + return [gserialized_to_shapely_point(result[i], precision) for i in range(count)] def start_value(self, precision: int = 15) -> shp.Point: """ @@ -133,74 +181,6 @@ def value_at_timestamp(self, timestamp: datetime, precision: int = 15) -> shp.Po return gserialized_to_shapely_point( tpoint_value_at_timestamp(self._inner, datetime_to_timestamptz(timestamp), True)[0], precision) - def simplify_min_dist(self: Self, tolerance: float) -> Self: - """ - Returns a new :class:`TPoint` with the same trajectory but simplified so that there is a minimum distance - between points. - - Args: - tolerance: A :class:`float` representing the tolerance in units of the coordinate system. - - Returns: - A new :class:`TPoint` with the simplified trajectory. - - MEOS Functions: - temporal_simplify_min_dist - """ - return self.__class__(_inner=temporal_simplify_min_dist(self._inner, tolerance)) - - def simplify_min_time_delta(self: Self, tolerance: timedelta) -> Self: - """ - Returns a new :class:`TPoint` with the same trajectory but simplified so that there is a minimum time delta - between points. - - Args: - tolerance: A :class:`timedelta` representing the tolerance. - - Returns: - A new :class:`TPoint` with the simplified trajectory. - - MEOS Functions: - temporal_simplify_min_tdelta - """ - return self.__class__(_inner=temporal_simplify_min_tdelta(self._inner, timedelta_to_interval(tolerance))) - - def simplify_douglas_peucker(self: Self, tolerance: float, synchronized: bool = False) -> Self: - """ - Returns a new :class:`TPoint` with the same trajectory but simplified using the Douglas-Peucker algorithm. - - Args: - tolerance: A :class:`float` representing the tolerance in units of the coordinate system. - synchronized: A :class:`bool` indicating whether the simplification should use the synchronized distance - or the spatial-only distance. - - Returns: - A new :class:`TPoint` with the simplified trajectory. - - MEOS Functions: - temporal_simplify_dp - """ - return self.__class__(_inner=temporal_simplify_dp(self._inner, tolerance, synchronized)) - - def simplify_max_dist(self: Self, tolerance: float, synchronized: bool = False) -> Self: - """ - Returns a new :class:`TPoint` with the same trajectory but simplified using a single-pass implementation of - the Douglas-Peucker line simplification algorithm that checks whether the projected distance threshold is - exceeded. - - Args: - tolerance: A :class:`float` representing the tolerance in units of the coordinate system. - synchronized: A :class:`bool` indicating whether the simplification should use the synchronized distance - or the spatial-only distance. - - Returns: - A new :class:`TPoint` with the simplified trajectory. - - MEOS Functions: - temporal_simplify_max_dist - """ - return self.__class__(_inner=temporal_simplify_max_dist(self._inner, tolerance, synchronized)) - def length(self) -> float: """ Returns the length of the trajectory. @@ -228,10 +208,10 @@ def cumulative_length(self) -> TFloat: def speed(self) -> TFloat: """ - Returns the speed of the trajectory. + Returns the speed of the temporal point. Returns: - A :class:`TFloat` with the speed of the trajectory. + A :class:`TFloat` with the speed of the temporal point. MEOS Functions: tpoint_speed @@ -241,10 +221,10 @@ def speed(self) -> TFloat: def x(self) -> TFloat: """ - Returns the x coordinate of the trajectory. + Returns the x coordinate of the temporal point. Returns: - A :class:`TFloat` with the x coordinate of the trajectory. + A :class:`TFloat` with the x coordinate of the temporal point. MEOS Functions: tpoint_get_coord @@ -254,10 +234,10 @@ def x(self) -> TFloat: def y(self) -> TFloat: """ - Returns the y coordinate of the trajectory. + Returns the y coordinate of the temporal point. Returns: - A :class:`TFloat` with the y coordinate of the trajectory. + A :class:`TFloat` with the y coordinate of the temporal point. MEOS Functions: tpoint_get_coord @@ -267,10 +247,10 @@ def y(self) -> TFloat: def z(self) -> TFloat: """ - Returns the z coordinate of the trajectory. + Returns the z coordinate of the temporal point. Returns: - A :class:`TFloat` with the z coordinate of the trajectory. + A :class:`TFloat` with the z coordinate of the temporal point. MEOS Functions: tpoint_get_coord @@ -280,10 +260,10 @@ def z(self) -> TFloat: def has_z(self) -> bool: """ - Returns whether the trajectory has a z coordinate. + Returns whether the temporal point has a z coordinate. Returns: - A :class:`bool` indicating whether the trajectory has a z coordinate. + A :class:`bool` indicating whether the temporal point has a z coordinate. MEOS Functions: tpoint_start_value @@ -292,7 +272,7 @@ def has_z(self) -> bool: def stboxes(self) -> List[STBox]: """ - Returns a collection of :class:`STBox`es representing the bounding boxes of the segments of the trajectory. + Returns a collection of :class:`STBox`es representing the bounding boxes of the segments of the temporal point. Returns: A :class:`list` of :class:`STBox`es. @@ -306,19 +286,117 @@ def stboxes(self) -> List[STBox]: def is_simple(self) -> bool: """ - Returns whether the trajectory is simple. That is, whether it does not self-intersect. + Returns whether the temporal point is simple. That is, whether it does not self-intersect. Returns: - A :class:`bool` indicating whether the trajectory is simple. + A :class:`bool` indicating whether the temporal point is simple. MEOS Functions: tpoint_is_simple """ return tpoint_is_simple(self._inner) + def bearing(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint]) -> TFloat: + """ + Returns the temporal bearing between the temporal point and `other`. + + Args: + other: An object to check the bearing to. + + Returns: + A new :class:`TFloat` indicating the temporal bearing between the temporal point and `other`. + + MEOS Functions: + bearing_tpoint_point, bearing_tpoint_tpoint + """ + if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + result = bearing_tpoint_point(self._inner, gs, False) + elif isinstance(other, TPoint): + result = bearing_tpoint_tpoint(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + return Temporal._factory(result) + + def azimuth(self) -> TFloatSeqSet: + """ + Returns the temporal azimuth of the temporal point. + + Returns: + A new :class:`TFloatSeqSet` indicating the temporal azimuth of the temporal point. + + MEOS Functions: + tpoint_azimuth + """ + result = tpoint_azimuth(self._inner) + return Temporal._factory(result) + + def angular_difference(self) -> TFloatSeqSet: + """ + Returns the angular_difference of the temporal point. + + Returns: + A new :class:`TFloatSeqSet` indicating the temporal angular_difference of the temporal point. + + MEOS Functions: + tpoint_angular_difference + """ + result = tpoint_angular_difference(self._inner) + return Temporal._factory(result) + + def time_weighted_centroid(self, precision: int = 15) -> shp.Point: + """ + Returns the time weighted centroid of the temporal point. + + Args: + precision: The precision of the returned geometry. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` indicating the time weighted centroid of the temporal point. + + MEOS Functions: + tpoint_twcentroid + """ + return gserialized_to_shapely_geometry(tpoint_twcentroid(self._inner), precision) # type: ignore + + # ------------------------- Spatial Reference System ---------------------- + def srid(self) -> int: + """ + Returns the SRID. + + Returns: + An :class:`int` representing the SRID. + + MEOS Functions: + tpoint_srid + """ + return tpoint_srid(self._inner) + + def set_srid(self: Self, srid: int) -> Self: + """ + Returns a new TPoint with the given SRID. + + + """ + return self.__class__(_inner=tpoint_set_srid(self._inner, srid)) + + # ------------------------- Transformations ------------------------------- + def round(self, maxdd : int = 0) -> TPoint: + """ + Round the coordinate values to a number of decimal places. + + Returns: + A new :class:`TGeomPoint` object. + + MEOS Functions: + tpoint_round + """ + result = tpoint_round(self._inner, maxdd) + return Temporal._factory(result) + def make_simple(self) -> List[TPoint]: """ - Split the trajectory into a collection of simple trajectories. + Split the temporal point into a collection of simple temporal points. Returns: A :class:`list` of :class:`TPoint`es. @@ -330,6 +408,26 @@ def make_simple(self) -> List[TPoint]: from ..factory import _TemporalFactory return [_TemporalFactory.create_temporal(result[i]) for i in range(count)] + def expand(self, other: Union[int, float]) -> STBox: + """ + Expands ``self`` with `other`. + The result is equal to ``self`` but with the spatial dimensions + expanded by `other` in all directions. + + Args: + other: The object to expand ``self`` with. + + Returns: + A new :class:`STBox` instance. + + MEOS Functions: + tpoint_expand_space + """ + from ..boxes import STBox + result = tpoint_expand_space(self._inner, float(other)) + return STBox(_inner=result) + + # ------------------------- Restrictions ---------------------------------- def at(self, other: Union[pg.Geometry, List[pg.Geometry], shpb.BaseGeometry, List[shpb.BaseGeometry], STBox, Time]) -> TG: """ @@ -347,14 +445,14 @@ def at(self, """ from ..boxes import STBox if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other) - result = tpoint_at_geometry(self._inner, gs) + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + result = tpoint_at_value(self._inner, gs) elif isinstance(other, list): - gss = [geometry_to_gserialized(gm) for gm in other] - results = [tpoint_at_geometry(self._inner, gs) for gs in gss] + gss = [geometry_to_gserialized(gm, isinstance(self, TGeogPoint)) for gm in other] + results = [tpoint_at_value(self._inner, gs) for gs in gss] result = temporal_merge_array(results, len(results)) elif isinstance(other, STBox): - result = tpoint_at_stbox(self._inner, other._inner) + result = tpoint_at_stbox(self._inner, other._inner, True) else: return super().at(other) return Temporal._factory(result) @@ -377,353 +475,543 @@ def minus(self, """ from ..boxes import STBox if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other) - result = tpoint_minus_geometry(self._inner, gs) + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + result = tpoint_minus_value(self._inner, gs) elif isinstance(other, list): - gss = [geometry_to_gserialized(gm) for gm in other] + gss = [geometry_to_gserialized(gm, isinstance(self, TGeogPoint)) for gm in other] result = reduce(tpoint_minus_value, gss, self._inner) elif isinstance(other, STBox): - result = tpoint_minus_stbox(self._inner, other._inner) + result = tpoint_minus_stbox(self._inner, other._inner, True) else: return super().minus(other) return Temporal._factory(result) - def within_distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint], distance: float) -> TBool: + # ------------------------- Position Operations --------------------------- + def is_left(self, other: Union[Temporal, Box]) -> bool: """ - Returns a new temporal boolean indicating whether the trajectory is within `distance` of `other`. + Returns whether the bounding box of `self` is left to the bounding box of `other`. Args: - other: An object to check the distance to. - distance: The distance to check in units of the spatial reference system. + other: A box or a temporal object to compare to `self`. Returns: - A new :TBool: indicating whether the trajectory is within `distance` of `other`. + True if left, False otherwise. - MEOS Functions: - tdwithin_tpoint_geo, tdwithin_tpoint_tpoint + See Also: + :meth:`Period.is_before` """ - if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other) - result = tdwithin_tpoint_geo(self._inner, gs, distance, False, False) - elif isinstance(other, TPoint): - result = tdwithin_tpoint_tpoint(self._inner, other._inner, distance, False, False) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - return Temporal._factory(result) + return self.bounding_box().is_left(other) - def intersects(self, other: pg.Geometry) -> TBool: + def is_over_or_left(self, other: Union[Temporal, Box]) -> bool: """ - Returns a new temporal boolean indicating whether the trajectory intersects `other`. + Returns whether the bounding box of `self` is over or left to the bounding box of `other`. Args: - other: An object to check for intersection with. + other: A box or a temporal object to compare to `self`. Returns: - A new :TBool: indicating whether the trajectory intersects `other`. + True if over or left, False otherwise. - MEOS Functions: - tintersects_tpoint_geo + See Also: + :meth:`Period.is_over_or_before` """ - gs = gserialized_in(other.to_ewkb(), -1) - result = tintersects_tpoint_geo(self._inner, gs, False, False) - return Temporal._factory(result) + return self.bounding_box().is_over_or_left(other) - def touches(self, other: pg.Geometry) -> TBool: + def is_right(self, other: Union[Temporal, Box]) -> bool: """ - Returns a new temporal boolean indicating whether the trajectory touches `other`. + Returns whether the bounding box of `self` is right to the bounding box of `other`. Args: - other: An object to check for touching with. + other: A box or a temporal object to compare to `self`. Returns: - A new :TBool: indicating whether the trajectory touches `other`. + True if right, False otherwise. - MEOS Functions: - ttouches_tpoint_geo + See Also: + :meth:`Period.is_after` """ - gs = gserialized_in(other.to_ewkb(), -1) - result = ttouches_tpoint_geo(self._inner, gs, False, False) - return Temporal._factory(result) + return self.bounding_box().is_right(other) - def is_contained(self, container: pg.Geometry) -> TBool: + def is_over_or_right(self, other: Union[Temporal, Box]) -> bool: """ - Returns a new temporal boolean indicating whether the trajectory is contained by `container`. + Returns whether the bounding box of `self` is over or right to the bounding box of `other`. Args: - container: An object to check for containing `self`. + other: A box or a temporal object to compare to `self`. Returns: - A new :TBool: indicating whether the trajectory is contained by `container`. + True if over or right, False otherwise. - MEOS Functions: - tcontains_geo_tpoint + See Also: + :meth:`Period.is_over_or_before` """ - gs = gserialized_in(container.to_ewkb(), -1) - result = tcontains_geo_tpoint(gs, self._inner, False, False) - return Temporal._factory(result) + return self.bounding_box().is_over_or_right(other) - def disjoint(self, other: pg.Geometry) -> TBool: + def is_below(self, other: Union[Temporal, Box]) -> bool: """ - Returns a new temporal boolean indicating whether the trajectory is disjoint from `other`. + Returns whether the bounding box of `self` is below to the bounding box of `other`. Args: - other: An object to check for disjointness with. + other: A box or a temporal object to compare to `self`. + + Returns: + True if below, False otherwise. + + See Also: + :meth:`Period.is_before` + """ + return self.bounding_box().is_below(other) + + def is_over_or_below(self, other: Union[Temporal, Box]) -> bool: + """ + Returns whether the bounding box of `self` is over or below to the bounding box of `other`. + + Args: + other: A box or a temporal object to compare to `self`. + + Returns: + True if over or below, False otherwise. + + See Also: + :meth:`Period.is_over_or_before` + """ + return self.bounding_box().is_over_or_below(other) + + def is_above(self, other: Union[Temporal, Box]) -> bool: + """ + Returns whether the bounding box of `self` is above to the bounding box of `other`. + + Args: + other: A box or a temporal object to compare to `self`. + + Returns: + True if above, False otherwise. + + See Also: + :meth:`Period.is_after` + """ + return self.bounding_box().is_above(other) + + def is_over_or_above(self, other: Union[Temporal, Box]) -> bool: + """ + Returns whether the bounding box of `self` is over or above to the bounding box of `other`. + + Args: + other: A box or a temporal object to compare to `self`. + + Returns: + True if over or above, False otherwise. + + See Also: + :meth:`Period.is_over_or_before` + """ + return self.bounding_box().is_over_or_above(other) + + def is_front(self, other: Union[Temporal, Box]) -> bool: + """ + Returns whether the bounding box of `self` is front to the bounding box of `other`. + + Args: + other: A box or a temporal object to compare to `self`. + + Returns: + True if front, False otherwise. + + See Also: + :meth:`Period.is_before` + """ + return self.bounding_box().is_front(other) + + def is_over_or_front(self, other: Union[Temporal, Box]) -> bool: + """ + Returns whether the bounding box of `self` is over or front to the bounding box of `other`. + + Args: + other: A box or a temporal object to compare to `self`. + + Returns: + True if over or front, False otherwise. + + See Also: + :meth:`Period.is_over_or_before` + """ + return self.bounding_box().is_over_or_front(other) + + def is_behind(self, other: Union[Temporal, Box]) -> bool: + """ + Returns whether the bounding box of `self` is behind to the bounding box of `other`. + + Args: + other: A box or a temporal object to compare to `self`. + + Returns: + True if behind, False otherwise. + + See Also: + :meth:`Period.is_after` + """ + return self.bounding_box().is_behind(other) + + def is_over_or_behind(self, other: Union[Temporal, Box]) -> bool: + """ + Returns whether the bounding box of `self` is over or behind to the bounding box of `other`. + + Args: + other: A box or a temporal object to compare to `self`. Returns: - A new :TBool: indicating whether the trajectory is disjoint from `other`. + True if over or behind, False otherwise. - MEOS Functions: - tdisjoint_tpoint_geo + See Also: + :meth:`Period.is_over_or_before` """ - gs = gserialized_in(other.to_ewkb(), -1) - result = tdisjoint_tpoint_geo(self._inner, gs, False, False) - return Temporal._factory(result) + return self.bounding_box().is_over_or_behind(other) - def is_ever_contained(self, container: pg.Geometry) -> bool: + # ------------------------- Ever Spatial Relationships -------------------- + def is_ever_contained_in(self, container: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> bool: """ - Returns whether the trajectory is ever contained by `container`. + Returns whether the temporal point is ever contained by `container`. Args: container: An object to check for containing `self`. Returns: - A :class:`bool` indicating whether the trajectory is ever contained by `container`. + A :class:`bool` indicating whether the temporal point is ever contained by `container`. MEOS Functions: econtains_geo_tpoint """ - gs = gserialized_in(container.to_ewkb(), -1) - return econtains_geo_tpoint(gs, self._inner) == 1 + from ..boxes import STBox + if isinstance(container, pg.Geometry) or isinstance(container, shpb.BaseGeometry): + gs = geometry_to_gserialized(container, isinstance(self, TGeogPoint)) + result = econtains_geo_tpoint(gs, self._inner) + elif isinstance(container, STBox): + result = econtains_geo_tpoint(stbox_to_geo(container._inner), self._inner) + else: + raise TypeError(f'Operation not supported with type {container.__class__}') + return result == 1 - def is_ever_disjoint(self, other: Union[pg.Geometry, TPoint]) -> bool: + def is_ever_disjoint(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, STBox]) -> bool: """ - Returns whether the trajectory is ever disjoint from `other`. + Returns whether the temporal point is ever disjoint from `other`. Args: other: An object to check for disjointness with. Returns: - A :class:`bool` indicating whether the trajectory is ever disjoint from `other`. + A :class:`bool` indicating whether the temporal point is ever disjoint from `other`. MEOS Functions: edisjoint_tpoint_geo, edisjoint_tpoint_tpoint """ - if isinstance(other, pg.Geometry): - gs = gserialized_in(other.to_ewkb(), -1) + from ..boxes import STBox + if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) result = edisjoint_tpoint_geo(self._inner, gs) + elif isinstance(other, STBox): + result = edisjoint_tpoint_geo(self._inner, stbox_to_geo(other._inner)) elif isinstance(other, TPoint): result = edisjoint_tpoint_tpoint(self._inner, other._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') return result == 1 - def is_ever_within_distance(self, other: Union[pg.Geometry, TPoint], distance: float) -> bool: + def is_ever_within_distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, STBox], + distance: float) -> bool: """ - Returns whether the trajectory is ever within `distance` of `other`. + Returns whether the temporal point is ever within `distance` of `other`. Args: other: An object to check the distance to. distance: The distance to check in units of the spatial reference system. Returns: - A :class:`bool` indicating whether the trajectory is ever within `distance` of `other`. + A :class:`bool` indicating whether the temporal point is ever within `distance` of `other`. MEOS Functions: edwithin_tpoint_geo, edwithin_tpoint_tpoint """ - if isinstance(other, pg.Geometry): - gs = gserialized_in(other.to_ewkb(), -1) + from ..boxes import STBox + if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) result = edwithin_tpoint_geo(self._inner, gs, distance) + elif isinstance(other, STBox): + result = edwithin_tpoint_geo(self._inner, stbox_to_geo(other._inner), distance) elif isinstance(other, TPoint): result = edwithin_tpoint_tpoint(self._inner, other._inner, distance) else: raise TypeError(f'Operation not supported with type {other.__class__}') return result == 1 - def ever_intersects(self, other: Union[pg.Geometry, TPoint]) -> bool: + def ever_intersects(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, STBox]) -> bool: """ - Returns whether the trajectory ever intersects `other`. + Returns whether the temporal point ever intersects `other`. Args: other: An object to check for intersection with. Returns: - A :class:`bool` indicating whether the trajectory ever intersects `other`. + A :class:`bool` indicating whether the temporal point ever intersects `other`. MEOS Functions: eintersects_tpoint_geo, eintersects_tpoint_tpoint """ - if isinstance(other, pg.Geometry): - gs = gserialized_in(other.to_ewkb(), -1) + from ..boxes import STBox + if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) result = eintersects_tpoint_geo(self._inner, gs) + elif isinstance(other, STBox): + result = eintersects_tpoint_geo(self._inner, stbox_to_geo(other._inner)) elif isinstance(other, TPoint): result = eintersects_tpoint_tpoint(self._inner, other._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') return result == 1 - def ever_touches(self, other: pg.Geometry) -> bool: + def ever_touches(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> bool: """ - Returns whether the trajectory ever touches `other`. + Returns whether the temporal point ever touches `other`. Args: other: An object to check for touching with. Returns: - A :class:`bool` indicating whether the trajectory ever touches `other`. + A :class:`bool` indicating whether the temporal point ever touches `other`. MEOS Functions: etouches_tpoint_geo """ - gs = gserialized_in(other.to_ewkb(), -1) - return etouches_tpoint_geo(gs, self._inner) == 1 + from ..boxes import STBox + if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + result = etouches_tpoint_geo(self._inner, gs) + elif isinstance(other, STBox): + result = etouches_tpoint_geo(self._inner, stbox_to_geo(other._inner)) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + return result == 1 + + # ------------------------- Temporal Spatial Relationships ---------------- + def is_spatially_contained_in(self, container: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBool: + """ + Returns a new temporal boolean indicating whether the temporal point is contained by `container`. + + Args: + container: An object to check for containing `self`. + + Returns: + A new :TBool: indicating whether the temporal point is contained by `container`. + + MEOS Functions: + tcontains_geo_tpoint + """ + from ..boxes import STBox + if isinstance(container, pg.Geometry) or isinstance(container, shpb.BaseGeometry): + gs = geometry_to_gserialized(container, isinstance(self, TGeogPoint)) + result = tcontains_geo_tpoint(gs, self._inner, False, False) + elif isinstance(container, STBox): + gs = stbox_to_geo(container._inner) + result = tcontains_geo_tpoint(gs, self._inner, False, False) + else: + raise TypeError(f'Operation not supported with type {container.__class__}') + return Temporal._factory(result) + + def disjoint(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBool: + """ + Returns a new temporal boolean indicating whether the temporal point intersects `other`. + + Args: + other: An object to check for intersection with. + + Returns: + A new :TBool: indicating whether the temporal point intersects `other`. + + MEOS Functions: + tintersects_tpoint_geo + """ + from ..boxes import STBox + if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + result = tdisjoint_tpoint_geo(self._inner, gs, False, False) + elif isinstance(other, STBox): + result = tdisjoint_tpoint_geo(self._inner, stbox_to_geo(other._inner), False, False) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + return Temporal._factory(result) - def distance(self, other: Union[pg.Geometry, TPoint]) -> TFloat: + def within_distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, STBox], distance: float) -> TBool: """ - Returns the temporal distance between the trajectory and `other`. + Returns a new temporal boolean indicating whether the temporal point is within `distance` of `other`. Args: other: An object to check the distance to. + distance: The distance to check in units of the spatial reference system. Returns: - A new :class:`TFloat` indicating the temporal distance between the trajectory and `other`. + A new :TBool: indicating whether the temporal point is within `distance` of `other`. MEOS Functions: - distance_tpoint_geo, distance_tpoint_tpoint + tdwithin_tpoint_geo, tdwithin_tpoint_tpoint """ - if isinstance(other, pg.Geometry): - gs = gserialized_in(other.to_ewkb(), -1) - result = distance_tpoint_geo(self._inner, gs) + from ..boxes import STBox + if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + result = tdwithin_tpoint_geo(self._inner, gs, distance, False, False) + elif isinstance(other, STBox): + result = tdwithin_tpoint_geo(self._inner, stbox_to_geo(other._inner), distance, False, False) elif isinstance(other, TPoint): - result = distance_tpoint_tpoint(self._inner, other._inner) + result = tdwithin_tpoint_tpoint(self._inner, other._inner, distance, False, False) else: raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) - def nearest_approach_distance(self, other: Union[pg.Geometry, STBox, TPoint]) -> float: + def intersects(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBool: """ - Returns the nearest approach distance between the trajectory and `other`. + Returns a new temporal boolean indicating whether the temporal point intersects `other`. Args: - other: An object to check the nearest approach distance to. + other: An object to check for intersection with. Returns: - A :class:`float` indicating the nearest approach distance between the trajectory and `other`. + A new :TBool: indicating whether the temporal point intersects `other`. MEOS Functions: - nad_tpoint_geo, nad_tpoint_stbox, nad_tpoint_tpoint + tintersects_tpoint_geo """ from ..boxes import STBox - if isinstance(other, pg.Geometry): - gs = gserialized_in(other.to_ewkb(), -1) - return nad_tpoint_geo(self._inner, gs) + if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + result = tintersects_tpoint_geo(self._inner, gs, False, False) elif isinstance(other, STBox): - return nad_tpoint_stbox(self._inner, other._inner) - elif isinstance(other, TPoint): - return nad_tpoint_tpoint(self._inner, other._inner) + result = tintersects_tpoint_geo(self._inner, stbox_to_geo(other._inner), False, False) else: raise TypeError(f'Operation not supported with type {other.__class__}') + return Temporal._factory(result) - def nearest_approach_instant(self, other: Union[pg.Geometry, TPoint]) -> TI: + def touches(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox]) -> TBool: """ - Returns the nearest approach instant between the trajectory and `other`. + Returns a new temporal boolean indicating whether the temporal point touches `other`. Args: - other: An object to check the nearest approach instant to. + other: An object to check for touching with. Returns: - A new temporal instant indicating the nearest approach instant between the trajectory and `other`. + A new :TBool: indicating whether the temporal point touches `other`. MEOS Functions: - nai_tpoint_geo, nai_tpoint_tpoint + ttouches_tpoint_geo """ - if isinstance(other, pg.Geometry): - gs = gserialized_in(other.to_ewkb(), -1) - result = nai_tpoint_geo(self._inner, gs) - elif isinstance(other, TPoint): - result = nai_tpoint_tpoint(self._inner, other._inner) + from ..boxes import STBox + if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + result = ttouches_tpoint_geo(self._inner, gs, False, False) + elif isinstance(other, STBox): + result = ttouches_tpoint_geo(self._inner, stbox_to_geo(other._inner), False, False) else: raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) - def shortest_line(self, other: Union[pg.Geometry, TPoint]) -> shpb.BaseGeometry: + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint, STBox]) -> TFloat: """ - Returns the shortest line between the trajectory and `other`. + Returns the temporal distance between the temporal point and `other`. Args: - other: An object to check the shortest line to. + other: An object to check the distance to. Returns: - A new :class:`~shapely.geometry.base.BaseGeometry` indicating the shortest line between the trajectory - and `other`. + A new :class:`TFloat` indicating the temporal distance between the temporal point and `other`. MEOS Functions: - shortestline_tpoint_geo, shortestline_tpoint_tpoint + distance_tpoint_geo, distance_tpoint_tpoint """ - if isinstance(other, pg.Geometry): - gs = gserialized_in(other.to_ewkb(), -1) - result = shortestline_tpoint_geo(self._inner, gs) + from ..boxes import STBox + if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + result = distance_tpoint_geo(self._inner, gs) + elif isinstance(other, STBox): + result = distance_tpoint_geo(self._inner, stbox_to_geo(other._inner)) elif isinstance(other, TPoint): - result = shortestline_tpoint_tpoint(self._inner, other._inner) + result = distance_tpoint_tpoint(self._inner, other._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - return gserialized_to_shapely_geometry(result[0], 10) + return Temporal._factory(result) - def bearing(self, other: Union[pg.Geometry, TPoint]) -> TFloat: + def nearest_approach_distance(self, other: Union[pg.Geometry, shpb.BaseGeometry, STBox, TPoint]) -> float: """ - Returns the temporal bearing between the trajectory and `other`. + Returns the nearest approach distance between the temporal point and `other`. Args: - other: An object to check the bearing to. + other: An object to check the nearest approach distance to. Returns: - A new :class:`TFloat` indicating the temporal bearing between the trajectory and `other`. + A :class:`float` indicating the nearest approach distance between the temporal point and `other`. MEOS Functions: - bearing_tpoint_point, bearing_tpoint_tpoint + nad_tpoint_geo, nad_tpoint_stbox, nad_tpoint_tpoint """ - if isinstance(other, pg.Geometry): - gs = gserialized_in(other.to_ewkb(), -1) - result = bearing_tpoint_point(self._inner, gs, False) + from ..boxes import STBox + if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + return nad_tpoint_geo(self._inner, gs) + elif isinstance(other, STBox): + return nad_tpoint_stbox(self._inner, other._inner) elif isinstance(other, TPoint): - result = bearing_tpoint_tpoint(self._inner, other._inner) + return nad_tpoint_tpoint(self._inner, other._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - return Temporal._factory(result) - def azimuth(self) -> TFloatSeqSet: + def nearest_approach_instant(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint]) -> TI: """ - Returns the temporal azimuth of the trajectory. + Returns the nearest approach instant between the temporal point and `other`. + + Args: + other: An object to check the nearest approach instant to. Returns: - A new :class:`TFloatSeqSet` indicating the temporal azimuth of the trajectory. + A new temporal instant indicating the nearest approach instant between the temporal point and `other`. MEOS Functions: - azimuth_tpoint + nai_tpoint_geo, nai_tpoint_tpoint """ - result = tpoint_azimuth(self._inner) + if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + result = nai_tpoint_geo(self._inner, gs) + elif isinstance(other, TPoint): + result = nai_tpoint_tpoint(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) - def time_weighted_centroid(self, precision: int = 15) -> shp.Point: + def shortest_line(self, other: Union[pg.Geometry, shpb.BaseGeometry, TPoint]) -> shpb.BaseGeometry: """ - Returns the time weighted centroid of the trajectory. + Returns the shortest line between the temporal point and `other`. Args: - precision: The precision of the returned geometry. + other: An object to check the shortest line to. Returns: - A new :class:`~shapely.geometry.base.BaseGeometry` indicating the time weighted centroid of the trajectory. + A new :class:`~shapely.geometry.base.BaseGeometry` indicating the shortest line between the temporal point + and `other`. MEOS Functions: - tpoint_twcentroid + shortestline_tpoint_geo, shortestline_tpoint_tpoint """ - return gserialized_to_shapely_geometry(tpoint_twcentroid(self._inner), precision) # type: ignore + if isinstance(other, pg.Geometry) or isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) + result = shortestline_tpoint_geo(self._inner, gs) + elif isinstance(other, TPoint): + result = shortestline_tpoint_tpoint(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + return gserialized_to_shapely_geometry(result[0], 10) + # ------------------------- Tiling Operations ----------------------------- def tile(self, size: float, duration: Optional[Union[timedelta, str]] = None, origin: Optional[Union[shpb.BaseGeometry, pg.Geometry]] = None, start: Union[datetime, str, None] = None) -> List[List[List[List[TG]]]]: """ - Split the trajectory into segments following the tiling of the bounding box. + Split the temporal point into segments following the tiling of the bounding box. Args: size: The size of the spatial tiles. If `self` has a spatial dimension and this @@ -750,7 +1038,7 @@ def tile_flat(self, size: float, duration: Optional[Union[timedelta, str]] = Non origin: Optional[Union[shpb.BaseGeometry, pg.Geometry]] = None, start: Union[datetime, str, None] = None) -> List[TG]: """ - Split the trajectory into segments following the tiling of the bounding box. + Split the temporal point into segments following the tiling of the bounding box. Args: size: The size of the spatial tiles. If `self` has a spatial dimension and this @@ -772,93 +1060,6 @@ def tile_flat(self, size: float, duration: Optional[Union[timedelta, str]] = Non tiles = bbox.tile_flat(size, duration, origin, start) return [x for x in (self.at(tile) for tile in tiles) if x] - def as_geojson(self, option: int = 1, precision: int = 15, srs: Optional[str] = None) -> str: - """ - Returns the trajectory as a GeoJSON string. - - Args: - option: The option to use when serializing the trajectory. - precision: The precision of the returned geometry. - srs: The spatial reference system of the returned geometry. - - Returns: - A new GeoJSON string representing the trajectory. - - MEOS Functions: - gserialized_as_geojson - """ - return gserialized_as_geojson(tpoint_trajectory(self._inner), option, precision, srs) - - def to_shapely_geometry(self, precision: int = 15) -> shpb.BaseGeometry: - """ - Returns the trajectory as a Shapely geometry. - - Args: - precision: The precision of the returned geometry. - - Returns: - A new :class:`~shapely.geometry.base.BaseGeometry` representing the trajectory. - - MEOS Functions: - gserialized_to_shapely_geometry - """ - return gserialized_to_shapely_geometry(tpoint_trajectory(self._inner), precision) - - def to_dataframe(self) -> GeoDataFrame: - """ - Returns the trajectory as a GeoPandas DataFrame. - - Returns: - A new :class:`GeoDataFrame` representing the trajectory. - """ - data = { - 'time': self.timestamps(), - 'geometry': [i.value() for i in self.instants()] - } - return GeoDataFrame(data, crs=self.srid()).set_index(keys=['time']) - - def __str__(self): - """ - Returns the string representation of the trajectory. - - Returns: - A new :class:`str` representing the trajectory. - - MEOS Functions: - tpoint_out - """ - return tpoint_out(self._inner, 15) - - def as_wkt(self, precision: int = 15) -> str: - """ - Returns the trajectory as a WKT string. - - Args: - precision: The precision of the returned geometry. - - Returns: - A new :class:`str` representing the trajectory. - - MEOS Functions: - tpoint_out - """ - return tpoint_out(self._inner, precision) - - def as_ewkt(self, precision: int = 15) -> str: - """ - Returns the trajectory as an EWKT string. - - Args: - precision: The precision of the returned geometry. - - Returns: - A new :class:`str` representing the trajectory. - - MEOS Functions: - tpoint_as_ewkt - """ - return tpoint_as_ewkt(self._inner, precision) - class TPointInst(TInstant[shpb.BaseGeometry, TG, TI, TS, TSS], TPoint[TG, TI, TS, TSS], ABC): """ @@ -979,12 +1180,12 @@ class TGeomPoint(TPoint['TGeomPoint', 'TGeomPointInst', 'TGeomPointSeq', 'TGeomP """ Abstract class for temporal geometric points. """ - BaseClass = pg.Point + BaseClass = shp.Point _parse_function = tgeompoint_in + # ------------------------- Output ---------------------------------------- @staticmethod - def from_base(value: pg.Geometry, base: Temporal, - interpolation: TInterpolation = TInterpolation.LINEAR) -> TGeomPoint: + def from_base_temporal(value: Union[pg.Geometry, shpb.BaseGeometry], base: Temporal) -> TGeomPoint: """ Creates a temporal geometric point from a base geometry and the time frame of another temporal object. @@ -997,14 +1198,30 @@ def from_base(value: pg.Geometry, base: Temporal, A new :class:`TGeomPoint` object. MEOS Functions: - tgeompoint_from_base + tgeompoint_from_base_temp """ - gs = gserialized_in(value.to_ewkb(), -1) - result = tgeompoint_from_base(gs, base._inner, interpolation) + gs = geometry_to_gserialized(value, False) + result = tgeompoint_from_base_temp(gs, base._inner) return Temporal._factory(result) @staticmethod - def from_base_time(value: pg.Geometry, base: Time, interpolation: TInterpolation = None) -> TGeomPoint: + @overload + def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: datetime) -> TGeomPointInst: + ... + + @staticmethod + @overload + def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: Union[TimestampSet, Period]) -> TGeomPointSeq: + ... + + @staticmethod + @overload + def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: PeriodSet) -> TGeomPointSeqSet: + ... + + @staticmethod + def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: Time, + interpolation: TInterpolation = None) -> TGeomPoint: """ Creates a temporal geometric point from a base geometry and a time value. @@ -1017,20 +1234,21 @@ def from_base_time(value: pg.Geometry, base: Time, interpolation: TInterpolation A new :class:`TGeomPoint` object. MEOS Functions: - tgeompointinst_make, tgeompointdiscseq_from_base_time, tgeompointseq_from_base_time, - tgeompointseqset_from_base_time + tgeompointinst_make, tgeompointseq_from_base_timestampset, + tgeompointseq_from_base_period, tgeompointseqset_from_base_periodset """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, False) if isinstance(base, datetime): return TGeomPointInst(_inner=tgeompointinst_make(gs, datetime_to_timestamptz(base))) elif isinstance(base, TimestampSet): - return TGeomPointSeq(_inner=tgeompointdiscseq_from_base_time(gs, base._inner)) + return TGeomPointSeq(_inner=tgeompointseq_from_base_timestampset(gs, base._inner)) elif isinstance(base, Period): - return TGeomPointSeq(_inner=tgeompointseq_from_base_time(gs, base._inner, interpolation)) + return TGeomPointSeq(_inner=tgeompointseq_from_base_period(gs, base._inner, interpolation)) elif isinstance(base, PeriodSet): - return TGeomPointSeqSet(_inner=tgeompointseqset_from_base_time(gs, base._inner, interpolation)) + return TGeomPointSeqSet(_inner=tgeompointseqset_from_base_periodset(gs, base._inner, interpolation)) raise TypeError(f'Operation not supported with type {base.__class__}') + # ------------------------- Conversions ---------------------------------- def to_geographic(self) -> TGeogPoint: """ Returns a copy of `self` converted to geographic coordinates. @@ -1044,7 +1262,36 @@ def to_geographic(self) -> TGeogPoint: result = tgeompoint_tgeogpoint(self._inner, True) return Temporal._factory(result) - def always_equal(self, value: pg.Geometry) -> bool: + def to_shapely_geometry(self, precision: int = 15) -> shpb.BaseGeometry: + """ + Returns the trajectory of the temporal point as a Shapely geometry. + + Args: + precision: The precision of the returned geometry. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` representing the trajectory. + + MEOS Functions: + gserialized_to_shapely_geometry + """ + return gserialized_to_shapely_geometry(tpoint_trajectory(self._inner), precision) + + def to_dataframe(self) -> GeoDataFrame: + """ + Returns the trajectory of the temporal point as a GeoPandas DataFrame. + + Returns: + A new :class:`GeoDataFrame` representing the trajectory. + """ + data = { + 'time': self.timestamps(), + 'geometry': [i.value() for i in self.instants()] + } + return GeoDataFrame(data, crs=self.srid()).set_index(keys=['time']) + + # ------------------------- Ever and Always Comparisons ------------------- + def always_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: """ Returns whether `self` is always equal to `value`. @@ -1057,10 +1304,10 @@ def always_equal(self, value: pg.Geometry) -> bool: MEOS Functions: tgeompoint_always_eq """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) return tgeompoint_always_eq(self._inner, gs) - def always_not_equal(self, value: pg.Geometry) -> bool: + def always_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: """ Returns whether `self` is always different to `value`. @@ -1073,10 +1320,10 @@ def always_not_equal(self, value: pg.Geometry) -> bool: MEOS Functions: tgeompoint_ever_eq """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) return not tgeompoint_ever_eq(self._inner, gs) - def ever_equal(self, value: pg.Geometry) -> bool: + def ever_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: """ Returns whether `self` is ever equal to `value`. @@ -1089,10 +1336,10 @@ def ever_equal(self, value: pg.Geometry) -> bool: MEOS Functions: tgeompoint_ever_eq """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) return tgeompoint_ever_eq(self._inner, gs) - def ever_not_equal(self, value: pg.Geometry) -> bool: + def ever_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: """ Returns whether `self` is ever different to `value`. @@ -1105,10 +1352,10 @@ def ever_not_equal(self, value: pg.Geometry) -> bool: MEOS Functions: tgeompoint_always_eq """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) return not tgeompoint_always_eq(self._inner, gs) - def never_equal(self, value: pg.Geometry) -> bool: + def never_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: """ Returns whether `self` is never equal to `value`. @@ -1121,10 +1368,10 @@ def never_equal(self, value: pg.Geometry) -> bool: MEOS Functions: tgeompoint_ever_eq """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) return not tgeompoint_ever_eq(self._inner, gs) - def never_not_equal(self, value: pg.Geometry) -> bool: + def never_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: """ Returns whether `self` is never different to `value`. @@ -1137,10 +1384,11 @@ def never_not_equal(self, value: pg.Geometry) -> bool: MEOS Functions: tgeompoint_always_eq """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) return tgeompoint_always_eq(self._inner, gs) - def temporal_equal(self, other: Union[pg.Point, Temporal]) -> TBool: + # ------------------------- Temporal Comparisons -------------------------- + def temporal_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> TBool: """ Returns the temporal equality relation between `self` and `other`. @@ -1153,14 +1401,14 @@ def temporal_equal(self, other: Union[pg.Point, Temporal]) -> TBool: MEOS Functions: teq_tgeompoint_point, teq_temporal_temporal """ - if isinstance(other, pg.Point): - gs = gserialized_in(other.to_ewkb(), -1) + if isinstance(other, pg.Point) or isinstance(other, shp.Point): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) result = teq_tgeompoint_point(self._inner, gs) else: return super().temporal_equal(other) return Temporal._factory(result) - def temporal_not_equal(self, other: Union[pg.Point, Temporal]) -> Temporal: + def temporal_not_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> Temporal: """ Returns the temporal inequality relation between `self` and `other`. @@ -1173,17 +1421,18 @@ def temporal_not_equal(self, other: Union[pg.Point, Temporal]) -> Temporal: MEOS Functions: tne_tgeompoint_point, tne_temporal_temporal """ - if isinstance(other, pg.Point): - gs = gserialized_in(other.to_ewkb(), -1) + if isinstance(other, pg.Point) or isinstance(other, shp.Point): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) result = tne_tgeompoint_point(self._inner, gs) else: return super().temporal_not_equal(other) return Temporal._factory(result) + # ------------------------- Database Operations --------------------------- @staticmethod def read_from_cursor(value, _=None): """ - Reads a :class:`TGeomPoint` from a database cursor. Used when automatically loading objects from the database. + Reads a :class:`TGeogPoint` from a database cursor. Used when automatically loading objects from the database. Users should use the class constructor instead. """ if not value: @@ -1210,12 +1459,12 @@ class TGeogPoint(TPoint['TGeogPoint', 'TGeogPointInst', 'TGeogPointSeq', 'TGeogP """ Abstract class for representing temporal geographic points. """ - BaseClass = pg.Point + BaseClass = shp.Point _parse_function = tgeogpoint_in + # ------------------------- Output ---------------------------------------- @staticmethod - def from_base(value: pg.Geometry, base: Temporal, - interpolation: TInterpolation = TInterpolation.LINEAR) -> TGeogPoint: + def from_base_temporal(value: Union[pg.Geometry, shpb.BaseGeometry], base: Temporal) -> TGeogPoint: """ Creates a temporal geographic point from a base geometry and the time frame of another temporal object. @@ -1228,14 +1477,30 @@ def from_base(value: pg.Geometry, base: Temporal, A new :class:`TGeogPoint` object. MEOS Functions: - tgeogpoint_from_base + tgeogpoint_from_base_temp """ - gs = gserialized_in(value.to_ewkb(), -1) - result = tgeogpoint_from_base(gs, base._inner, interpolation) + gs = geometry_to_gserialized(value, True) + result = tgeogpoint_from_base_temp(gs, base._inner) return Temporal._factory(result) @staticmethod - def from_base_time(value: pg.Geometry, base: Time, interpolation: TInterpolation = None) -> TGeogPoint: + @overload + def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: datetime) -> TGeogPointInst: + ... + + @staticmethod + @overload + def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: Union[TimestampSet, Period]) -> TGeogPointSeq: + ... + + @staticmethod + @overload + def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: PeriodSet) -> TGeogPointSeqSet: + ... + + @staticmethod + def from_base_time(value: Union[pg.Geometry, shpb.BaseGeometry], base: Time, + interpolation: TInterpolation = None) -> TGeogPoint: """ Creates a temporal geographic point from a base geometry and a time object. @@ -1248,20 +1513,21 @@ def from_base_time(value: pg.Geometry, base: Time, interpolation: TInterpolation A new :class:`TGeogPoint` object. MEOS Functions: - tgeogpointinst_make, tgeogpointdiscseq_from_base_time, tgeogpointseq_from_base_time, - tgeogpointseqset_from_base_time + tgeogpointinst_make, tgeogpointseq_from_base_timestampset, + tgeogpointseq_from_base_period, tgeogpointseqset_from_base_periodset """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, True) if isinstance(base, datetime): return TGeogPointInst(_inner=tgeogpointinst_make(gs, datetime_to_timestamptz(base))) elif isinstance(base, TimestampSet): - return TGeogPointSeq(_inner=tgeogpointdiscseq_from_base_time(gs, base._inner)) + return TGeogPointSeq(_inner=tgeogpointseq_from_base_timestampset(gs, base._inner)) elif isinstance(base, Period): - return TGeogPointSeq(_inner=tgeogpointseq_from_base_time(gs, base._inner, interpolation)) + return TGeogPointSeq(_inner=tgeogpointseq_from_base_period(gs, base._inner, interpolation)) elif isinstance(base, PeriodSet): - return TGeogPointSeqSet(_inner=tgeogpointseqset_from_base_time(gs, base._inner, interpolation)) + return TGeogPointSeqSet(_inner=tgeogpointseqset_from_base_periodset(gs, base._inner, interpolation)) raise TypeError(f'Operation not supported with type {base.__class__}') + # ------------------------- Conversions ---------------------------------- def to_geometric(self) -> TGeomPoint: """ Converts the temporal geographic point to a temporal geometric point. @@ -1275,7 +1541,8 @@ def to_geometric(self) -> TGeomPoint: result = tgeompoint_tgeogpoint(self._inner, False) return Temporal._factory(result) - def always_equal(self, value: pg.Geometry) -> bool: + # ------------------------- Ever and Always Comparisons ------------------- + def always_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: """ Returns whether `self` is always equal to `value`. @@ -1288,10 +1555,10 @@ def always_equal(self, value: pg.Geometry) -> bool: MEOS Functions: tgeogpoint_always_eq """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) return tgeogpoint_always_eq(self._inner, gs) - def always_not_equal(self, value: pg.Geometry) -> bool: + def always_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: """ Returns whether `self` is always different to `value`. @@ -1304,10 +1571,10 @@ def always_not_equal(self, value: pg.Geometry) -> bool: MEOS Functions: tgeogpoint_ever_eq """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) return not tgeogpoint_ever_eq(self._inner, gs) - def ever_equal(self, value: pg.Geometry) -> bool: + def ever_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: """ Returns whether `self` is ever equal to `value`. @@ -1320,10 +1587,10 @@ def ever_equal(self, value: pg.Geometry) -> bool: MEOS Functions: tgeogpoint_ever_eq """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) return tgeogpoint_ever_eq(self._inner, gs) - def ever_not_equal(self, value: pg.Geometry) -> bool: + def ever_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: """ Returns whether `self` is ever different to `value`. @@ -1336,10 +1603,10 @@ def ever_not_equal(self, value: pg.Geometry) -> bool: MEOS Functions: tgeogpoint_always_eq """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) return not tgeogpoint_always_eq(self._inner, gs) - def never_equal(self, value: pg.Geometry) -> bool: + def never_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: """ Returns whether `self` is never equal to `value`. @@ -1352,10 +1619,10 @@ def never_equal(self, value: pg.Geometry) -> bool: MEOS Functions: tgeogpoint_ever_eq """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) return not tgeogpoint_ever_eq(self._inner, gs) - def never_not_equal(self, value: pg.Geometry) -> bool: + def never_not_equal(self, value: Union[pg.Geometry, shpb.BaseGeometry]) -> bool: """ Returns whether `self` is never different to `value`. @@ -1368,10 +1635,11 @@ def never_not_equal(self, value: pg.Geometry) -> bool: MEOS Functions: tgeogpoint_always_eq """ - gs = gserialized_in(value.to_ewkb(), -1) + gs = geometry_to_gserialized(value, isinstance(self, TGeogPoint)) return tgeogpoint_always_eq(self._inner, gs) - def temporal_equal(self, other: Union[pg.Point, Temporal]) -> TBool: + # ------------------------- Temporal Comparisons -------------------------- + def temporal_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> TBool: """ Returns the temporal equality relation between `self` and `other`. @@ -1384,14 +1652,14 @@ def temporal_equal(self, other: Union[pg.Point, Temporal]) -> TBool: MEOS Functions: teq_tgeogpoint_point, teq_temporal_temporal """ - if isinstance(other, pg.Point): - gs = gserialized_in(other.to_ewkb(), -1) + if isinstance(other, pg.Point) or isinstance(other, shp.Point): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) result = teq_tgeogpoint_point(self._inner, gs) else: return super().temporal_equal(other) return Temporal._factory(result) - def temporal_not_equal(self, other: Union[pg.Point, Temporal]) -> TBool: + def temporal_not_equal(self, other: Union[pg.Point, shp.Point, Temporal]) -> TBool: """ Returns the temporal inequality relation between `self` and `other`. @@ -1404,13 +1672,14 @@ def temporal_not_equal(self, other: Union[pg.Point, Temporal]) -> TBool: MEOS Functions: tne_tgeogpoint_point, tne_temporal_temporal """ - if isinstance(other, pg.Point): - gs = gserialized_in(other.to_ewkb(), -1) + if isinstance(other, pg.Point) or isinstance(other, shp.Point): + gs = geometry_to_gserialized(other, isinstance(self, TGeogPoint)) result = tne_tgeogpoint_point(self._inner, gs) else: return super().temporal_not_equal(other) return Temporal._factory(result) + # ------------------------- Database Operations --------------------------- @staticmethod def read_from_cursor(value, _=None): """ @@ -1433,7 +1702,7 @@ def read_from_cursor(value, _=None): if value[1] == '[' or value[1] == '(': return TGeogPointSeqSet(string=value) else: - return TGeogPointSeq(string=value) + return TGeomPointSeq(string=value) raise Exception("ERROR: Could not parse temporal point value") @@ -1444,8 +1713,9 @@ class TGeomPointInst(TPointInst['TGeomPoint', 'TGeomPointInst', 'TGeomPointSeq', _make_function = lambda *args: None _cast_function = lambda x: None - def __init__(self, string: Optional[str] = None, *, point: Optional[Union[str, pg.Point]] = None, - timestamp: Optional[Union[str, datetime]] = None, srid: Optional[int] = 0, _inner=None) -> None: + def __init__(self, string: Optional[str] = None, *, point: Optional[Union[str, pg.Point, shp.Point]] = None, + timestamp: Optional[Union[str, datetime]] = None, + srid: Optional[int] = 0, _inner=None) -> None: super().__init__(string=string, value=point, timestamp=timestamp, _inner=_inner) if self._inner is None: self._inner = tgeompoint_in(f"SRID={srid};{point}@{timestamp}") @@ -1459,8 +1729,10 @@ class TGeogPointInst(TPointInst['TGeogPoint', 'TGeogPointInst', 'TGeogPointSeq', _cast_function = lambda x: None def __init__(self, string: Optional[str] = None, *, - point: Optional[Union[str, pg.Point, Tuple[float, float]]] = None, - timestamp: Optional[Union[str, datetime]] = None, srid: Optional[int] = 0, _inner=None) -> None: + point: Optional[Union[str, pg.Point, shp.Point, + Tuple[float, float]]] = None, + timestamp: Optional[Union[str, datetime]] = None, + srid: Optional[int] = 4326, _inner=None) -> None: super().__init__(string=string, value=point, timestamp=timestamp, _inner=_inner) if self._inner is None: p = f'POINT({point[0]} {point[1]})' if isinstance(point, tuple) else f'{point}' @@ -1473,8 +1745,10 @@ class TGeomPointSeq(TPointSeq['TGeomPoint', 'TGeomPointInst', 'TGeomPointSeq', ' """ ComponentClass = TGeomPointInst - def __init__(self, string: Optional[str] = None, *, instant_list: Optional[List[Union[str, TGeomPointInst]]] = None, - lower_inc: bool = True, upper_inc: bool = False, expandable: Union[bool, int] = False, + def __init__(self, string: Optional[str] = None, *, + instant_list: Optional[List[Union[str, TGeomPointInst]]] = None, + lower_inc: bool = True, upper_inc: bool = False, + expandable: Union[bool, int] = False, interpolation: TInterpolation = TInterpolation.LINEAR, normalize: bool = True, _inner=None): super().__init__(string=string, instant_list=instant_list, lower_inc=lower_inc, upper_inc=upper_inc, @@ -1491,7 +1765,8 @@ def __init__(self, string: Optional[str] = None, *, instant_list: Optional[List[ lower_inc: bool = True, upper_inc: bool = False, expandable: Union[bool, int] = False, interpolation: TInterpolation = TInterpolation.LINEAR, normalize: bool = True, _inner=None): super().__init__(string=string, instant_list=instant_list, lower_inc=lower_inc, upper_inc=upper_inc, - expandable=expandable, interpolation=interpolation, normalize=normalize, _inner=_inner) + expandable=expandable, interpolation=interpolation, + normalize=normalize, _inner=_inner) class TGeomPointSeqSet(TPointSeqSet['TGeomPoint', 'TGeomPointInst', 'TGeomPointSeq', 'TGeomPointSeqSet'], TGeomPoint): @@ -1500,9 +1775,12 @@ class TGeomPointSeqSet(TPointSeqSet['TGeomPoint', 'TGeomPointInst', 'TGeomPointS """ ComponentClass = TGeomPointSeq - def __init__(self, string: Optional[str] = None, *, sequence_list: Optional[List[Union[str, TGeomPointSeq]]] = None, + def __init__(self, string: Optional[str] = None, *, + sequence_list: Optional[List[Union[str, TGeomPointSeq]]] = None, + expandable: Union[bool, int] = False, normalize: bool = True, _inner=None): - super().__init__(string=string, sequence_list=sequence_list, normalize=normalize, _inner=_inner) + super().__init__(string=string, sequence_list=sequence_list, + expandable=expandable, normalize=normalize, _inner=_inner) class TGeogPointSeqSet(TPointSeqSet['TGeogPoint', 'TGeogPointInst', 'TGeogPointSeq', 'TGeogPointSeqSet'], TGeogPoint): @@ -1511,6 +1789,9 @@ class TGeogPointSeqSet(TPointSeqSet['TGeogPoint', 'TGeogPointInst', 'TGeogPointS """ ComponentClass = TGeogPointSeq - def __init__(self, string: Optional[str] = None, *, sequence_list: Optional[List[Union[str, TGeogPointSeq]]] = None, + def __init__(self, string: Optional[str] = None, *, + sequence_list: Optional[List[Union[str, TGeogPointSeq]]] = None, + expandable: Union[bool, int] = False, normalize: bool = True, _inner=None): - super().__init__(string=string, sequence_list=sequence_list, normalize=normalize, _inner=_inner) + super().__init__(string=string, sequence_list=sequence_list, + expandable=expandable, normalize=normalize, _inner=_inner) diff --git a/pymeos/pymeos/main/ttext.py b/pymeos/pymeos/main/ttext.py index 141d6bd9..331b29fa 100644 --- a/pymeos/pymeos/main/ttext.py +++ b/pymeos/pymeos/main/ttext.py @@ -21,54 +21,9 @@ class TText(Temporal[str, 'TText', 'TTextInst', 'TTextSeq', 'TTextSeqSet'], ABC) def __init__(self, _inner) -> None: super().__init__() - def at(self, other: Union[str, List[str], datetime, TimestampSet, Period, PeriodSet]) -> TText: - """ - Returns a new temporal string with the values of `self` restricted to the time or value `other`. - - Args: - other: Time or value to restrict to. - - Returns: - A new temporal string. - - MEOS Functions: - ttext_at_value, temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset - """ - if isinstance(other, str): - result = ttext_at_value(self._inner, other) - elif isinstance(other, list): - # result = ttext_at_values(self._inner, other) - results = [ttext_at_value(self._inner, value) for value in other] - result = temporal_merge_array(results, len(results)) - else: - return super().at(other) - return Temporal._factory(result) - - def minus(self, other: Union[str, List[str], datetime, TimestampSet, Period, PeriodSet]) -> TText: - """ - Returns a new temporal string with the values of `self` restricted to the complement of the time or value - `other`. - - Args: - other: Time or value to restrict to the complement of. - - Returns: - A new temporal string. - - MEOS Functions: - ttext_minus_value, temporal_minus_timestamp, temporal_minus_timestampset, temporal_minus_period, - temporal_minus_periodset - """ - if isinstance(other, str): - result = ttext_minus_value(self._inner, other) - elif isinstance(other, list): - result = reduce(ttext_minus_value, other, self._inner) - else: - return super().minus(other) - return Temporal._factory(result) - + # ------------------------- Constructors ---------------------------------- @staticmethod - def from_base(value: str, base: Temporal) -> TText: + def from_base_temporal(value: str, base: Temporal) -> TText: """ Create a temporal string from a string and the time frame of another temporal object. @@ -77,12 +32,12 @@ def from_base(value: str, base: Temporal) -> TText: base: Temporal object to use as time frame. Returns: - A new temporal string. + A new :class:`TText` object. MEOS Functions: - ttext_from_base + ttext_from_base_temp """ - result = ttext_from_base(value, base._inner) + result = ttext_from_base_temp(value, base._inner) return Temporal._factory(result) @staticmethod @@ -113,19 +68,45 @@ def from_base_time(value: str, base: Time) -> TText: A new temporal string. MEOS Functions: - ttextinst_make, ttextdiscseq_from_base_time, ttextseq_from_base_time, ttextseqset_from_base_time - + ttextinst_make, ttextseq_from_base_timestampset, + ttextseq_from_base_period, ttextseqset_from_base_periodset """ if isinstance(base, datetime): return TTextInst(_inner=ttextinst_make(value, datetime_to_timestamptz(base))) elif isinstance(base, TimestampSet): - return TTextSeq(_inner=ttextdiscseq_from_base_time(value, base._inner)) + return TTextSeq(_inner=ttextseq_from_base_timestampset(value, base._inner)) elif isinstance(base, Period): - return TTextSeq(_inner=ttextseq_from_base_time(value, base._inner)) + return TTextSeq(_inner=ttextseq_from_base_period(value, base._inner)) elif isinstance(base, PeriodSet): - return TTextSeqSet(_inner=ttextseqset_from_base_time(value, base._inner)) + return TTextSeqSet(_inner=ttextseqset_from_base_periodset(value, base._inner)) raise TypeError(f'Operation not supported with type {base.__class__}') + # ------------------------- Output ---------------------------------------- + def __str__(self) -> str: + """ + Returns the string representation of `self`. + + Returns: + A string with the string representation of `self`. + + MEOS Functions: + ttext_out + """ + return ttext_out(self._inner) + + def as_wkt(self) -> str: + """ + Returns the Well-Known Text representation of `self`. + + Returns: + A string with the Well-Known Text representation of `self`. + + MEOS Functions: + ttext_as_wkt + """ + return ttext_out(self._inner) + + # ------------------------- Accessors ------------------------------------- def value_set(self) -> Set[str]: """ Returns the set of unique values of the temporal string. @@ -211,31 +192,52 @@ def lower(self) -> TText: """ return self.__class__(_inner=ttext_lower(self._inner)) + def value_at_timestamp(self, timestamp: datetime) -> str: + """ + Returns the value that `self` takes at a certain moment. + + Args: + timestamp: The moment to get the value. - def concatenate(self, other: Union[str, TText], other_before: bool = False): + Returns: + A string with the value of `self` at `timestamp`. + + MEOS Functions: + ttext_value_at_timestamp """ - Returns a new temporal string with the values of `self` concatenated with the values of `other`. + result = ttext_value_at_timestamp(self._inner, datetime_to_timestamptz(timestamp), True) + return text2cstring(result[0]) + + # ------------------------- Ever and Always Comparisons ------------------- + def always_equal(self, value: str) -> bool: + """ + Returns whether the values of `self` are always equal to `value`. Args: - other: Temporal string or string to concatenate. - other_before: If `True` the values of `other` are prepended to the values of `self`. + value: String value to compare. Returns: - A new temporal string. + `True` if the values of `self` are always equal to `value`, `False` otherwise. MEOS Functions: - textcat_ttext_text, textcat_text_ttext, textcat_ttext_ttext + ttext_always_eq + """ + return ttext_always_eq(self._inner, value) + def always_not_equal(self, value: str) -> bool: """ - if isinstance(other, str): - result = textcat_ttext_text(self._inner, other) if not other_before \ - else textcat_text_ttext(other, self._inner) - elif isinstance(other, TText): - result = textcat_ttext_ttext(self._inner, other._inner) if not other_before \ - else textcat_ttext_ttext(other._inner, self._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - return self.__class__(_inner=result) + Returns whether the values of `self` are always not equal to `value`. + + Args: + value: String value to compare. + + Returns: + `True` if the values of `self` are always not equal to `value`, `False` otherwise. + + MEOS Functions: + ttext_ever_eq + """ + return not ttext_always_eq(self._inner, value) def always_less(self, value: str) -> bool: """ @@ -267,65 +269,65 @@ def always_less_or_equal(self, value: str) -> bool: """ return ttext_always_le(self._inner, value) - def always_equal(self, value: str) -> bool: + def always_greater(self, value: str) -> bool: """ - Returns whether the values of `self` are always equal to `value`. + Returns whether the values of `self` are always greater than `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are always equal to `value`, `False` otherwise. + `True` if the values of `self` are always greater than `value`, `False` otherwise. MEOS Functions: - ttext_always_eq + ttext_ever_le """ - return ttext_always_eq(self._inner, value) + return not ttext_ever_le(self._inner, value) - def always_not_equal(self, value: str) -> bool: + def always_greater_or_equal(self, value: str) -> bool: """ - Returns whether the values of `self` are always not equal to `value`. + Returns whether the values of `self` are always greater than or equal to `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are always not equal to `value`, `False` otherwise. + `True` if the values of `self` are always greater than or equal to `value`, `False` otherwise. MEOS Functions: - ttext_ever_eq + ttext_ever_lt """ - return not ttext_always_eq(self._inner, value) + return not ttext_ever_lt(self._inner, value) - def always_greater_or_equal(self, value: str) -> bool: + def ever_equal(self, value: str) -> bool: """ - Returns whether the values of `self` are always greater than or equal to `value`. + Returns whether the values of `self` are ever equal to `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are always greater than or equal to `value`, `False` otherwise. + `True` if the values of `self` are ever equal to `value`, `False` otherwise. MEOS Functions: - ttext_ever_lt + ttext_ever_eq """ - return not ttext_ever_lt(self._inner, value) + return ttext_ever_eq(self._inner, value) - def always_greater(self, value: str) -> bool: + def ever_not_equal(self, value: str) -> bool: """ - Returns whether the values of `self` are always greater than `value`. + Returns whether the values of `self` are ever not equal to `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are always greater than `value`, `False` otherwise. + `True` if the values of `self` are ever not equal to `value`, `False` otherwise. MEOS Functions: - ttext_ever_le + ttext_always_eq """ - return not ttext_ever_le(self._inner, value) + return not ttext_ever_eq(self._inner, value) def ever_less(self, value: str) -> bool: """ @@ -342,7 +344,6 @@ def ever_less(self, value: str) -> bool: """ return ttext_ever_lt(self._inner, value) - def ever_less_or_equal(self, value: str) -> bool: """ Returns whether the values of `self` are ever less than or equal to `value`. @@ -358,65 +359,65 @@ def ever_less_or_equal(self, value: str) -> bool: """ return ttext_ever_le(self._inner, value) - def ever_equal(self, value: str) -> bool: + def ever_greater(self, value: str) -> bool: """ - Returns whether the values of `self` are ever equal to `value`. + Returns whether the values of `self` are ever greater than `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are ever equal to `value`, `False` otherwise. + `True` if the values of `self` are ever greater than `value`, `False` otherwise. MEOS Functions: - ttext_ever_eq + ttext_always_le """ - return ttext_ever_eq(self._inner, value) + return not ttext_always_le(self._inner, value) - def ever_not_equal(self, value: str) -> bool: + def ever_greater_or_equal(self, value: str) -> bool: """ - Returns whether the values of `self` are ever not equal to `value`. + Returns whether the values of `self` are ever greater than or equal to `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are ever not equal to `value`, `False` otherwise. + `True` if the values of `self` are ever greater than or equal to `value`, `False` otherwise. MEOS Functions: - ttext_always_eq + ttext_always_lt """ - return not ttext_ever_eq(self._inner, value) + return not ttext_always_lt(self._inner, value) - def ever_greater_or_equal(self, value: str) -> bool: + def never_equal(self, value: str) -> bool: """ - Returns whether the values of `self` are ever greater than or equal to `value`. + Returns whether the values of `self` are never equal to `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are ever greater than or equal to `value`, `False` otherwise. + `True` if the values of `self` are never equal to `value`, `False` otherwise. MEOS Functions: - ttext_always_lt + ttext_ever_eq """ - return not ttext_always_lt(self._inner, value) + return not ttext_ever_eq(self._inner, value) - def ever_greater(self, value: str) -> bool: + def never_not_equal(self, value: str) -> bool: """ - Returns whether the values of `self` are ever greater than `value`. + Returns whether the values of `self` are never not equal to `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are ever greater than `value`, `False` otherwise. + `True` if the values of `self` are never not equal to `value`, `False` otherwise. MEOS Functions: - ttext_always_le + ttext_always_eq """ - return not ttext_always_le(self._inner, value) + return ttext_always_eq(self._inner, value) def never_less(self, value: str) -> bool: """ @@ -448,65 +449,74 @@ def never_less_or_equal(self, value: str) -> bool: """ return not ttext_ever_le(self._inner, value) - def never_equal(self, value: str) -> bool: + def never_greater_or_equal(self, value: str) -> bool: """ - Returns whether the values of `self` are never equal to `value`. + Returns whether the values of `self` are never greater than or equal to `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are never equal to `value`, `False` otherwise. + `True` if the values of `self` are never greater than or equal to `value`, `False` otherwise. MEOS Functions: - ttext_ever_eq + ttext_always_lt """ - return not ttext_ever_eq(self._inner, value) + return ttext_always_lt(self._inner, value) - def never_not_equal(self, value: str) -> bool: + def never_greater(self, value: str) -> bool: """ - Returns whether the values of `self` are never not equal to `value`. + Returns whether the values of `self` are never greater than `value`. Args: value: String value to compare. Returns: - `True` if the values of `self` are never not equal to `value`, `False` otherwise. + `True` if the values of `self` are never greater than `value`, `False` otherwise. MEOS Functions: - ttext_always_eq + ttext_always_le """ - return ttext_always_eq(self._inner, value) + return ttext_always_le(self._inner, value) - def never_greater_or_equal(self, value: str) -> bool: + # ------------------------- Temporal Comparisons -------------------------- + def temporal_equal(self, other: Union[str, Temporal]) -> Temporal: """ - Returns whether the values of `self` are never greater than or equal to `value`. + Returns the temporal equality relation between `self` and `other`. Args: - value: String value to compare. + other: A string or temporal object to compare to `self`. Returns: - `True` if the values of `self` are never greater than or equal to `value`, `False` otherwise. + A :class:`TBool` with the result of the temporal equality relation. MEOS Functions: - ttext_always_lt + teq_ttext_text, teq_temporal_temporal """ - return ttext_always_lt(self._inner, value) + if isinstance(other, str): + result = teq_ttext_text(self._inner, other) + else: + return super().temporal_equal(other) + return Temporal._factory(result) - def never_greater(self, value: str) -> bool: + def temporal_not_equal(self, other: Union[str, Temporal]) -> Temporal: """ - Returns whether the values of `self` are never greater than `value`. + Returns the temporal not equal relation between `self` and `other`. Args: - value: String value to compare. + other: A string or temporal object to compare to `self`. Returns: - `True` if the values of `self` are never greater than `value`, `False` otherwise. + A :class:`TBool` with the result of the temporal not equal relation. MEOS Functions: - ttext_always_le + tne_ttext_text, tne_temporal_temporal """ - return ttext_always_le(self._inner, value) + if isinstance(other, str): + result = tne_ttext_text(self._inner, other) + else: + return super().temporal_not_equal(other) + return Temporal._factory(result) def temporal_less(self, other: Union[str, Temporal]) -> TBool: """ @@ -546,100 +556,120 @@ def temporal_less_or_equal(self, other: Union[str, Temporal]) -> Temporal: return super().temporal_less_or_equal(other) return Temporal._factory(result) - def temporal_equal(self, other: Union[str, Temporal]) -> Temporal: + def temporal_greater(self, other: Union[str, Temporal]) -> Temporal: """ - Returns the temporal equality relation between `self` and `other`. + Returns the temporal greater than relation between `self` and `other`. Args: other: A string or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal equality relation. + A :class:`TBool` with the result of the temporal greater than relation. MEOS Functions: - teq_ttext_text, teq_temporal_temporal + tgt_ttext_text, tgt_temporal_temporal """ if isinstance(other, str): - result = teq_ttext_text(self._inner, other) + result = tgt_ttext_text(self._inner, other) else: - return super().temporal_equal(other) + return super().temporal_greater(other) return Temporal._factory(result) - def temporal_not_equal(self, other: Union[str, Temporal]) -> Temporal: + def temporal_greater_or_equal(self, other: Union[str, Temporal]) -> Temporal: """ - Returns the temporal not equal relation between `self` and `other`. + Returns the temporal greater or equal relation between `self` and `other`. Args: other: A string or temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal not equal relation. + A :class:`TBool` with the result of the temporal greater or equal relation. MEOS Functions: - tne_ttext_text, tne_temporal_temporal + tge_ttext_text, tge_temporal_temporal """ if isinstance(other, str): - result = tne_ttext_text(self._inner, other) + result = tge_ttext_text(self._inner, other) else: - return super().temporal_not_equal(other) + return super().temporal_greater_or_equal(other) return Temporal._factory(result) - def temporal_greater_or_equal(self, other: Union[str, Temporal]) -> Temporal: + # ------------------------- Restrictions ---------------------------------- + def at(self, other: Union[str, List[str], datetime, TimestampSet, Period, PeriodSet]) -> TText: """ - Returns the temporal greater or equal relation between `self` and `other`. + Returns a new temporal string with the values of `self` restricted to the time or value `other`. Args: - other: A string or temporal object to compare to `self`. + other: Time or value to restrict to. Returns: - A :class:`TBool` with the result of the temporal greater or equal relation. + A new temporal string. MEOS Functions: - tge_ttext_text, tge_temporal_temporal + ttext_at_value, temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset """ if isinstance(other, str): - result = tge_ttext_text(self._inner, other) + result = ttext_at_value(self._inner, other) + elif isinstance(other, list): + # result = ttext_at_values(self._inner, other) + results = [ttext_at_value(self._inner, value) for value in other] + result = temporal_merge_array(results, len(results)) else: - return super().temporal_greater_or_equal(other) + return super().at(other) return Temporal._factory(result) - def temporal_greater(self, other: Union[str, Temporal]) -> Temporal: + def minus(self, other: Union[str, List[str], datetime, TimestampSet, Period, PeriodSet]) -> TText: """ - Returns the temporal greater than relation between `self` and `other`. + Returns a new temporal string with the values of `self` restricted to the complement of the time or value + `other`. Args: - other: A string or temporal object to compare to `self`. + other: Time or value to restrict to the complement of. Returns: - A :class:`TBool` with the result of the temporal greater than relation. + A new temporal string. MEOS Functions: - tgt_ttext_text, tgt_temporal_temporal + ttext_minus_value, temporal_minus_timestamp, temporal_minus_timestampset, temporal_minus_period, + temporal_minus_periodset """ if isinstance(other, str): - result = tgt_ttext_text(self._inner, other) + result = ttext_minus_value(self._inner, other) + elif isinstance(other, list): + result = reduce(ttext_minus_value, other, self._inner) else: - return super().temporal_greater(other) + return super().minus(other) return Temporal._factory(result) - def __add__(self, other): + # ------------------------- Text Operations ------------------------------ + def concatenate(self, other: Union[str, TText], other_before: bool = False): """ Returns a new temporal string with the values of `self` concatenated with the values of `other`. Args: other: Temporal string or string to concatenate. + other_before: If `True` the values of `other` are prepended to the values of `self`. Returns: A new temporal string. MEOS Functions: textcat_ttext_text, textcat_text_ttext, textcat_ttext_ttext + """ - return self.concatenate(other) + if isinstance(other, str): + result = textcat_ttext_text(self._inner, other) if not other_before \ + else textcat_text_ttext(other, self._inner) + elif isinstance(other, TText): + result = textcat_ttext_ttext(self._inner, other._inner) if not other_before \ + else textcat_ttext_ttext(other._inner, self._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + return self.__class__(_inner=result) - def __radd__(self, other): + def __add__(self, other): """ - Returns a new temporal string with the values of `other` concatenated with the values of `self`. + Returns a new temporal string with the values of `self` concatenated with the values of `other`. Args: other: Temporal string or string to concatenate. @@ -650,47 +680,24 @@ def __radd__(self, other): MEOS Functions: textcat_ttext_text, textcat_text_ttext, textcat_ttext_ttext """ - return self.concatenate(other, True) + return self.concatenate(other) - def value_at_timestamp(self, timestamp: datetime) -> str: + def __radd__(self, other): """ - Returns the value that `self` takes at a certain moment. + Returns a new temporal string with the values of `other` concatenated with the values of `self`. Args: - timestamp: The moment to get the value. - - Returns: - A string with the value of `self` at `timestamp`. - - MEOS Functions: - ttext_value_at_timestamp - """ - return ttext_value_at_timestamp(self._inner, datetime_to_timestamptz(timestamp), True) - - def __str__(self) -> str: - """ - Returns the string representation of `self`. - - Returns: - A string with the string representation of `self`. - - MEOS Functions: - ttext_out - """ - return ttext_out(self._inner) - - def as_wkt(self) -> str: - """ - Returns the Well-Known Text representation of `self`. + other: Temporal string or string to concatenate. Returns: - A string with the Well-Known Text representation of `self`. + A new temporal string. MEOS Functions: - ttext_as_wkt + textcat_ttext_text, textcat_text_ttext, textcat_ttext_ttext """ - return ttext_out(self._inner) + return self.concatenate(other, True) + # ------------------------- Database Operations --------------------------- @staticmethod def read_from_cursor(value, _=None): """ diff --git a/pymeos/pymeos/meos_function_list.md b/pymeos/pymeos/meos_function_list.md index 08d91239..fc62c02a 100644 --- a/pymeos/pymeos/meos_function_list.md +++ b/pymeos/pymeos/meos_function_list.md @@ -3,7 +3,7 @@ - [x] `extern void meos_initialize(const char *tz_str);` -- [x] `extern void meos_finish(void);` +- [x] `extern void meos_finalize(void);` ## Functions for input/output PostgreSQL time types @@ -609,43 +609,32 @@ ### Constructor functions for temporal types -- [x] `extern Temporal *tbool_from_base(bool b, const Temporal *temp);` +- [x] `extern Temporal *tbool_from_base_temp(bool b, const Temporal *temp);` - [x] `extern TInstant *tboolinst_make(bool b, TimestampTz t);` -- [ ] `extern TSequence *tbooldiscseq_from_base(bool b, const TSequence *is);` Not implemented in MEOS -- [x] `extern TSequence *tbooldiscseq_from_base_time(bool b, const TimestampSet *ts);` -- [ ] `extern TSequence *tboolseq_from_base(bool b, const TSequence *seq);` `tbool_from_base` used instead -- [x] `extern TSequence *tboolseq_from_base_time(bool b, const Period *p);` -- [ ] `extern TSequenceSet *tboolseqset_from_base(bool b, const TSequenceSet *ss);` `tbool_from_base` used instead -- [x] `extern TSequenceSet *tboolseqset_from_base_time(bool b, const PeriodSet *ps);` +- [x] `extern TSequence *tboolseq_from_base_timestampset(bool b, const TimestampSet *ts);` +- [x] `extern TSequence *tboolseq_from_base_period(bool b, const Period *p);` +- [x] `extern TSequenceSet *tboolseqset_from_base_periodset(bool b, const PeriodSet *ps);` - [x] `extern Temporal *temporal_copy(const Temporal *temp);` -- [x] `extern Temporal *tfloat_from_base(double d, const Temporal *temp, interpType interp);` +- [x] `extern Temporal *tfloat_from_base_temp(double d, const Temporal *temp, interpType interp);` - [x] `extern TInstant *tfloatinst_make(double d, TimestampTz t);` -- [x] `extern TSequence *tfloatdiscseq_from_base_time(double d, const TimestampSet *ts);` -- [ ] `extern TSequence *tfloatseq_from_base(double d, const TSequence *seq, interpType interp);` `tfloat_from_base` used instead -- [x] `extern TSequence *tfloatseq_from_base_time(double d, const Period *p, interpType interp);` -- [ ] `extern TSequenceSet *tfloatseqset_from_base(double d, const TSequenceSet *ss, interpType interp);` `tfloat_from_base` used instead -- [x] `extern TSequenceSet *tfloatseqset_from_base_time(double d, const PeriodSet *ps, interpType interp);` -- [x] `extern Temporal *tgeogpoint_from_base(const GSERIALIZED *gs, const Temporal *temp, interpType interp);` +- [x] `extern TSequence *tfloatseq_from_base_timestampset(double d, const TimestampSet *ts);` +- [x] `extern TSequence *tfloatseq_from_base_period(double d, const Period *p, interpType interp);` +- [x] `extern TSequenceSet *tfloatseqset_from_base_periodset(double d, const PeriodSet *ps, interpType interp);` +- [x] `extern Temporal *tgeogpoint_from_base_temp(const GSERIALIZED *gs, const Temporal *temp, interpType interp);` - [x] `extern TInstant *tgeogpointinst_make(const GSERIALIZED *gs, TimestampTz t);` -- [x] `extern TSequence *tgeogpointdiscseq_from_base_time(const GSERIALIZED *gs, const TimestampSet *ts);` -- [ ] `extern TSequence *tgeogpointseq_from_base(const GSERIALIZED *gs, const TSequence *seq, interpType interp);` `tgeogpoint_from_base` used instead -- [x] `extern TSequence *tgeogpointseq_from_base_time(const GSERIALIZED *gs, const Period *p, interpType interp);` -- [ ] `extern TSequenceSet *tgeogpointseqset_from_base(const GSERIALIZED *gs, const TSequenceSet *ss, interpType interp);` `tgeogpoint_from_base` used instead -- [x] `extern TSequenceSet *tgeogpointseqset_from_base_time(const GSERIALIZED *gs, const PeriodSet *ps, interpType interp);` -- [x] `extern Temporal *tgeompoint_from_base(const GSERIALIZED *gs, const Temporal *temp, interpType interp);` +- [x] `extern TSequence *tgeogpointseq_from_base_timestampset(const GSERIALIZED *gs, const TimestampSet *ts);` +- [x] `extern TSequence *tgeogpointseq_from_base_period(const GSERIALIZED *gs, const Period *p, interpType interp);` +- [x] `extern TSequenceSet *tgeogpointseqset_from_base_periodset(const GSERIALIZED *gs, const PeriodSet *ps, interpType interp);` +- [x] `extern Temporal *tgeompoint_from_base_temp(const GSERIALIZED *gs, const Temporal *temp, interpType interp);` - [x] `extern TInstant *tgeompointinst_make(const GSERIALIZED *gs, TimestampTz t);` -- [x] `extern TSequence *tgeompointdiscseq_from_base_time(const GSERIALIZED *gs, const TimestampSet *ts);` -- [ ] `extern TSequence *tgeompointseq_from_base(const GSERIALIZED *gs, const TSequence *seq, interpType interp);` `tgeompoint_from_base` used instead -- [x] `extern TSequence *tgeompointseq_from_base_time(const GSERIALIZED *gs, const Period *p, interpType interp);` -- [ ] `extern TSequenceSet *tgeompointseqset_from_base(const GSERIALIZED *gs, const TSequenceSet *ss, interpType interp);` `tgeompoint_from_base` used instead -- [x] `extern TSequenceSet *tgeompointseqset_from_base_time(const GSERIALIZED *gs, const PeriodSet *ps, interpType interp);` -- [x] `extern Temporal *tint_from_base(int i, const Temporal *temp);` +- [x] `extern TSequence *tgeompointseq_from_base_timestampset(const GSERIALIZED *gs, const TimestampSet *ts);` +- [x] `extern TSequence *tgeompointseq_from_base_period(const GSERIALIZED *gs, const Period *p, interpType interp);` +- [x] `extern TSequenceSet *tgeompointseqset_from_base_periodset(const GSERIALIZED *gs, const PeriodSet *ps, interpType interp);` +- [x] `extern Temporal *tint_from_base_temp(int i, const Temporal *temp);` - [x] `extern TInstant *tintinst_make(int i, TimestampTz t);` -- [x] `extern TSequence *tintdiscseq_from_base_time(int i, const TimestampSet *ts);` -- [ ] `extern TSequence *tintseq_from_base(int i, const TSequence *seq);` `tint_from_base` used instead -- [x] `extern TSequence *tintseq_from_base_time(int i, const Period *p);` -- [ ] `extern TSequenceSet *tintseqset_from_base(int i, const TSequenceSet *ss);` `tint_from_base` used instead -- [x] `extern TSequenceSet *tintseqset_from_base_time(int i, const PeriodSet *ps);` +- [x] `extern TSequence *tintseq_from_base_timestampset(int i, const TimestampSet *ts);` +- [x] `extern TSequence *tintseq_from_base_period(int i, const Period *p);` +- [x] `extern TSequenceSet *tintseqset_from_base_periodset(int i, const PeriodSet *ps);` - [x] `extern TSequence *tsequence_make(const TInstant **instants, int count, int maxcount, bool lower_inc, bool upper_inc, interpType interp, bool normalize);` - [x] `extern TSequence *tpointseq_make_coords(const double *xcoords, const double *ycoords, const double *zcoords, const TimestampTz *times, int count, int32 srid, bool geodetic, bool lower_inc, bool upper_inc, interpType interp, bool normalize);` @@ -653,13 +642,11 @@ - [x] `extern TSequenceSet *tsequenceset_make(const TSequence **sequences, int count, bool normalize);` - [ ] `extern TSequenceSet *tsequenceset_make_free(TSequence **sequences, int count, bool normalize);` Not necessary in PyMEOS - [ ] `extern TSequenceSet *tsequenceset_make_gaps(const TInstant **instants, int count, interpType interp, float maxdist, Interval *maxt);` -- [x] `extern Temporal *ttext_from_base(const text *txt, const Temporal *temp);` +- [x] `extern Temporal *ttext_from_base_temp(const text *txt, const Temporal *temp);` - [x] `extern TInstant *ttextinst_make(const text *txt, TimestampTz t);` -- [x] `extern TSequence *ttextdiscseq_from_base_time(const text *txt, const TimestampSet *ts);` -- [ ] `extern TSequence *ttextseq_from_base(const text *txt, const TSequence *seq);` `ttext_from_base` used instead -- [x] `extern TSequence *ttextseq_from_base_time(const text *txt, const Period *p);` -- [ ] `extern TSequenceSet *ttextseqset_from_base(const text *txt, const TSequenceSet *ss);` `ttext_from_base` used instead -- [x] `extern TSequenceSet *ttextseqset_from_base_time(const text *txt, const PeriodSet *ps);` +- [x] `extern TSequence *ttextseq_from_base_timestampset(const text *txt, const TimestampSet *ts);` +- [x] `extern TSequence *ttextseq_from_base_period(const text *txt, const Period *p);` +- [x] `extern TSequenceSet *ttextseqset_from_base_periodset(const text *txt, const PeriodSet *ps);` diff --git a/pymeos/pymeos/plotters/point_sequenceset_plotter.py b/pymeos/pymeos/plotters/point_sequenceset_plotter.py index fa5356b0..1039f169 100644 --- a/pymeos/pymeos/plotters/point_sequenceset_plotter.py +++ b/pymeos/pymeos/plotters/point_sequenceset_plotter.py @@ -31,7 +31,7 @@ def plot_xy(sequence_set: Union[TPointSeqSet, List[TPointSeq]], *args, **kwargs) seqs = sequence_set.sequences() if isinstance(sequence_set, TPointSeqSet) else sequence_set plots = [TemporalPointSequencePlotter.plot_xy(seqs[0], *args, **kwargs)] if 'color' not in kwargs: - kwargs['color'] = plots[0][0].get_color() + kwargs['color'] = plots[0][0][0].get_color() kwargs.pop('label', None) for seq in seqs[1:]: plots.append(TemporalPointSequencePlotter.plot_xy(seq, *args, **kwargs)) diff --git a/pymeos/pymeos/plotters/sequence_plotter.py b/pymeos/pymeos/plotters/sequence_plotter.py index 5be7d8b1..762350ef 100644 --- a/pymeos/pymeos/plotters/sequence_plotter.py +++ b/pymeos/pymeos/plotters/sequence_plotter.py @@ -67,3 +67,25 @@ def plot(sequence: Union[TSequence, List[TInstant]], *args, axes=None, show_mark base.tick_params(axis="x", rotation=45) return plots + + @staticmethod + def plot_sequences(sequences: List[TSequence], *args, **kwargs): + """ + Plot a list of :class:`TSequence` on the given axes. Every sequence will be plotted in a different color. + + Params: + sequences: The list of :class:`TSequence` to plot. + *args: Additional arguments to pass to the plot function. + **kwargs: Additional keyword arguments to pass to the plot function. + + Returns: + List of lists with the plotted elements of each sequence. + + See Also: + :func:`~pymeos.plotters.sequence_plotter.TemporalSequencePlotter.plot`, + :meth:`~pymeos.plotters.sequenceset_plotter.TemporalSequenceSetPlotter.plot` + """ + plots = [] + for seq in sequences: + plots.append(TemporalSequencePlotter.plot(seq, *args, **kwargs)) + return plots diff --git a/pymeos/pymeos/temporal/interpolation.py b/pymeos/pymeos/temporal/interpolation.py index 7a06aba8..4cb0b577 100644 --- a/pymeos/pymeos/temporal/interpolation.py +++ b/pymeos/pymeos/temporal/interpolation.py @@ -19,7 +19,7 @@ def from_string(source: str, none: bool = True) -> TInterpolation: Args: source: :class:`string` representing the interpolation - none: indicates whther to return `TIntepolation.NONE` when `source` represents an invalid interpolation + none: indicates whether to return `TIntepolation.NONE` when `source` represents an invalid interpolation Returns: A new :class:`TInterpolation` instance. @@ -32,7 +32,7 @@ def from_string(source: str, none: bool = True) -> TInterpolation: return TInterpolation.DISCRETE elif source.lower() == 'linear': return TInterpolation.LINEAR - elif source.lower() == 'stepwise': + elif source.lower() == 'stepwise' or source.lower() == 'step': return TInterpolation.STEPWISE elif source.lower() == 'none' or none: return TInterpolation.NONE diff --git a/pymeos/pymeos/temporal/temporal.py b/pymeos/pymeos/temporal/temporal.py index 09fe6de9..dd8fbad0 100644 --- a/pymeos/pymeos/temporal/temporal.py +++ b/pymeos/pymeos/temporal/temporal.py @@ -42,7 +42,35 @@ class Temporal(Generic[TBase, TG, TI, TS, TSS], ABC): _parse_function = None + def _expandable(self) -> bool: + return False + + # ------------------------- Constructors ---------------------------------- + @classmethod + def _factory(cls, inner): + from ..factory import _TemporalFactory + return _TemporalFactory.create_temporal(inner) + + def __copy__(self) -> Self: + """ + Returns a copy of the temporal object. + """ + inner_copy = temporal_copy(self._inner) + return self.__class__._factory(inner_copy) + + # ------------------------- Output ---------------------------------------- + def __str__(self) -> str: + """ + Returns the string representation of the `self`. + """ + pass + + def __repr__(self) -> str: + return (f'{self.__class__.__name__}' + f'({self})') + @staticmethod + @abstractmethod def from_base_time(value: TBase, base: Time) -> TG: """ Create a temporal object from a boolean value and a time object. @@ -57,8 +85,153 @@ def from_base_time(value: TBase, base: Time) -> TG: """ pass - def _expandable(self) -> bool: - return False + @classmethod + def from_wkb(cls: Type[Self], wkb: bytes) -> Self: + """ + Returns a temporal object from WKB bytes. + + Args: + wkb: The WKB string. + + Returns: + A temporal object from WKB bytes. + + MEOS Functions: + temporal_from_wkb + """ + result = temporal_from_wkb(wkb) + return Temporal._factory(result) + + @classmethod + def from_hexwkb(cls: Type[Self], hexwkb: str) -> Self: + """ + Returns a temporal object from a hex-encoded WKB string. + + Args: + hexwkb: The hex-encoded WKB string. + + Returns: + A temporal object from a hex-encoded WKB string. + + MEOS Functions: + temporal_from_hexwkb + """ + result = temporal_from_hexwkb(hexwkb) + return Temporal._factory(result) + + @classmethod + def from_mfjson(cls: Type[Self], mfjson: str) -> Self: + """ + Returns a temporal object from a MF-JSON string. + + Args: + mfjson: The MF-JSON string. + + Returns: + A temporal object from a MF-JSON string. + + MEOS Functions: + temporal_from_mfjson + """ + result = temporal_from_mfjson(mfjson) + return Temporal._factory(result) + + @classmethod + def from_merge(cls: Type[Self], *temporals: TG) -> Self: + """ + Returns a temporal object that is the result of merging the given temporal objects. + + Args: + *temporals: The temporal objects to merge. + + Returns: + A temporal object that is the result of merging the given temporal objects. + + MEOS Functions: + temporal_merge_array + """ + result = temporal_merge_array([temp._inner for temp in temporals if temp is not None], len(temporals)) + return Temporal._factory(result) + + @classmethod + def from_merge_array(cls: Type[Self], temporals: List[TG]) -> Self: + """ + Returns a temporal object that is the result of merging the given temporal objects. + + Args: + temporals: The temporal objects to merge. + + Returns: + A temporal object that is the result of merging the given temporal objects. + """ + result = temporal_merge_array([temp._inner for temp in temporals if temp is not None], len(temporals)) + return Temporal._factory(result) + + # ------------------------- Output ---------------------------------------- + @abstractmethod + def as_wkt(self) -> str: + """ + Returns the temporal object as a WKT string. + + Returns: + The temporal object as a WKT string. + """ + pass + + def as_mfjson(self, with_bbox: bool = True, flags: int = 3, precision: int = 6, srs: Optional[str] = None) -> str: + """ + Returns the temporal object as a MF-JSON string. + + Args: + with_bbox: Whether to include the bounding box in the output. + flags: The flags to use for the output. + precision: The precision to use for the output. + srs: The SRS to use for the output. + + Returns: + The temporal object as a MF-JSON string. + + MEOS Functions: + temporal_as_mfjson + """ + return temporal_as_mfjson(self._inner, with_bbox, flags, precision, srs) + + def as_wkb(self) -> bytes: + """ + Returns the temporal object as a hex-encoded WKB string. + + Returns: + The temporal object as a hex-encoded WKB string. + + MEOS Functions: + temporal_as_hexwkb + """ + return temporal_as_wkb(self._inner, 4) + + def as_hexwkb(self) -> str: + """ + Returns the temporal object as a hex-encoded WKB string. + + Returns: + The temporal object as a hex-encoded WKB string. + + MEOS Functions: + temporal_as_hexwkb + """ + return temporal_as_hexwkb(self._inner, 4)[0] + + # ------------------------- Accessors ------------------------------------- + def bounding_box(self) -> TBox: + """ + Returns the bounding box of `self`. + + Returns: + The bounding box of `self`. + + MEOS Functions: + temporal_to_period + """ + return Period(_inner=temporal_to_period(self._inner)) def interpolation(self) -> TInterpolation: """ @@ -67,7 +240,7 @@ def interpolation(self) -> TInterpolation: MEOS Functions: temporal_interpolation """ - val = temporal_interpolation(self._inner) + val = temporal_interp(self._inner) return TInterpolation.from_string(val) @abstractmethod @@ -125,25 +298,30 @@ def time(self) -> PeriodSet: """ return PeriodSet(_inner=temporal_time(self._inner)) - def duration(self) -> timedelta: + def duration(self, ignore_gaps=False) -> timedelta: """ - Returns the duration of `self` taking into account any possible gap. + Returns the duration of `self`. By default, the gaps in `self` are taken into account, but this can be + changed by setting `ignore_gaps` to ``True``. This will only potentially alter the result for sequence sets and + discrete sequences. + + Parameters: + ignore_gaps: Whether to take into account potential time gaps in the temporal value. MEOS Functions: temporal_duration """ - return interval_to_timedelta(temporal_duration(self._inner, False)) + return interval_to_timedelta(temporal_duration(self._inner, ignore_gaps)) - def timespan(self) -> timedelta: + def period(self) -> Period: """ - Returns the duration of `self` ignoring any potential gap. + Returns the :class:`Period` on which `self` is defined ignoring potential time gaps. MEOS Functions: - temporal_duration + temporal_to_period """ - return interval_to_timedelta(temporal_duration(self._inner, True)) + return self.timespan() - def period(self) -> Period: + def timespan(self) -> Period: """ Returns the :class:`Period` on which `self` is defined ignoring potential time gaps. @@ -181,35 +359,37 @@ def end_instant(self) -> TI: from ..factory import _TemporalFactory return _TemporalFactory.create_temporal(temporal_end_instant(self._inner)) - def max_instant(self) -> TI: + def min_instant(self) -> TI: """ - Returns the instant in `self` with the maximum value. + Returns the instant in `self` with the minimum value. + If multiple instants have the minimum value, the first one is returned. MEOS Functions: - temporal_max_instant + temporal_min_instant """ from ..factory import _TemporalFactory - return _TemporalFactory.create_temporal(temporal_max_instant(self._inner)) + return _TemporalFactory.create_temporal(temporal_min_instant(self._inner)) - def min_instant(self) -> TI: + def max_instant(self) -> TI: """ - Returns the instant in `self` with the minimum value. + Returns the instant in `self` with the maximum value. + If multiple instants have the maximum value, the first one is returned. MEOS Functions: - temporal_min_instant + temporal_max_instant """ from ..factory import _TemporalFactory - return _TemporalFactory.create_temporal(temporal_min_instant(self._inner)) + return _TemporalFactory.create_temporal(temporal_max_instant(self._inner)) def instant_n(self, n: int) -> TI: """ - Returns the n-th instant in `self`. + Returns the n-th instant in `self`. (0-based) MEOS Functions: temporal_instant_n """ from ..factory import _TemporalFactory - return _TemporalFactory.create_temporal(temporal_instant_n(self._inner, n)) + return _TemporalFactory.create_temporal(temporal_instant_n(self._inner, n + 1)) def instants(self) -> List[TI]: """ @@ -251,12 +431,12 @@ def end_timestamp(self) -> datetime: def timestamp_n(self, n: int) -> datetime: """ - Returns the n-th timestamp in `self`. + Returns the n-th timestamp in `self`. (0-based) MEOS Functions: temporal_timestamp_n """ - return timestamptz_to_datetime(temporal_timestamp_n(self._inner, n)) + return timestamptz_to_datetime(temporal_timestamp_n(self._inner, n + 1)) def timestamps(self) -> List[datetime]: """ @@ -279,6 +459,29 @@ def segments(self) -> List[TS]: from ..factory import _TemporalFactory return [_TemporalFactory.create_temporal(seqs[i]) for i in range(count)] + def __hash__(self) -> int: + """ + Returns the hash of the temporal object. + + Returns: + The hash of the temporal object. + + MEOS Functions: + temporal_hash + """ + return temporal_hash(self._inner) + + # ------------------------- Transformations ------------------------------- + def set_interpolation(self: Self, interpolation: TInterpolation) -> Self: + """ + Returns a new :class:`Temporal` object equal to `self` with the given interpolation. + + MEOS Functions: + temporal_set_interpolation + """ + new_temp = temporal_set_interp(self._inner, interpolation) + return Temporal._factory(new_temp) + def shift(self, delta: timedelta) -> Period: """ Returns a new :class:`Temporal` with the temporal dimension shifted by ``delta``. @@ -287,9 +490,10 @@ def shift(self, delta: timedelta) -> Period: delta: :class:`datetime.timedelta` instance to shift MEOS Functions: - temporal_shift_tscale + temporal_shift """ - return self.shift_tscale(shift=delta) + shifted = temporal_shift(self._inner, timedelta_to_interval(delta)) + return Temporal._factory(shifted) def tscale(self, duration: timedelta) -> Period: """ @@ -299,9 +503,10 @@ def tscale(self, duration: timedelta) -> Period: duration: :class:`datetime.timedelta` instance representing the duration of the new temporal MEOS Functions: - temporal_shift_tscale + temporal_tscale """ - return self.shift_tscale(duration=duration) + scaled = temporal_tscale(self._inner, timedelta_to_interval(duration)) + return Temporal._factory(scaled) def shift_tscale(self, shift: Optional[timedelta] = None, duration: Optional[timedelta] = None) -> Self: """ @@ -333,17 +538,14 @@ def to_instant(self) -> TI: inst = temporal_to_tinstant(self._inner) return Temporal._factory(inst) - def to_sequence(self, discrete: bool = False) -> TS: + def to_sequence(self) -> TS: """ - Returns `self` as a :class:`TSequence`. - - Args: - discrete: whether the sequence returned is discrete or continuous (stepwise or linear depending on subtype). + Converts `self` into a :class:`TSequence`. MEOS Functions: - temporal_to_tcontseq, temporal_to_tdiscseq + temporal_to_sequence """ - seq = temporal_to_tcontseq(self._inner) if not discrete else temporal_to_tdiscseq(self._inner) + seq = temporal_to_tsequence(self._inner) return Temporal._factory(seq) def to_sequenceset(self) -> TSS: @@ -366,7 +568,9 @@ def to_dataframe(self) -> DataFrame: } return DataFrame(data).set_index(keys='time') - def append(self, instant: TInstant[TBase], max_dist: float, max_time: timedelta) -> TG: + # ------------------------- Modifications --------------------------------- + def append_instant(self, instant: TInstant[TBase], max_dist: Optional[float] = 0.0, + max_time: Optional[timedelta] = None) -> TG: """ Returns a new :class:`Temporal` object equal to `self` with `instant` appended. @@ -378,19 +582,43 @@ def append(self, instant: TInstant[TBase], max_dist: float, max_time: timedelta) MEOS Functions: temporal_append_tinstant """ - new_inner = temporal_append_tinstant(self._inner, instant._inner, max_dist, timedelta_to_interval(max_time), - self._expandable()) + if max_time is None: + interv = None + else: + interv = timedelta_to_interval(max_time) + new_inner = temporal_append_tinstant(self._inner, instant._inner, max_dist, + interv, self._expandable()) + if new_inner == self._inner: + return self + return Temporal._factory(new_inner) + + def append_sequence(self, sequence: TSequence[TBase]) -> TG: + """ + Returns a new :class:`Temporal` object equal to `self` with `sequence` appended. + + Args: + sequence: :class:`TSequence` to append + + MEOS Functions: + temporal_append_tsequence + """ + new_inner = temporal_append_tsequence(self._inner, sequence._inner, + self._expandable()) if new_inner == self._inner: return self return Temporal._factory(new_inner) - def merge(self, other: Union[Temporal[TBase], List[Temporal[TBase]]]) -> TG: + def merge(self, other: Union[type(None), Temporal[TBase], List[Temporal[TBase]]]) -> TG: """ Returns a new :class:`Temporal` object that is the result of merging `self` with `other`. MEOS Functions: temporal_merge, temporal_merge_array """ + if isinstance(self, type(None)): + return other + if isinstance(other, type(None)): + return self if isinstance(other, Temporal): new_temp = temporal_merge(self._inner, other._inner) elif isinstance(other, list): @@ -399,7 +627,7 @@ def merge(self, other: Union[Temporal[TBase], List[Temporal[TBase]]]) -> TG: raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(new_temp) - def insert(self, other: TG, connect: bool = False) -> TG: + def insert(self, other: TG, connect: bool = True) -> TG: """ Returns a new :class:`Temporal` object equal to `self` with `other` inserted. @@ -415,7 +643,7 @@ def insert(self, other: TG, connect: bool = False) -> TG: return self return Temporal._factory(new_inner) - def update(self, other: TG, connect: bool = False) -> TG: + def update(self, other: TG, connect: bool = True) -> TG: """ Returns a new :class:`Temporal` object equal to `self` updated with `other`. @@ -431,7 +659,7 @@ def update(self, other: TG, connect: bool = False) -> TG: return self return Temporal._factory(new_inner) - def delete(self, other: Time, connect: bool = False) -> TG: + def delete(self, other: Time, connect: bool = True) -> TG: """ Returns a new :class:`Temporal` object equal to `self` with elements at `other` removed. @@ -456,100 +684,43 @@ def delete(self, other: Time, connect: bool = False) -> TG: return self return Temporal._factory(new_inner) - # TODO: Move to proper classes (Sequence[Set] with continuous base type) - def to_linear(self: Self) -> Self: - """ - Returns `self` transformed from stepwise to linear interpolation. - - MEOS Functions: - temporal_step_to_linear - """ - new_temp = temporal_step_to_linear(self._inner) - return Temporal._factory(new_temp) - - def is_after(self, other: Union[Time, Temporal, Box]) -> bool: + # ------------------------- Restrictions ---------------------------------- + def at(self, other: Time) -> TG: """ - Returns whether `self` is after `other`. + Returns a new temporal object with the values of `self` restricted to the time `other`. Args: - other: A time or temporal object to compare `self` to. + other: A time object to restrict the values of `self` to. Returns: - True if `self` is after `other`, False otherwise. + A new temporal object of the same subtype as `self`. - See Also: - :meth:`Period.is_after` + MEOS Functions: + temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset """ - return self.period().is_after(other) + if isinstance(other, datetime): + result = temporal_at_timestamp(self._inner, datetime_to_timestamptz(other)) + elif isinstance(other, TimestampSet): + result = temporal_at_timestampset(self._inner, other._inner) + elif isinstance(other, Period): + result = temporal_at_period(self._inner, other._inner) + elif isinstance(other, PeriodSet): + result = temporal_at_periodset(self._inner, other._inner) + else: + raise TypeError(f'Operation not supported with type {other.__class__}') + return Temporal._factory(result) - def is_before(self, other: Union[Time, Temporal, Box]) -> bool: + def at_min(self) -> TG: """ - Returns whether `self` is before `other`. - - Args: - other: A time or temporal object to compare `self` to. + Returns a new temporal object containing the times ``self`` is at its minimum value. Returns: - True if `self` is before `other`, False otherwise. - - See Also: - :meth:`Period.is_before` - """ - return self.period().is_before(other) - - def is_over_or_after(self, other: Union[Time, Temporal, Box]) -> bool: - """ - Returns whether `self` is after `other` allowing overlap. That is, `self` doesn't extend before `other`. - - Args: - other: A time or temporal object to compare `self` to. - - Returns: - True if `self` is after `other` allowing overlap, False otherwise. - - See Also: - :meth:`Period.is_over_or_after` - """ - return self.period().is_over_or_after(other) - - def is_over_or_before(self, other: Union[Time, Temporal, Box]) -> bool: - """ - Returns whether `self` is before `other` allowing overlap. That is, `self` doesn't extend after `other`. - - Args: - other: A time or temporal object to compare `self` to. - - Returns: - True if `self` is before `other` allowing overlap, False otherwise. - - See Also: - :meth:`Period.is_over_or_before` - """ - return self.period().is_over_or_before(other) - - def at(self, other: Time) -> TG: - """ - Returns a new temporal object with the values of `self` restricted to the time `other`. - - Args: - other: A time object to restrict the values of `self` to. - - Returns: - A new temporal object of the same subtype as `self`. + A new temporal object of the same subtype as `self`. MEOS Functions: - temporal_at_timestamp, temporal_at_timestampset, temporal_at_period, temporal_at_periodset + temporal_at_min """ - if isinstance(other, datetime): - result = temporal_at_timestamp(self._inner, datetime_to_timestamptz(other)) - elif isinstance(other, TimestampSet): - result = temporal_at_timestampset(self._inner, other._inner) - elif isinstance(other, Period): - result = temporal_at_period(self._inner, other._inner) - elif isinstance(other, PeriodSet): - result = temporal_at_periodset(self._inner, other._inner) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') + result = temporal_at_min(self._inner) return Temporal._factory(result) def at_max(self) -> TG: @@ -565,19 +736,6 @@ def at_max(self) -> TG: result = temporal_at_max(self._inner) return Temporal._factory(result) - def at_min(self) -> TG: - """ - Returns a new temporal object containing the times ``self`` is at its minimum value. - - Returns: - A new temporal object of the same subtype as `self`. - - MEOS Functions: - temporal_at_min - """ - result = temporal_at_min(self._inner) - return Temporal._factory(result) - def minus(self, other: Time) -> TG: """ Returns a new temporal object with the values of `self` removing those happening at `other`. @@ -603,32 +761,33 @@ def minus(self, other: Time) -> TG: raise TypeError(f'Operation not supported with type {other.__class__}') return Temporal._factory(result) - def minus_max(self) -> TG: + def minus_min(self) -> TG: """ - Returns a new temporal object containing the times ``self`` is not at its maximum value. + Returns a new temporal object containing the times ``self`` is not at its minimum value. Returns: A new temporal object of the same subtype as `self`. MEOS Functions: - temporal_minus_max + temporal_minus_min """ - result = temporal_minus_max(self._inner) + result = temporal_minus_min(self._inner) return Temporal._factory(result) - def minus_min(self) -> TG: + def minus_max(self) -> TG: """ - Returns a new temporal object containing the times ``self`` is not at its minimum value. + Returns a new temporal object containing the times ``self`` is not at its maximum value. Returns: A new temporal object of the same subtype as `self`. MEOS Functions: - temporal_minus_min + temporal_minus_max """ - result = temporal_minus_min(self._inner) + result = temporal_minus_max(self._inner) return Temporal._factory(result) + # ------------------------- Topological Operations ------------------------ def is_adjacent(self, other: Union[Time, Temporal, Box]) -> bool: """ Returns whether the bounding box of `self` is adjacent to the bounding box of `other`. @@ -644,7 +803,7 @@ def is_adjacent(self, other: Union[Time, Temporal, Box]) -> bool: See Also: :meth:`Period.is_adjacent` """ - return self.period().is_adjacent(other) + return self.bounding_box().is_adjacent(other) def is_temporally_adjacent(self, other: Union[Time, Temporal, Box]) -> bool: """ @@ -675,7 +834,7 @@ def is_contained_in(self, container: Union[Time, Temporal, Box]) -> bool: See Also: :meth:`Period.is_contained_in` """ - return self.period().is_contained_in(container) + return self.bounding_box().is_contained_in(container) def is_temporally_contained_in(self, container: Union[Time, Temporal, Box]) -> bool: """ @@ -706,7 +865,22 @@ def contains(self, content: Union[Time, Temporal, Box]) -> bool: See Also: :meth:`Period.contains` """ - return self.period().contains(content) + return self.bounding_box().contains(content) + + def __contains__(self, item): + """ + Returns whether the bounding period of `self` contains the bounding period of `content`. + + Args: + item: A time or temporal object to compare to `self`. + + Returns: + True if contains, False otherwise. + + See Also: + :meth:`Period.contains` + """ + return self.contains(item) def temporally_contains(self, content: Union[Time, Temporal, Box]) -> bool: """ @@ -737,7 +911,7 @@ def overlaps(self, other: Union[Time, Temporal, Box]) -> bool: See Also: :meth:`Period.overlaps` """ - return self.period().overlaps(other) + return self.bounding_box().overlaps(other) def temporally_overlaps(self, other: Union[Time, Temporal, Box]) -> bool: """ @@ -768,8 +942,70 @@ def is_same(self, other: Union[Time, Temporal, Box]) -> bool: See Also: :meth:`Period.is_same` """ - return self.period().is_same(other) + return self.bounding_box().is_same(other) + + # ------------------------- Position Operations --------------------------- + def is_before(self, other: Union[Time, Temporal, Box]) -> bool: + """ + Returns whether `self` is before `other`. + + Args: + other: A time or temporal object to compare `self` to. + + Returns: + True if `self` is before `other`, False otherwise. + + See Also: + :meth:`Period.is_before` + """ + return self.period().is_before(other) + + def is_over_or_before(self, other: Union[Time, Temporal, Box]) -> bool: + """ + Returns whether `self` is before `other` allowing overlap. That is, `self` doesn't extend after `other`. + + Args: + other: A time or temporal object to compare `self` to. + + Returns: + True if `self` is before `other` allowing overlap, False otherwise. + + See Also: + :meth:`Period.is_over_or_before` + """ + return self.period().is_over_or_before(other) + + def is_after(self, other: Union[Time, Temporal, Box]) -> bool: + """ + Returns whether `self` is after `other`. + + Args: + other: A time or temporal object to compare `self` to. + + Returns: + True if `self` is after `other`, False otherwise. + + See Also: + :meth:`Period.is_after` + """ + return self.period().is_after(other) + + def is_over_or_after(self, other: Union[Time, Temporal, Box]) -> bool: + """ + Returns whether `self` is after `other` allowing overlap. That is, `self` doesn't extend before `other`. + + Args: + other: A time or temporal object to compare `self` to. + + Returns: + True if `self` is after `other` allowing overlap, False otherwise. + + See Also: + :meth:`Period.is_over_or_after` + """ + return self.period().is_over_or_after(other) + # ------------------------- Similarity Operations ------------------------- def frechet_distance(self, other: Temporal) -> float: """ Returns the Frechet distance between `self` and `other`. @@ -815,6 +1051,7 @@ def hausdorff_distance(self, other: Temporal) -> float: """ return temporal_hausdorff_distance(self._inner, other._inner) + # ------------------------- Split Operations ------------------------------ def time_split(self, duration: Union[str, timedelta], start: Optional[Union[str, datetime]] = None) -> List[TG]: """ Returns a list of temporal objects of the same subtype as `self` with the same values as `self` but split in @@ -854,6 +1091,8 @@ def time_split_n(self, n: int) -> List[TG]: MEOS Functions: temporal_time_split """ + if self.end_timestamp() == self.start_timestamp(): + return [self] st = temporal_start_timestamp(self._inner) dt = timedelta_to_interval((self.end_timestamp() - self.start_timestamp()) / n) tiles, new_count = temporal_time_split(self._inner, dt, st) @@ -862,7 +1101,7 @@ def time_split_n(self, n: int) -> List[TG]: def stops(self, max_distance: float, min_duration: timedelta) -> TSS: """ - Return the subsequences where the objects stays within an area with a given maximum size for at least + Return the subsequences where the objects stay within an area with a given maximum size for at least the specified duration. Args: @@ -879,6 +1118,7 @@ def stops(self, max_distance: float, min_duration: timedelta) -> TSS: from ..factory import _TemporalFactory return _TemporalFactory.create_temporal(new_inner) + # ------------------------- Temporal Comparisons -------------------------- def __comparable(self, other: Temporal) -> bool: if not isinstance(other, Temporal): return False @@ -893,72 +1133,72 @@ def __assert_comparable(self, other: Temporal) -> None: raise TypeError(f'Operation not supported with type {other.__class__}. ' f'{self.BaseClass} and {other.BaseClass} are not comparable.') - def temporal_less(self, other: Temporal) -> TBool: + def temporal_equal(self, other: Temporal) -> TBool: """ - Returns the temporal less than relation between `self` and `other`. + Returns the temporal equality relation between `self` and `other`. Args: other: A temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal less than relation. + A :class:`TBool` with the result of the temporal equality relation. MEOS Functions: - tlt_temporal_temporal + teq_temporal_temporal """ self.__assert_comparable(other) - result = tlt_temporal_temporal(self._inner, other._inner) + result = teq_temporal_temporal(self._inner, other._inner) return Temporal._factory(result) - def temporal_less_or_equal(self, other: Temporal) -> TBool: + def temporal_not_equal(self, other: Temporal) -> TBool: """ - Returns the temporal less or equal relation between `self` and `other`. + Returns the temporal not equal relation between `self` and `other`. Args: other: A temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal less or equal relation. + A :class:`TBool` with the result of the temporal not equal relation. MEOS Functions: - tle_temporal_temporal + tne_temporal_temporal """ self.__assert_comparable(other) - result = tle_temporal_temporal(self._inner, other._inner) + result = tne_temporal_temporal(self._inner, other._inner) return Temporal._factory(result) - def temporal_equal(self, other: Temporal) -> TBool: + def temporal_less(self, other: Temporal) -> TBool: """ - Returns the temporal equality relation between `self` and `other`. + Returns the temporal less than relation between `self` and `other`. Args: other: A temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal equality relation. + A :class:`TBool` with the result of the temporal less than relation. MEOS Functions: - teq_temporal_temporal + tlt_temporal_temporal """ self.__assert_comparable(other) - result = teq_temporal_temporal(self._inner, other._inner) + result = tlt_temporal_temporal(self._inner, other._inner) return Temporal._factory(result) - def temporal_not_equal(self, other: Temporal) -> TBool: + def temporal_less_or_equal(self, other: Temporal) -> TBool: """ - Returns the temporal not equal relation between `self` and `other`. + Returns the temporal less or equal relation between `self` and `other`. Args: other: A temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal not equal relation. + A :class:`TBool` with the result of the temporal less or equal relation. MEOS Functions: - tne_temporal_temporal + tle_temporal_temporal """ self.__assert_comparable(other) - result = tne_temporal_temporal(self._inner, other._inner) + result = tle_temporal_temporal(self._inner, other._inner) return Temporal._factory(result) def temporal_greater_or_equal(self, other: Temporal) -> TBool: @@ -995,246 +1235,94 @@ def temporal_greater(self, other: Temporal) -> TBool: result = tgt_temporal_temporal(self._inner, other._inner) return Temporal._factory(result) - def __lt__(self, other): - """ - Returns the temporal less than relation between `self` and `other`. - - Args: - other: A temporal object to compare to `self`. - - Returns: - A :class:`TBool` with the result of the temporal less than relation. - - MEOS Functions: - tlt_temporal_temporal - """ - return self.temporal_less(other) - - def __le__(self, other): - """ - Returns the temporal less or equal relation between `self` and `other`. - - Args: - other: A temporal object to compare to `self`. - - Returns: - A :class:`TBool` with the result of the temporal less or equal relation. - - MEOS Functions: - tle_temporal_temporal - """ - return self.temporal_less_or_equal(other) - + # ------------------------- Comparisons ----------------------------------- def __eq__(self, other): """ - Returns the temporal equality relation between `self` and `other`. + Returns whether `self` is equal to `other`. Args: other: A temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal equality relation. + A :class:`bool` with the result of the equality relation. MEOS Functions: - teq_temporal_temporal + temporal_eq """ - return self.temporal_equal(other) + return temporal_eq(self._inner, other._inner) def __ne__(self, other): """ - Returns the temporal not equal relation between `self` and `other`. + Returns whether `self` is not equal to `other`. Args: other: A temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal not equal relation. + A :class:`bool` with the result of the not equal relation. MEOS Functions: - tne_temporal_temporal + temporal_ne """ - return self.temporal_not_equal(other) + return temporal_ne(self._inner, other._inner) - def __ge__(self, other): + def __lt__(self, other): """ - Returns the temporal greater or equal relation between `self` and `other`. + Returns whether `self` is less than `other`. Args: other: A temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal greater or equal relation. + A :class:`bool` with the result of the less than relation. MEOS Functions: - tge_temporal_temporal + temporal_lt """ - return self.temporal_greater_or_equal(other) + return temporal_lt(self._inner, other._inner) - def __gt__(self, other): + def __le__(self, other): """ - Returns the temporal greater than relation between `self` and `other`. + Returns whether `self` is less or equal than `other`. Args: other: A temporal object to compare to `self`. Returns: - A :class:`TBool` with the result of the temporal greater than relation. - - MEOS Functions: - tgt_temporal_temporal - """ - return self.temporal_greater(other) - - def __hash__(self) -> int: - """ - Returns the hash of the temporal object. - - Returns: - The hash of the temporal object. - - MEOS Functions: - temporal_hash - """ - return temporal_hash(self._inner) - - def __contains__(self, item): - """ - Returns whether the bounding period of `self` contains the bounding period of `content`. - - Args: - item: A time or temporal object to compare to `self`. - - Returns: - True if contains, False otherwise. - - See Also: - :meth:`Period.contains` - """ - return self.contains(item) - - def __str__(self) -> str: - """ - Returns the string representation of the `self`. - """ - pass - - def __repr__(self) -> str: - return (f'{self.__class__.__name__}' - f'({self})') - - def __copy__(self) -> Self: - """ - Returns a copy of the temporal object. - """ - inner_copy = temporal_copy(self._inner) - return self.__class__._factory(inner_copy) - - @abstractmethod - def as_wkt(self) -> str: - """ - Returns the temporal object as a WKT string. - - Returns: - The temporal object as a WKT string. - """ - pass - - def as_mfjson(self, with_bbox: bool = True, flags: int = 3, precision: int = 6, srs: Optional[str] = None) -> str: - """ - Returns the temporal object as a MF-JSON string. - - Args: - with_bbox: Whether to include the bounding box in the output. - flags: The flags to use for the output. - precision: The precision to use for the output. - srs: The SRS to use for the output. - - Returns: - The temporal object as a MF-JSON string. - - MEOS Functions: - temporal_as_mfjson - """ - return temporal_as_mfjson(self._inner, with_bbox, flags, precision, srs) - - def as_hexwkb(self) -> str: - """ - Returns the temporal object as a hex-encoded WKB string. - - Returns: - The temporal object as a hex-encoded WKB string. - - MEOS Functions: - temporal_as_hexwkb - """ - return temporal_as_hexwkb(self._inner, 0)[0] - - @classmethod - def from_merge(cls: Type[Self], *temporals: TG) -> Self: - """ - Returns a temporal object that is the result of merging the given temporal objects. - - Args: - *temporals: The temporal objects to merge. - - Returns: - A temporal object that is the result of merging the given temporal objects. + A :class:`bool` with the result of the less or equal than relation. MEOS Functions: - temporal_merge_array - """ - result = temporal_merge_array([temp._inner for temp in temporals], len(temporals)) - return Temporal._factory(result) - - @classmethod - def from_merge_array(cls: Type[Self], temporals: List[TG]) -> Self: - """ - Returns a temporal object that is the result of merging the given temporal objects. - - Args: - temporals: The temporal objects to merge. - - Returns: - A temporal object that is the result of merging the given temporal objects. + temporal_le """ - result = temporal_merge_array([temp._inner for temp in temporals], len(temporals)) - return Temporal._factory(result) + return temporal_le(self._inner, other._inner) - @classmethod - def from_hexwkb(cls: Type[Self], hexwkb: str) -> Self: + def __gt__(self, other): """ - Returns a temporal object from a hex-encoded WKB string. + Returns whether `self` is greater than `other`. Args: - hexwkb: The hex-encoded WKB string. + other: A temporal object to compare to `self`. Returns: - A temporal object from a hex-encoded WKB string. + A :class:`bool` with the result of the greater than relation. MEOS Functions: - temporal_from_hexwkb + temporal_gt """ - result = temporal_from_hexwkb(hexwkb) - return Temporal._factory(result) + return temporal_gt(self._inner, other._inner) - @classmethod - def from_mfjson(cls: Type[Self], mfjson: str) -> Self: + def __ge__(self, other): """ - Returns a temporal object from a MF-JSON string. + Returns whether `self` is greater or equal than `other`. Args: - mfjson: The MF-JSON string. + other: A temporal object to compare to `self`. Returns: - A temporal object from a MF-JSON string. + A :class:`bool` with the result of the greater or equal than relation. MEOS Functions: - temporal_from_mfjson + temporal_ge """ - result = temporal_from_mfjson(mfjson) - return Temporal._factory(result) + return temporal_ge(self._inner, other._inner) - @classmethod - def _factory(cls, inner): - from ..factory import _TemporalFactory - return _TemporalFactory.create_temporal(inner) diff --git a/pymeos/pymeos/temporal/tinstant.py b/pymeos/pymeos/temporal/tinstant.py index 617b4187..78a5b265 100644 --- a/pymeos/pymeos/temporal/tinstant.py +++ b/pymeos/pymeos/temporal/tinstant.py @@ -8,9 +8,6 @@ from .temporal import Temporal -if TYPE_CHECKING: - pass - TBase = TypeVar('TBase') TG = TypeVar('TG', bound='Temporal[Any]') TI = TypeVar('TI', bound='TInstant[Any]') @@ -28,7 +25,7 @@ class TInstant(Temporal[TBase, TG, TI, TS, TSS], ABC): _make_function = None _cast_function = None - def __init__(self, string: Optional[str] = None, *, value: Optional[Union[str, Any]] = None, + def __init__(self, string: Optional[str] = None, *, value: Optional[Union[str, TBase]] = None, timestamp: Optional[Union[str, datetime]] = None, _inner=None): assert (_inner is not None) or ((string is not None) != (value is not None and timestamp is not None)), \ "Either string must be not None or both point and timestamp must be not" @@ -41,6 +38,15 @@ def __init__(self, string: Optional[str] = None, *, value: Optional[Union[str, A else pg_timestamptz_in(timestamp, -1) self._inner = self.__class__._make_function(self.__class__._cast_function(value), ts) + def value(self) -> TBase: + """ + Returns the value of the temporal instant. + + Returns: + The value of the temporal instant. + """ + return self.start_value() + def timestamp(self) -> datetime: """ Returns the timestamp of the temporal instant. @@ -55,15 +61,6 @@ def timestamp(self) -> datetime: assert count == 1 return timestamptz_to_datetime(ts[0]) - def value(self) -> TBase: - """ - Returns the value of the temporal instant. - - Returns: - The value of the temporal instant. - """ - return self.start_value() - def start_instant(self: Self) -> Self: return self @@ -71,7 +68,7 @@ def end_instant(self: Self) -> Self: return self def instant_n(self: Self, n: int) -> Self: - if n == 1: + if n == 0: return self else: raise Exception("ERROR: Out of range") @@ -86,7 +83,7 @@ def end_timestamp(self) -> datetime: return self.timestamp() def timestamp_n(self, n) -> datetime: - if n == 1: + if n == 0: return self.timestamp() else: raise Exception("ERROR: Out of range") diff --git a/pymeos/pymeos/temporal/tsequence.py b/pymeos/pymeos/temporal/tsequence.py index b13141a9..fd2932be 100644 --- a/pymeos/pymeos/temporal/tsequence.py +++ b/pymeos/pymeos/temporal/tsequence.py @@ -21,6 +21,10 @@ class TSequence(Temporal[TBase, TG, TI, TS, TSS], ABC): Base class for temporal sequence types, i.e. temporal values that are defined over a continuous period of time. """ + def _expandable(self) -> bool: + return self._expandable_sequence + + # ------------------------- Constructors ---------------------------------- def __init__(self, string: Optional[str] = None, *, instant_list: Optional[List[Union[str, Any]]] = None, lower_inc: bool = True, upper_inc: bool = False, expandable: Union[bool, int] = False, interpolation: TInterpolation = TInterpolation.LINEAR, normalize: bool = True, _inner=None): @@ -43,9 +47,6 @@ def __init__(self, string: Optional[str] = None, *, instant_list: Optional[List[ interpolation.value, normalize) self._expandable_sequence = bool(expandable) or self._inner.maxcount > self._inner.count - def _expandable(self) -> bool: - return self._expandable_sequence - @classmethod def from_instants(cls: Type[Self], instant_list: Optional[List[Union[str, Any]]], lower_inc: bool = True, upper_inc: bool = False, @@ -66,6 +67,7 @@ def from_instants(cls: Type[Self], instant_list: Optional[List[Union[str, Any]]] return cls(instant_list=instant_list, lower_inc=lower_inc, upper_inc=upper_inc, interpolation=interpolation, normalize=normalize) + # ------------------------- Accessors ------------------------------------- def lower_inc(self) -> bool: """ Returns whether the lower bound is inclusive. @@ -84,6 +86,7 @@ def upper_inc(self) -> bool: """ return self._inner.period.upper_inc + # ------------------------- Plot Operations ------------------------------- def plot(self, *args, **kwargs): """ Plot the temporal sequence. diff --git a/pymeos/pymeos/temporal/tsequenceset.py b/pymeos/pymeos/temporal/tsequenceset.py index 0ff7c128..96e46719 100644 --- a/pymeos/pymeos/temporal/tsequenceset.py +++ b/pymeos/pymeos/temporal/tsequenceset.py @@ -21,7 +21,13 @@ class TSequenceSet(Temporal[TBase, TG, TI, TS, TSS], ABC): Base class for temporal sequence set types, i.e. temporal values that are defined by a set of temporal sequences. """ - def __init__(self, string: Optional[str] = None, *, sequence_list: Optional[List[Union[str, Any]]] = None, + def _expandable(self) -> bool: + return self._expandable_sequenceset + + # ------------------------- Constructors ---------------------------------- + def __init__(self, string: Optional[str] = None, *, + sequence_list: Optional[List[Union[str, Any]]] = None, + expandable: Union[bool, int] = False, normalize: bool = True, _inner=None): assert (_inner is not None) or ((string is not None) != (sequence_list is not None)), \ "Either string must be not None or sequence_list must be not" @@ -32,7 +38,15 @@ def __init__(self, string: Optional[str] = None, *, sequence_list: Optional[List else: sequences = [x._inner if isinstance(x, self.ComponentClass) else self.__class__._parse_function(x) for x in sequence_list] - self._inner = tsequenceset_make(sequences, len(sequences), normalize) + count = len(sequences) + self._inner = tsequenceset_make(sequences, count, normalize) + if not expandable: + self._inner = tsequenceset_make(sequences, count, normalize) + else: + max_size = max(expandable, count) if isinstance(expandable, int) else 2 * count + self._inner = tsequenceset_make_exp(sequences, count, max_size, + normalize) + self._expandable_sequenceset = bool(expandable) or self._inner.maxcount > self._inner.count @classmethod def from_sequences(cls: Type[Self], sequence_list: Optional[List[Union[str, Any]]] = None, @@ -49,6 +63,7 @@ def from_sequences(cls: Type[Self], sequence_list: Optional[List[Union[str, Any] """ return cls(sequence_list=sequence_list, normalize=normalize) + # ------------------------- Accessors ------------------------------------- def num_sequences(self) -> int: """ Returns the number of sequences in ``self``. @@ -80,6 +95,7 @@ def sequences(self) -> List[TS]: ss, count = temporal_sequences(self._inner) return [self.ComponentClass(_inner=ss[i]) for i in range(count)] + # ------------------------- Transformations ------------------------------- def to_dataframe(self) -> DataFrame: """ Returns a pandas DataFrame representation of ``self``. @@ -92,6 +108,7 @@ def to_dataframe(self) -> DataFrame: } return DataFrame(data).set_index(keys=['sequence', 'time']) + # ------------------------- Plot Operations ------------------------------- def plot(self, *args, **kwargs): """ Plot the temporal sequence set. diff --git a/pymeos/pymeos/time/period.py b/pymeos/pymeos/time/period.py index ce939a29..ecbd64c7 100644 --- a/pymeos/pymeos/time/period.py +++ b/pymeos/pymeos/time/period.py @@ -1,8 +1,7 @@ from __future__ import annotations from datetime import datetime, timedelta -from typing import Optional, Union, overload -from typing import TYPE_CHECKING +from typing import Optional, Union, overload, TYPE_CHECKING, get_args from dateutil.parser import parse from pymeos_cffi import * @@ -36,9 +35,13 @@ class Period: __slots__ = ['_inner'] - def __init__(self, string: Optional[str] = None, *, lower: Optional[Union[str, datetime]] = None, + # ------------------------- Constructors ---------------------------------- + def __init__(self, string: Optional[str] = None, *, + lower: Optional[Union[str, datetime]] = None, upper: Optional[Union[str, datetime]] = None, - lower_inc: bool = True, upper_inc: bool = False, _inner=None): + lower_inc: bool = True, + upper_inc: bool = False, + _inner=None): super().__init__() assert (_inner is not None) or ((string is not None) != (lower is not None and upper is not None)), \ "Either string must be not None or both lower and upper must be not" @@ -49,7 +52,37 @@ def __init__(self, string: Optional[str] = None, *, lower: Optional[Union[str, d else: lower_ts = pg_timestamptz_in(lower, -1) if isinstance(lower, str) else datetime_to_timestamptz(lower) upper_ts = pg_timestamptz_in(upper, -1) if isinstance(upper, str) else datetime_to_timestamptz(upper) - self._inner = tstzspan_make(lower_ts, upper_ts, lower_inc, upper_inc) + self._inner = period_make(lower_ts, upper_ts, lower_inc, upper_inc) + + def __copy__(self): + """ + Return a copy of ``self``. + + Returns: + A new :class:`Period` instance + + MEOS Functions: + span_copy + """ + inner_copy = span_copy(self._inner) + return Period(_inner=inner_copy) + + @staticmethod + def from_wkb(wkb: bytes) -> Period: + """ + Returns a `Period` from its WKB representation. + + Args: + wkb: The WKB string. + + Returns: + A new :class:`Period` instance + + MEOS Functions: + span_from_wkb + """ + result = span_from_wkb(wkb) + return Period(_inner=result) @staticmethod def from_hexwkb(hexwkb: str) -> Period: @@ -68,6 +101,44 @@ def from_hexwkb(hexwkb: str) -> Period: result = span_from_hexwkb(hexwkb) return Period(_inner=result) + # ------------------------- Output ---------------------------------------- + def __str__(self): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + period_out + """ + return period_out(self._inner) + + def __repr__(self): + """ + Return the string representation of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + period_out + """ + return (f'{self.__class__.__name__}' + f'({self})') + + def as_wkb(self) -> bytes: + """ + Returns the WKB representation of ``self``. + + Returns: + A :class:`str` object with the WKB representation of ``self``. + + MEOS Functions: + span_as_wkb + """ + return span_as_wkb(self._inner, 4) + def as_hexwkb(self) -> str: """ Returns the WKB representation of ``self`` in hex-encoded ASCII. @@ -80,6 +151,21 @@ def as_hexwkb(self) -> str: """ return span_as_hexwkb(self._inner, -1)[0] + # ------------------------- Conversions ----------------------------------- + def to_periodset(self) -> PeriodSet: + """ + Returns a period set containing ``self``. + + Returns: + A new :class:`PeriodSet` instance + + MEOS Functions: + span_to_spanset + """ + from .periodset import PeriodSet + return PeriodSet(_inner=span_to_spanset(self._inner)) + + # ------------------------- Accessors ------------------------------------- def lower(self) -> datetime: """ Returns the lower bound of a period @@ -152,6 +238,19 @@ def duration_in_seconds(self) -> float: """ return span_width(self._inner) + def __hash__(self) -> int: + """ + Return the hash representation of ``self``. + + Returns: + A new :class:`int` instance + + MEOS Functions: + span_hash + """ + return span_hash(self._inner) + + # ------------------------- Transformations ------------------------------- def shift(self, delta: timedelta) -> Period: """ Returns a new period that is the result of shifting ``self`` by ``delta`` @@ -209,14 +308,12 @@ def shift_tscale(self, shift: Optional[timedelta] = None, duration: Optional[tim period_shift_tscale """ assert shift is not None or duration is not None, 'shift and scale deltas must not be both None' - shifted = span_copy(self._inner) - period_shift_tscale( - shifted, + modified = period_shift_tscale( + self._inner, timedelta_to_interval(shift) if shift else None, timedelta_to_interval(duration) if duration else None, - None, None ) - return Period(_inner=shifted) + return Period(_inner=modified) def expand(self, other: Period) -> Period: """ @@ -239,20 +336,8 @@ def expand(self, other: Period) -> Period: span_expand(other._inner, copy) return Period(_inner=copy) - def to_periodset(self) -> PeriodSet: - """ - Returns a period set containing ``self``. - - Returns: - A new :class:`PeriodSet` instance - - MEOS Functions: - span_to_spanset - """ - from .periodset import PeriodSet - return PeriodSet(_inner=span_to_spanset(self._inner)) - - def is_adjacent(self, other: Union[Time, Temporal, Box]) -> bool: + # ------------------------- Topological Operations ------------------------ + def is_adjacent(self, other: Union[Time, Box, Temporal]) -> bool: """ Returns whether ``self`` is temporally adjacent to ``other``. That is, they share a bound but only one of them contains it. @@ -286,15 +371,15 @@ def is_adjacent(self, other: Union[Time, Temporal, Box]) -> bool: elif isinstance(other, datetime): return adjacent_period_timestamp(self._inner, datetime_to_timestamptz(other)) elif isinstance(other, TimestampSet): - return adjacent_span_span(self._inner, other._inner.span) + return adjacent_span_span(self._inner, set_span(other._inner)) elif isinstance(other, Temporal): return adjacent_span_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, Box): + elif isinstance(other, get_args(Box)): return adjacent_span_span(self._inner, other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_contained_in(self, container: Union[Period, PeriodSet, Temporal, Box]) -> bool: + def is_contained_in(self, container: Union[Period, PeriodSet, Box, Temporal]) -> bool: """ Returns whether ``self`` is temporally contained in ``container``. @@ -329,7 +414,7 @@ def is_contained_in(self, container: Union[Period, PeriodSet, Temporal, Box]) -> else: raise TypeError(f'Operation not supported with type {container.__class__}') - def contains(self, content: Union[Time, Temporal, Box]) -> bool: + def contains(self, content: Union[Time, Box, Temporal]) -> bool: """ Returns whether ``self`` temporally contains ``content``. @@ -362,15 +447,39 @@ def contains(self, content: Union[Time, Temporal, Box]) -> bool: elif isinstance(content, datetime): return contains_period_timestamp(self._inner, datetime_to_timestamptz(content)) elif isinstance(content, TimestampSet): - return contains_span_span(self._inner, content._inner.span) + return contains_span_span(self._inner, set_span(content._inner)) elif isinstance(content, Temporal): return contains_span_span(self._inner, temporal_to_period(content._inner)) - elif isinstance(content, Box): + elif isinstance(content, get_args(Box)): return contains_span_span(self._inner, content.to_period()._inner) else: raise TypeError(f'Operation not supported with type {content.__class__}') - def overlaps(self, other: Union[Period, PeriodSet, TimestampSet, Temporal, Box]) -> bool: + def __contains__(self, item): + """ + Return whether ``self`` temporally contains ``item``. + + Examples: + >>> Period('[2012-01-02, 2012-01-03]') in Period('[2012-01-01, 2012-01-04]') + >>> True + >>> Period('(2012-01-01, 2012-01-02)') in Period('[2012-01-01, 2012-01-02]') + >>> True + >>> Period('[2012-01-01, 2012-01-02]') in Period('(2012-01-01, 2012-01-02)') + >>> False + + Args: + item: temporal object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_span_span, contains_span_spanset, contains_period_timestamp, + contains_period_timestampset, contains_period_temporal + """ + return self.contains(item) + + def overlaps(self, other: Union[Time, Box, Temporal]) -> bool: """ Returns whether ``self`` temporally overlaps ``other``. That is, both share at least an instant @@ -400,56 +509,51 @@ def overlaps(self, other: Union[Period, PeriodSet, TimestampSet, Temporal, Box]) return overlaps_span_span(self._inner, other._inner) elif isinstance(other, PeriodSet): return overlaps_spanset_span(other._inner, self._inner) + elif isinstance(other, datetime): + return overlaps_span_span(self._inner, timestamp_to_period(datetime_to_timestamptz(other))) elif isinstance(other, TimestampSet): - return overlaps_span_span(self._inner, other._inner.span) + return overlaps_span_span(self._inner, set_span(other._inner)) elif isinstance(other, Temporal): return overlaps_span_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, Box): + elif isinstance(other, get_args(Box)): return overlaps_span_span(self._inner, other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_after(self, other: Union[Time, Temporal, Box]) -> bool: + def is_same(self, other: Union[Time, Box, Temporal]) -> bool: """ - Returns whether ``self`` is strictly after ``other``. That is, ``self`` starts after ``other`` ends. - - Examples: - >>> Period('[2012-01-02, 2012-01-03]').is_after(Period('[2012-01-01, 2012-01-02)')) - >>> True - >>> Period('(2012-01-02, 2012-01-03]').is_after(Period('[2012-01-01, 2012-01-02)')) - >>> True - >>> Period('[2012-01-02, 2012-01-03]').is_after(Period('[2012-01-01, 2012-01-02]')) - >>> False + Returns whether ``self`` and the bounding period of ``other`` is the same. Args: other: temporal object to compare with Returns: - True if after, False otherwise + True if equal, False otherwise MEOS Functions: - right_span_span, right_span_spanset, after_period_timestamp, - after_period_timestampset, after_period_temporal + same_period_temporal """ from .periodset import PeriodSet from .timestampset import TimestampSet + from ..temporal import Temporal from ..boxes import Box - if isinstance(other, Period): - return right_span_span(self._inner, other._inner) + if isinstance(other, Temporal): + return span_eq(self._inner, temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return span_eq(self._inner, other.to_period()._inner) + elif isinstance(other, Period): + return span_eq(self._inner, other._inner) elif isinstance(other, PeriodSet): - return right_span_spanset(self._inner, other._inner) + return span_eq(self._inner, spanset_span(other._inner)) elif isinstance(other, datetime): - return overbefore_timestamp_period(datetime_to_timestamptz(other), self._inner) - if isinstance(other, TimestampSet): - return right_span_span(self._inner, other._inner.span) - elif isinstance(other, Temporal): - return right_span_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, Box): - return right_span_span(self._inner, other.to_period()._inner) + return span_eq(self._inner, timestamp_to_period(datetime_to_timestamptz(other))) + elif isinstance(other, TimestampSet): + return span_eq(self._inner, set_span(other._inner)) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_before(self, other: Union[Time, Temporal, Box]) -> bool: + # ------------------------- Position Operations --------------------------- + def is_before(self, other: Union[Time, Box, Temporal]) -> bool: """ Returns whether ``self`` is strictly before ``other``. That is, ``self`` ends before ``other`` starts. @@ -473,6 +577,7 @@ def is_before(self, other: Union[Time, Temporal, Box]) -> bool: """ from .periodset import PeriodSet from .timestampset import TimestampSet + from ..temporal import Temporal from ..boxes import Box if isinstance(other, Period): return left_span_span(self._inner, other._inner) @@ -481,116 +586,141 @@ def is_before(self, other: Union[Time, Temporal, Box]) -> bool: elif isinstance(other, datetime): return overafter_timestamp_period(datetime_to_timestamptz(other), self._inner) if isinstance(other, TimestampSet): - return left_span_span(self._inner, other._inner.span) + return left_span_span(self._inner, set_span(other._inner)) elif isinstance(other, Temporal): return left_span_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, Box): + elif isinstance(other, get_args(Box)): return left_span_span(self._inner, other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_over_or_after(self, other: Union[Time, Temporal, Box]) -> bool: + def is_over_or_before(self, other: Union[Time, Box, Temporal]) -> bool: """ - Returns whether ``self`` is after ``other`` allowing overlap. That is, ``self`` starts after ``other`` starts - (or at the same time). + Returns whether ``self`` is before ``other`` allowing overlap. That is, ``self`` ends before ``other`` ends (or + at the same time). Examples: - >>> Period('[2012-01-02, 2012-01-03]').is_over_or_after(Period('[2012-01-01, 2012-01-02)')) + >>> Period('[2012-01-01, 2012-01-02)').is_over_or_before(Period('[2012-01-02, 2012-01-03]')) >>> True - >>> Period('[2012-01-02, 2012-01-03]').is_over_or_after(Period('[2012-01-01, 2012-01-02]')) + >>> Period('[2012-01-01, 2012-01-02]').is_over_or_before(Period('[2012-01-02, 2012-01-03]')) >>> True - >>> Period('[2012-01-02, 2012-01-03]').is_over_or_after(Period('[2012-01-01, 2012-01-03]')) + >>> Period('[2012-01-03, 2012-01-05]').is_over_or_before(Period('[2012-01-01, 2012-01-04]')) >>> False Args: other: temporal object to compare with Returns: - True if overlapping or after, False otherwise + True if before, False otherwise MEOS Functions: - overright_span_span, overright_span_spanset, overafter_period_timestamp, - overafter_period_timestampset, overafter_period_temporal + overleft_span_span, overleft_span_spanset, overbefore_period_timestamp, + overbefore_period_timestampset, overbefore_period_temporal """ from .periodset import PeriodSet from .timestampset import TimestampSet + from ..temporal import Temporal from ..boxes import Box if isinstance(other, Period): - return overright_span_span(self._inner, other._inner) + return overleft_span_span(self._inner, other._inner) elif isinstance(other, PeriodSet): - return overright_span_spanset(self._inner, other._inner) + return overleft_span_spanset(self._inner, other._inner) elif isinstance(other, datetime): - return overafter_period_timestamp(self._inner, datetime_to_timestamptz(other)) + return overbefore_period_timestamp(self._inner, datetime_to_timestamptz(other)) if isinstance(other, TimestampSet): - return overright_span_span(self._inner, other._inner.span) + return overleft_span_span(self._inner, set_span(other._inner)) elif isinstance(other, Temporal): - return overright_span_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, Box): - return overright_span_span(self._inner, other.to_period()._inner) + return overleft_span_span(self._inner, temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return overleft_span_span(self._inner, other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_over_or_before(self, other: Union[Time, Temporal, Box]) -> bool: + def is_after(self, other: Union[Time, Box, Temporal]) -> bool: """ - Returns whether ``self`` is before ``other`` allowing overlap. That is, ``self`` ends before ``other`` ends (or - at the same time). + Returns whether ``self`` is strictly after ``other``. That is, ``self`` starts after ``other`` ends. Examples: - >>> Period('[2012-01-01, 2012-01-02)').is_over_or_before(Period('[2012-01-02, 2012-01-03]')) + >>> Period('[2012-01-02, 2012-01-03]').is_after(Period('[2012-01-01, 2012-01-02)')) >>> True - >>> Period('[2012-01-01, 2012-01-02]').is_over_or_before(Period('[2012-01-02, 2012-01-03]')) + >>> Period('(2012-01-02, 2012-01-03]').is_after(Period('[2012-01-01, 2012-01-02)')) >>> True - >>> Period('[2012-01-03, 2012-01-05]').is_over_or_before(Period('[2012-01-01, 2012-01-04]')) + >>> Period('[2012-01-02, 2012-01-03]').is_after(Period('[2012-01-01, 2012-01-02]')) >>> False Args: other: temporal object to compare with Returns: - True if before, False otherwise + True if after, False otherwise MEOS Functions: - overleft_span_span, overleft_span_spanset, overbefore_period_timestamp, - overbefore_period_timestampset, overbefore_period_temporal + right_span_span, right_span_spanset, after_period_timestamp, + after_period_timestampset, after_period_temporal """ from .periodset import PeriodSet from .timestampset import TimestampSet + from ..temporal import Temporal from ..boxes import Box if isinstance(other, Period): - return overleft_span_span(self._inner, other._inner) + return right_span_span(self._inner, other._inner) elif isinstance(other, PeriodSet): - return overleft_span_spanset(self._inner, other._inner) + return right_span_spanset(self._inner, other._inner) elif isinstance(other, datetime): - return overbefore_period_timestamp(self._inner, datetime_to_timestamptz(other)) + return overbefore_timestamp_period(datetime_to_timestamptz(other), self._inner) if isinstance(other, TimestampSet): - return overleft_span_span(self._inner, other._inner.span) + return right_span_span(self._inner, set_span(other._inner)) elif isinstance(other, Temporal): - return overleft_span_span(self._inner, temporal_to_period(other._inner)) - elif isinstance(other, Box): - return overleft_span_span(self._inner, other.to_period()._inner) + return right_span_span(self._inner, temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return right_span_span(self._inner, other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_same(self, other: Temporal) -> bool: + def is_over_or_after(self, other: Union[Time, Box, Temporal]) -> bool: """ - Returns whether ``self`` and the bounding period of ``other`` is the same. + Returns whether ``self`` is after ``other`` allowing overlap. That is, ``self`` starts after ``other`` starts + (or at the same time). + + Examples: + >>> Period('[2012-01-02, 2012-01-03]').is_over_or_after(Period('[2012-01-01, 2012-01-02)')) + >>> True + >>> Period('[2012-01-02, 2012-01-03]').is_over_or_after(Period('[2012-01-01, 2012-01-02]')) + >>> True + >>> Period('[2012-01-02, 2012-01-03]').is_over_or_after(Period('[2012-01-01, 2012-01-03]')) + >>> False Args: other: temporal object to compare with Returns: - True if equal, False otherwise + True if overlapping or after, False otherwise MEOS Functions: - same_period_temporal + overright_span_span, overright_span_spanset, overafter_period_timestamp, + overafter_period_timestampset, overafter_period_temporal """ + from .periodset import PeriodSet + from .timestampset import TimestampSet from ..temporal import Temporal - if isinstance(other, Temporal): - return span_eq(self._inner, temporal_to_period(other._inner)) + from ..boxes import Box + if isinstance(other, Period): + return overright_span_span(self._inner, other._inner) + elif isinstance(other, PeriodSet): + return overright_span_spanset(self._inner, other._inner) + elif isinstance(other, datetime): + return overafter_period_timestamp(self._inner, datetime_to_timestamptz(other)) + if isinstance(other, TimestampSet): + return overright_span_span(self._inner, set_span(other._inner)) + elif isinstance(other, Temporal): + return overright_span_span(self._inner, temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return overright_span_span(self._inner, other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def distance(self, other: Union[Time, Box]) -> timedelta: + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[Time, Box, Temporal]) -> timedelta: """ Returns the temporal distance between ``self`` and ``other``. @@ -605,33 +735,37 @@ def distance(self, other: Union[Time, Box]) -> timedelta: """ from .periodset import PeriodSet from .timestampset import TimestampSet + from ..temporal import Temporal from ..boxes import Box - if isinstance(other, Period): + if isinstance(other, Temporal): + return timedelta(seconds=distance_span_span(self._inner, temporal_to_period(other._inner))) + elif isinstance(other, Period): return timedelta(seconds=distance_span_span(self._inner, other._inner)) elif isinstance(other, PeriodSet): return timedelta(seconds=distance_spanset_span(other._inner, self._inner)) elif isinstance(other, datetime): return timedelta(seconds=distance_period_timestamp(self._inner, datetime_to_timestamptz(other))) elif isinstance(other, TimestampSet): - return timedelta(seconds=distance_span_span(self._inner, other._inner.span)) - elif isinstance(other, Box): + return timedelta(seconds=distance_span_span(self._inner, set_span(other._inner))) + elif isinstance(other, get_args(Box)): return timedelta(seconds=distance_span_span(self._inner, other.to_period()._inner)) else: raise TypeError(f'Operation not supported with type {other.__class__}') - + # ------------------------- Set Operations -------------------------------- @overload - def intersection(self, other: datetime) -> datetime: + def intersection(self, other: datetime) -> Optional[datetime]: ... + @overload - def intersection(self, other: Period) -> Period: + def intersection(self, other: Period) -> Optional[Period]: ... @overload - def intersection(self, other: Union[TimestampSet, PeriodSet]) -> PeriodSet: + def intersection(self, other: Union[TimestampSet, PeriodSet]) -> Optional[PeriodSet]: ... - def intersection(self, other: Time) -> Time: + def intersection(self, other: Time) -> Optional[Time]: """ Returns the temporal intersection of ``self`` and ``other``. @@ -647,16 +781,35 @@ def intersection(self, other: Time) -> Time: from .periodset import PeriodSet from .timestampset import TimestampSet if isinstance(other, datetime): - return timestamptz_to_datetime(intersection_period_timestamp(self._inner, datetime_to_timestamptz(other))) + result = intersection_period_timestamp(self._inner, datetime_to_timestamptz(other)) + return timestamptz_to_datetime(result) if result is not None else None elif isinstance(other, TimestampSet): - return TimestampSet(_inner=intersection_spanset_span(set_to_spanset(other._inner), self._inner)) + result = intersection_spanset_span(set_to_spanset(other._inner), self._inner) + return TimestampSet(_inner=result) if result is not None else None elif isinstance(other, Period): - return Period(_inner=intersection_span_span(self._inner, other._inner)) + result = intersection_span_span(self._inner, other._inner) + return Period(_inner=result) if result is not None else None elif isinstance(other, PeriodSet): - return PeriodSet(_inner=intersection_spanset_span(other._inner, self._inner)) + result = intersection_spanset_span(other._inner, self._inner) + return PeriodSet(_inner=result) if result is not None else None else: raise TypeError(f'Operation not supported with type {other.__class__}') + def __mul__(self, other): + """ + Returns the temporal intersection of ``self`` and ``other``. + + Args: + other: temporal object to intersect with + + Returns: + A :class:`Time` instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_span_span, intersection_spanset_span, intersection_period_timestamp + """ + return self.intersection(other) + def minus(self, other: Time) -> PeriodSet: """ Returns the temporal difference of ``self`` and ``other``. @@ -673,16 +826,35 @@ def minus(self, other: Time) -> PeriodSet: from .periodset import PeriodSet from .timestampset import TimestampSet if isinstance(other, datetime): - return PeriodSet(_inner=minus_period_timestamp(self._inner, datetime_to_timestamptz(other))) + result = minus_period_timestamp(self._inner, datetime_to_timestamptz(other)) + return PeriodSet(_inner=result) if result is not None else None elif isinstance(other, TimestampSet): - return PeriodSet(_inner=minus_span_spanset(self._inner, set_to_spanset(other._inner))) + result = minus_span_spanset(self._inner, set_to_spanset(other._inner)) + return PeriodSet(_inner=result) if result is not None else None elif isinstance(other, Period): - return PeriodSet(_inner=minus_span_span(self._inner, other._inner)) + result = minus_span_span(self._inner, other._inner) + return PeriodSet(_inner=result) if result is not None else None elif isinstance(other, PeriodSet): - return PeriodSet(_inner=minus_span_spanset(self._inner, other._inner)) + result = minus_span_spanset(self._inner, other._inner) + return PeriodSet(_inner=result) if result is not None else None else: raise TypeError(f'Operation not supported with type {other.__class__}') + def __sub__(self, other): + """ + Returns the temporal difference of ``self`` and ``other``. + + Args: + other: temporal object to diff with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + minus_period_timestamp, minus_span_spanset, minus_span_span + """ + return self.minus(other) + def union(self, other: Time) -> PeriodSet: """ Returns the temporal union of ``self`` and ``other``. @@ -709,21 +881,6 @@ def union(self, other: Time) -> PeriodSet: else: raise TypeError(f'Operation not supported with type {other.__class__}') - def __mul__(self, other): - """ - Returns the temporal intersection of ``self`` and ``other``. - - Args: - other: temporal object to intersect with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - intersection_span_span, intersection_spanset_span, intersection_period_timestamp - """ - return self.intersection(other) - def __add__(self, other): """ Returns the temporal union of ``self`` and ``other``. @@ -739,45 +896,7 @@ def __add__(self, other): """ return self.union(other) - def __sub__(self, other): - """ - Returns the temporal difference of ``self`` and ``other``. - - Args: - other: temporal object to diff with - - Returns: - A :class:`PeriodSet` instance. - - MEOS Functions: - minus_period_timestamp, minus_span_spanset, minus_span_span - """ - return self.minus(other) - - def __contains__(self, item): - """ - Return whether ``self`` temporally contains ``item``. - - Examples: - >>> Period('[2012-01-02, 2012-01-03]') in Period('[2012-01-01, 2012-01-04]') - >>> True - >>> Period('(2012-01-01, 2012-01-02)') in Period('[2012-01-01, 2012-01-02]') - >>> True - >>> Period('[2012-01-01, 2012-01-02]') in Period('(2012-01-01, 2012-01-02)') - >>> False - - Args: - item: temporal object to compare with - - Returns: - True if contains, False otherwise - - MEOS Functions: - contains_span_span, contains_span_spanset, contains_period_timestamp, - contains_period_timestampset, contains_period_temporal - """ - return self.contains(item) - + # ------------------------- Comparisons ----------------------------------- def __eq__(self, other): """ Return whether ``self`` and ``other`` are equal. @@ -812,23 +931,6 @@ def __ne__(self, other): return span_ne(self._inner, other._inner) return True - def __cmp__(self, other): - """ - Return the result of comparing ``self`` and ``other``. - - Args: - other: temporal object to compare with - - Returns: - -1 if less than, 0 if equal, and 1 if greater than - - MEOS Functions: - span_cmp - """ - if isinstance(other, self.__class__): - return span_cmp(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - def __lt__(self, other): """ Return whether ``self`` is less than ``other``. @@ -897,6 +999,12 @@ def __ge__(self, other): return span_ge(self._inner, other._inner) raise TypeError(f'Operation not supported with type {other.__class__}') + # ------------------------- Plot Operations ------------------------------- + def plot(self, *args, **kwargs): + from ..plotters import TimePlotter + return TimePlotter.plot_period(self, *args, **kwargs) + + # ------------------------- Database Operations --------------------------- @staticmethod def read_from_cursor(value, _=None): """ @@ -907,56 +1015,3 @@ def read_from_cursor(value, _=None): return None return Period(string=value) - def __copy__(self): - """ - Return a copy of ``self``. - - Returns: - A new :class:`Period` instance - - MEOS Functions: - span_copy - """ - inner_copy = span_copy(self._inner) - return Period(_inner=inner_copy) - - def __str__(self): - """ - Return the string representation of the content of ``self``. - - Returns: - A new :class:`str` instance - - MEOS Functions: - period_out - """ - return period_out(self._inner) - - def __hash__(self) -> int: - """ - Return the hash representation of ``self``. - - Returns: - A new :class:`int` instance - - MEOS Functions: - span_hash - """ - return span_hash(self._inner) - - def __repr__(self): - """ - Return the string representation of ``self``. - - Returns: - A new :class:`str` instance - - MEOS Functions: - period_out - """ - return (f'{self.__class__.__name__}' - f'({self})') - - def plot(self, *args, **kwargs): - from ..plotters import TimePlotter - return TimePlotter.plot_period(self, *args, **kwargs) diff --git a/pymeos/pymeos/time/periodset.py b/pymeos/pymeos/time/periodset.py index 1ca7ba4f..3101d6ff 100644 --- a/pymeos/pymeos/time/periodset.py +++ b/pymeos/pymeos/time/periodset.py @@ -1,13 +1,14 @@ from __future__ import annotations from datetime import timedelta, datetime -from typing import Optional, Union, List, overload +from typing import Optional, Union, List, overload, get_args from typing import TYPE_CHECKING from pymeos_cffi import * if TYPE_CHECKING: from ..temporal import Temporal + from ..boxes import Box from .period import Period from .timestampset import TimestampSet from .time import Time @@ -33,6 +34,7 @@ class PeriodSet: __slots__ = ['_inner'] + # ------------------------- Constructors ---------------------------------- def __init__(self, string: Optional[str] = None, *, period_list: Optional[List[Union[str, Period]]] = None, normalize: bool = True, _inner=None): super().__init__() @@ -43,8 +45,38 @@ def __init__(self, string: Optional[str] = None, *, period_list: Optional[List[U elif string is not None: self._inner = periodset_in(string) else: - periods = [period_in(period) if isinstance(period, str) else period._inner for period in period_list] - self._inner = spanset_make(periods, len(periods), normalize) + periods = [period_in(period)[0] if isinstance(period, str) else period._inner[0] for period in period_list] + self._inner = spanset_make(periods, normalize) + + def __copy__(self): + """ + Return a copy of ``self``. + + Returns: + A new :class:`Period` instance + + MEOS Functions: + spanset_copy + """ + inner_copy = spanset_copy(self._inner) + return PeriodSet(_inner=inner_copy) + + @staticmethod + def from_wkb(wkb: bytes) -> Period: + """ + Returns a `PeriodSet` from its WKB representation. + + Args: + wkb: The WKB string. + + Returns: + A new :class:`PeriodSet` instance + + MEOS Functions: + spanset_from_wkb + """ + result = spanset_from_wkb(wkb) + return PeriodSet(_inner=result) @staticmethod def from_hexwkb(hexwkb: str) -> PeriodSet: @@ -62,43 +94,56 @@ def from_hexwkb(hexwkb: str) -> PeriodSet: result = spanset_from_hexwkb(hexwkb) return PeriodSet(_inner=result) - def as_hexwkb(self) -> str: + # ------------------------- Output ---------------------------------------- + def __str__(self): """ - Returns the WKB representation of ``self`` in hex-encoded ASCII. + Return the string representation of the content of ``self``. + Returns: - A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. + A new :class:`str` instance MEOS Functions: - spanset_as_hexwkb + periodset_out """ - return spanset_as_hexwkb(self._inner, -1)[0] + return periodset_out(self._inner) - def duration(self) -> timedelta: + def __repr__(self): """ - Returns the duration of the periodset taking into account the gaps within, - i.e. the sum of the durations of the periods within. + Return the string representation of ``self``. Returns: - A :class:`datetime.timedelta` instance representing the duration of the periodset + A new :class:`str` instance MEOS Functions: - periodset_duration + periodset_out """ - return interval_to_timedelta(periodset_duration(self._inner, False)) + return (f'{self.__class__.__name__}' + f'({self})') - def timespan(self) -> timedelta: + def as_wkb(self) -> bytes: """ - Returns the duration of the periodset ignoring any gap, i.e. the duration from the - lower bound of the first period to the upper bound of the last period. + Returns the WKB representation of ``self``. Returns: - A :class:`datetime.timedelta` instance representing the duration of the periodset + A :class:`str` object with the WKB representation of ``self``. MEOS Functions: - periodset_duration + spanset_as_wkb + """ + return spanset_as_wkb(self._inner, 4) + + def as_hexwkb(self) -> str: + """ + Returns the WKB representation of ``self`` in hex-encoded ASCII. + Returns: + A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. + + MEOS Functions: + spanset_as_hexwkb """ - return interval_to_timedelta(periodset_duration(self._inner, True)) + return spanset_as_hexwkb(self._inner, -1)[0] + # ------------------------- Conversions ----------------------------------- def to_period(self) -> Period: """ Returns a period that encompasses ``self``. @@ -107,10 +152,31 @@ def to_period(self) -> Period: A new :class:`Period` instance MEOS Functions: - spanset_to_span + spanset_span """ from .period import Period - return Period(_inner=spanset_to_span(self._inner)) + return Period(_inner=spanset_span(self._inner)) + + # ------------------------- Accessors ------------------------------------- + def duration(self, ignore_gaps: Optional[bool] = False) -> timedelta: + """ + Returns the duration of the periodset. By default, i.e., when the + second argument is False, the function takes into account the gaps within, + i.e., returns the sum of the durations of the periods within. + Otherwise, the function returns the duration of the periodset ignoring + any gap, i.e., the duration from the lower bound of the first period to + the upper bound of the last period. + + Parameters: + ignore_gaps: Whether to take into account potential time gaps in the periodset. + + Returns: + A :class:`datetime.timedelta` instance representing the duration of the periodset + + MEOS Functions: + periodset_duration + """ + return interval_to_timedelta(periodset_duration(self._inner, ignore_gaps)) def num_timestamps(self) -> int: """ @@ -154,7 +220,10 @@ def timestamp_n(self, n: int) -> datetime: MEOS Functions: periodset_timestamp_n """ - return timestamptz_to_datetime(periodset_timestamp_n(self._inner, n + 1)) + result = periodset_timestamp_n(self._inner, n + 1) + if result is None: + raise IndexError(f"Index {n} out of range 0 - {self.num_timestamps() - 1}") + return timestamptz_to_datetime(result) def timestamps(self) -> List[datetime]: """ @@ -189,7 +258,7 @@ def start_period(self) -> Period: periodset_lower """ from .period import Period - return Period(_inner=periodset_lower(self._inner)) + return Period(_inner=spanset_start_span(self._inner)) def end_period(self) -> Period: """ @@ -201,7 +270,7 @@ def end_period(self) -> Period: periodset_upper """ from .period import Period - return Period(_inner=periodset_upper(self._inner)) + return Period(_inner=spanset_end_span(self._inner)) def period_n(self, n: int) -> Period: """ @@ -213,7 +282,11 @@ def period_n(self, n: int) -> Period: spanset_span_n """ from .period import Period - return Period(_inner=spanset_span_n(self._inner, n)) + + result = spanset_span_n(self._inner, n + 1) + if result is None: + raise IndexError(f"Index {n} out of range 0 - {self.num_periods() - 1}") + return Period(_inner=result) def periods(self) -> List[Period]: """ @@ -225,9 +298,22 @@ def periods(self) -> List[Period]: spanset_spans """ from .period import Period - ps, count = spanset_spans(self._inner) - return [Period(_inner=ps[i]) for i in range(count)] + ps = spanset_spans(self._inner) + return [Period(_inner=ps[i]) for i in range(self.num_periods())] + + def __hash__(self) -> int: + """ + Return the hash representation of ``self``. + Returns: + A new :class:`int` instance + + MEOS Functions: + spanset_hash + """ + return spanset_hash(self._inner) + + # ------------------------- Transformations ------------------------------- def shift(self, delta: timedelta) -> PeriodSet: """ Returns a new periodset that is the result of shifting ``self`` by ``delta`` @@ -292,7 +378,8 @@ def shift_tscale(self, shift: Optional[timedelta] = None, duration: Optional[tim ) return PeriodSet(_inner=ps) - def is_adjacent(self, other: Union[Time, Temporal]) -> bool: + # ------------------------- Topological Operations ------------------------ + def is_adjacent(self, other: Union[Time, Box, Temporal]) -> bool: """ Returns whether ``self`` is temporally adjacent to ``other``. That is, they share a bound but only one of them contains it. @@ -313,11 +400,12 @@ def is_adjacent(self, other: Union[Time, Temporal]) -> bool: MEOS Functions: adjacent_spanset_span, adjacent_spanset_spanset, adjacent_periodset_timestamp, - adjacent_periodset_timestampset, adjacent_periodset_temporal + adjacent_periodset_timestampset """ from .period import Period from .timestampset import TimestampSet from ..temporal import Temporal + from ..boxes import Box if isinstance(other, Period): return adjacent_spanset_span(self._inner, other._inner) if isinstance(other, PeriodSet): @@ -328,10 +416,12 @@ def is_adjacent(self, other: Union[Time, Temporal]) -> bool: return adjacent_spanset_spanset(self._inner, set_to_spanset(other._inner)) elif isinstance(other, Temporal): return adjacent_spanset_spanset(self._inner, temporal_time(other._inner)) + elif isinstance(other, get_args(Box)): + return adjacent_spanset_span(self._inner, other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_contained_in(self, container: Union[Period, PeriodSet, Temporal]) -> bool: + def is_contained_in(self, container: Union[Period, PeriodSet, Box, Temporal]) -> bool: """ Returns whether ``self`` is temporally contained in ``container``. @@ -354,16 +444,19 @@ def is_contained_in(self, container: Union[Period, PeriodSet, Temporal]) -> bool """ from .period import Period from ..temporal import Temporal + from ..boxes import Box if isinstance(container, Period): return contained_spanset_span(self._inner, container._inner) elif isinstance(container, PeriodSet): return contained_spanset_spanset(self._inner, container._inner) elif isinstance(container, Temporal): return contained_spanset_spanset(self._inner, temporal_time(container._inner)) + elif isinstance(container, get_args(Box)): + return contained_spanset_span(self._inner, container.to_period()._inner) else: raise TypeError(f'Operation not supported with type {container.__class__}') - def contains(self, content: Union[Time, Temporal]) -> bool: + def contains(self, content: Union[Time, Box, Temporal]) -> bool: """ Returns whether ``self`` temporally contains ``content``. @@ -387,6 +480,7 @@ def contains(self, content: Union[Time, Temporal]) -> bool: from .period import Period from .timestampset import TimestampSet from ..temporal import Temporal + from ..boxes import Box if isinstance(content, Period): return contains_spanset_span(self._inner, content._inner) if isinstance(content, PeriodSet): @@ -397,10 +491,36 @@ def contains(self, content: Union[Time, Temporal]) -> bool: return contains_spanset_spanset(self._inner, set_to_spanset(content._inner)) elif isinstance(content, Temporal): return contains_spanset_spanset(self._inner, temporal_time(content._inner)) + elif isinstance(content, get_args(Box)): + return contains_spanset_span(self._inner, content.to_period()._inner) else: raise TypeError(f'Operation not supported with type {content.__class__}') - def overlaps(self, other: Union[Period, PeriodSet, TimestampSet, Temporal]) -> bool: + def __contains__(self, item): + """ + Returns whether ``self`` temporally contains ``content``. + + Examples: + >>> PeriodSet('{[2012-01-01, 2012-01-04]}').contains(PeriodSet('{[2012-01-02, 2012-01-03]}')) + >>> True + >>> PeriodSet('{[2012-01-01, 2012-01-02]}').contains(PeriodSet('{(2012-01-01, 2012-01-02)}')) + >>> True + >>> PeriodSet('{(2012-01-01, 2012-01-02)}').contains(PeriodSet('{[2012-01-01, 2012-01-02]}')) + >>> False + + Args: + item: temporal object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_spanset_span, contains_spanset_spanset, contains_periodset_timestamp, + contains_periodset_timestampset, contains_periodset_temporal + """ + return self.contains(item) + + def overlaps(self, other: Union[Period, PeriodSet, TimestampSet, Box, Temporal]) -> bool: """ Returns whether ``self`` temporally overlaps ``other``. That is, both share at least an instant @@ -424,54 +544,37 @@ def overlaps(self, other: Union[Period, PeriodSet, TimestampSet, Temporal]) -> b from .period import Period from .timestampset import TimestampSet from ..temporal import Temporal + from ..boxes import Box if isinstance(other, Period): return overlaps_spanset_span(self._inner, other._inner) if isinstance(other, PeriodSet): return overlaps_spanset_spanset(self._inner, other._inner) elif isinstance(other, TimestampSet): - return overlaps_spanset_span(self._inner, set_to_span(other._inner)) + return overlaps_spanset_span(self._inner, set_span(other._inner)) elif isinstance(other, Temporal): return overlaps_spanset_span(self._inner, temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return overlaps_spanset_span(self._inner, other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_after(self, other: Union[Time, Temporal]) -> bool: + def is_same(self, other: Union[Time, Box, Temporal]) -> bool: """ - Returns whether ``self`` is strictly after ``other``.That is, ``self`` starts after ``other`` ends. - - Examples: - >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_after(PeriodSet('{[2012-01-01, 2012-01-02)}')) - >>> True - >>> PeriodSet('{(2012-01-02, 2012-01-03]}').is_after(PeriodSet('{[2012-01-01, 2012-01-02)}')) - >>> True - >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_after(PeriodSet('{[2012-01-01, 2012-01-02]}')) - >>> False + Returns whether the bounding period of `self` is the same as the bounding period of `other`. Args: - other: temporal object to compare with + other: A time or temporal object to compare to `self`. Returns: - True if after, False otherwise + True if same, False otherwise. - MEOS Functions: - right_spanset_span, right_spanset_spanset, overbefore_timestamp_periodset + See Also: + :meth:`Period.is_same` """ - from .period import Period - from .timestampset import TimestampSet - if isinstance(other, datetime): - return overbefore_timestamp_periodset(datetime_to_timestamptz(other), self._inner) - elif isinstance(other, TimestampSet): - return right_spanset_span(self._inner, set_to_span(other._inner)) - elif isinstance(other, Period): - return right_spanset_span(self._inner, other._inner) - elif isinstance(other, PeriodSet): - return right_spanset_spanset(self._inner, other._inner) - elif isinstance(other, Temporal): - return right_spanset_span(self._inner, temporal_to_period(other._inner)) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') + return self.to_period().is_same(other) - def is_before(self, other: Union[Time, Temporal]) -> bool: + # ------------------------- Position Operations --------------------------- + def is_before(self, other: Union[Time, Box, Temporal]) -> bool: """ Returns whether ``self`` is strictly before ``other``. That is, ``self`` ends before ``other`` starts. @@ -494,6 +597,8 @@ def is_before(self, other: Union[Time, Temporal]) -> bool: """ from .period import Period from .timestampset import TimestampSet + from ..temporal import Temporal + from ..boxes import Box if isinstance(other, datetime): return before_periodset_timestamp(self._inner, datetime_to_timestamptz(other)) elif isinstance(other, TimestampSet): @@ -504,105 +609,137 @@ def is_before(self, other: Union[Time, Temporal]) -> bool: return left_spanset_spanset(self._inner, other._inner) elif isinstance(other, Temporal): return left_spanset_span(self._inner, temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return left_spanset_span(self._inner, other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_over_or_after(self, other: Union[Time, Temporal]) -> bool: + def is_over_or_before(self, other: Union[Time, Box, Temporal]) -> bool: """ - Returns whether ``self`` is after ``other`` allowing overlap. That is, ``self`` starts after ``other`` starts - (or at the same time). + Returns whether ``self`` is before ``other`` allowing overlap. That is, ``self`` ends before ``other`` ends (or + at the same time). Examples: - >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_over_or_after(PeriodSet('{[2012-01-01, 2012-01-02)}')) + >>> PeriodSet('{[2012-01-01, 2012-01-02)}').is_over_or_before(PeriodSet('{[2012-01-02, 2012-01-03]}')) >>> True - >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_over_or_after(PeriodSet('{[2012-01-01, 2012-01-02]}')) + >>> PeriodSet('{[2012-01-01, 2012-01-02]}').is_over_or_before(PeriodSet('{[2012-01-02, 2012-01-03]}')) >>> True - >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_over_or_after(PeriodSet('{[2012-01-01, 2012-01-03]}')) + >>> PeriodSet('{[2012-01-03, 2012-01-05]}').is_over_or_before(PeriodSet('{[2012-01-01, 2012-01-04]}')) >>> False Args: other: temporal object to compare with Returns: - True if overlapping or after, False otherwise + True if before, False otherwise MEOS Functions: - overright_spanset_span, overright_spanset_spanset, overafter_periodset_timestamp, - overafter_periodset_timestampset, + overleft_spanset_span, overleft_spanset_spanset, overbefore_periodset_timestamp, + overbefore_periodset_timestampset, overbefore_periodset_temporal """ from .period import Period from .timestampset import TimestampSet - if isinstance(other, datetime): - return overafter_periodset_timestamp(self._inner, datetime_to_timestamptz(other)) - elif isinstance(other, TimestampSet): - return overright_spanset_span(self._inner, set_to_span(other._inner)) - elif isinstance(other, Period): - return overright_spanset_span(self._inner, other._inner) - elif isinstance(other, PeriodSet): - return overright_spanset_spanset(self._inner, other._inner) + from ..temporal import Temporal + from ..boxes import Box + if isinstance(other, Period): + return overleft_spanset_span(self._inner, other._inner) + if isinstance(other, PeriodSet): + return overleft_spanset_spanset(self._inner, other._inner) + elif isinstance(other, datetime): + return overbefore_periodset_timestamp(self._inner, datetime_to_timestamptz(other)) + if isinstance(other, TimestampSet): + return overleft_spanset_span(self._inner, set_span(other._inner)) elif isinstance(other, Temporal): - return overright_spanset_span(self._inner, temporal_to_period(other._inner)) + return overleft_spanset_span(self._inner, temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return overleft_spanset_span(self._inner, other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_over_or_before(self, other: Union[Time, Temporal]) -> bool: + def is_after(self, other: Union[Time, Box, Temporal]) -> bool: """ - Returns whether ``self`` is before ``other`` allowing overlap. That is, ``self`` ends before ``other`` ends (or - at the same time). + Returns whether ``self`` is strictly after ``other``.That is, ``self`` starts after ``other`` ends. Examples: - >>> PeriodSet('{[2012-01-01, 2012-01-02)}').is_over_or_before(PeriodSet('{[2012-01-02, 2012-01-03]}')) + >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_after(PeriodSet('{[2012-01-01, 2012-01-02)}')) >>> True - >>> PeriodSet('{[2012-01-01, 2012-01-02]}').is_over_or_before(PeriodSet('{[2012-01-02, 2012-01-03]}')) + >>> PeriodSet('{(2012-01-02, 2012-01-03]}').is_after(PeriodSet('{[2012-01-01, 2012-01-02)}')) >>> True - >>> PeriodSet('{[2012-01-03, 2012-01-05]}').is_over_or_before(PeriodSet('{[2012-01-01, 2012-01-04]}')) + >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_after(PeriodSet('{[2012-01-01, 2012-01-02]}')) >>> False Args: other: temporal object to compare with Returns: - True if before, False otherwise + True if after, False otherwise MEOS Functions: - overleft_spanset_span, overleft_spanset_spanset, overbefore_periodset_timestamp, - overbefore_periodset_timestampset, overbefore_periodset_temporal + right_spanset_span, right_spanset_spanset, overbefore_timestamp_periodset """ from .period import Period from .timestampset import TimestampSet - if isinstance(other, Period): - return overleft_spanset_span(self._inner, other._inner) - if isinstance(other, PeriodSet): - return overleft_spanset_spanset(self._inner, other._inner) - elif isinstance(other, datetime): - return overbefore_periodset_timestamp(self._inner, datetime_to_timestamptz(other)) - if isinstance(other, TimestampSet): - return overleft_spanset_span(self._inner, set_to_span(other._inner)) + from ..temporal import Temporal + from ..boxes import Box + if isinstance(other, datetime): + return overbefore_timestamp_periodset(datetime_to_timestamptz(other), self._inner) + elif isinstance(other, TimestampSet): + return right_spanset_span(self._inner, set_span(other._inner)) + elif isinstance(other, Period): + return right_spanset_span(self._inner, other._inner) + elif isinstance(other, PeriodSet): + return right_spanset_spanset(self._inner, other._inner) elif isinstance(other, Temporal): - return overleft_spanset_span(self._inner, temporal_to_period(other._inner)) + return right_spanset_span(self._inner, temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return right_spanset_span(self._inner, other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_same(self, other: Temporal) -> bool: + def is_over_or_after(self, other: Union[Time, Box, Temporal]) -> bool: """ - Returns whether ``self`` and ``other`` have the same temporal bounds. + Returns whether ``self`` is after ``other`` allowing overlap. That is, ``self`` starts after ``other`` starts + (or at the same time). + + Examples: + >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_over_or_after(PeriodSet('{[2012-01-01, 2012-01-02)}')) + >>> True + >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_over_or_after(PeriodSet('{[2012-01-01, 2012-01-02]}')) + >>> True + >>> PeriodSet('{[2012-01-02, 2012-01-03]}').is_over_or_after(PeriodSet('{[2012-01-01, 2012-01-03]}')) + >>> False Args: other: temporal object to compare with Returns: - True if equal, False otherwise + True if overlapping or after, False otherwise MEOS Functions: - spanset_eq + overright_spanset_span, overright_spanset_spanset, overafter_periodset_timestamp, + overafter_periodset_timestampset, """ + from .period import Period + from .timestampset import TimestampSet from ..temporal import Temporal - if isinstance(other, Temporal): - return spanset_eq(self._inner, temporal_time(other._inner)) + from ..boxes import Box + if isinstance(other, datetime): + return overafter_periodset_timestamp(self._inner, datetime_to_timestamptz(other)) + elif isinstance(other, TimestampSet): + return overright_spanset_span(self._inner, set_span(other._inner)) + elif isinstance(other, Period): + return overright_spanset_span(self._inner, other._inner) + elif isinstance(other, PeriodSet): + return overright_spanset_spanset(self._inner, other._inner) + elif isinstance(other, Temporal): + return overright_spanset_span(self._inner, temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return overright_spanset_span(self._inner, other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def distance(self, other: Time) -> timedelta: + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[Time, Box, Temporal]) -> timedelta: """ Returns the temporal distance between ``self`` and ``other``. @@ -618,17 +755,24 @@ def distance(self, other: Time) -> timedelta: """ from .period import Period from .timestampset import TimestampSet - if isinstance(other, Period): + from ..temporal import Temporal + from ..boxes import Box + if isinstance(other, Temporal): + return timedelta(seconds=distance_spanset_span(self._inner, temporal_to_period(other._inner))) + elif isinstance(other, Period): return timedelta(seconds=distance_spanset_span(self._inner, other._inner)) elif isinstance(other, PeriodSet): return timedelta(seconds=distance_spanset_spanset(self._inner, other._inner)) elif isinstance(other, datetime): return timedelta(seconds=distance_periodset_timestamp(self._inner, datetime_to_timestamptz(other))) elif isinstance(other, TimestampSet): - return timedelta(seconds=distance_spanset_span(self._inner, set_to_span(other._inner))) + return timedelta(seconds=distance_spanset_span(self._inner, set_span(other._inner))) + elif isinstance(other, get_args(Box)): + return timedelta(seconds=distance_spanset_span(self._inner, other.to_period()._inner)) else: raise TypeError(f'Operation not supported with type {other.__class__}') + # ------------------------- Set Operations -------------------------------- @overload def intersection(self, other: Period) -> PeriodSet: ... @@ -661,18 +805,35 @@ def intersection(self, other: Time) -> Union[PeriodSet, datetime, TimestampSet]: from .period import Period from .timestampset import TimestampSet if isinstance(other, datetime): - return timestamptz_to_datetime( - intersection_periodset_timestamp(self._inner, datetime_to_timestamptz(other))) + result = intersection_periodset_timestamp(self._inner, datetime_to_timestamptz(other)) + return timestamptz_to_datetime(result) if result is not None else None elif isinstance(other, TimestampSet): - return TimestampSet(_inner=intersection_spanset_spanset(self._inner, set_to_spanset(other._inner))) + result = intersection_spanset_spanset(self._inner, set_to_spanset(other._inner)) + return TimestampSet(_inner=result) if result is not None else None elif isinstance(other, Period): - return PeriodSet(_inner=intersection_spanset_span(self._inner, other._inner)) + result = intersection_spanset_span(self._inner, other._inner) + return PeriodSet(_inner=result) if result is not None else None elif isinstance(other, PeriodSet): - return PeriodSet(_inner=intersection_spanset_spanset(self._inner, other._inner)) - + result = intersection_spanset_spanset(self._inner, other._inner) + return PeriodSet(_inner=result) if result is not None else None else: raise TypeError(f'Operation not supported with type {other.__class__}') + def __mul__(self, other): + """ + Returns the temporal intersection of ``self`` and ``other``. + + Args: + other: temporal object to intersect with + + Returns: + A :class:`Time` instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_periodset_timestamp, intersection_spanset_spanset, intersection_spanset_span + """ + return self.intersection(other) + def minus(self, other: Time) -> PeriodSet: """ Returns the temporal difference of ``self`` and ``other``. @@ -689,16 +850,31 @@ def minus(self, other: Time) -> PeriodSet: from .period import Period from .timestampset import TimestampSet if isinstance(other, datetime): - return PeriodSet(_inner=minus_periodset_timestamp(self._inner, datetime_to_timestamptz(other))) + result = minus_periodset_timestamp(self._inner, datetime_to_timestamptz(other)) elif isinstance(other, TimestampSet): - return PeriodSet(_inner=minus_spanset_spanset(self._inner, set_to_spanset(other._inner))) + result = minus_spanset_spanset(self._inner, set_to_spanset(other._inner)) elif isinstance(other, Period): - return PeriodSet(_inner=minus_spanset_span(self._inner, other._inner)) + result = minus_spanset_span(self._inner, other._inner) elif isinstance(other, PeriodSet): - return PeriodSet(_inner=minus_spanset_spanset(self._inner, other._inner)) - + result = minus_spanset_spanset(self._inner, other._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') + return PeriodSet(_inner=result) if result is not None else None + + def __sub__(self, other): + """ + Returns the temporal difference of ``self`` and ``other``. + + Args: + other: temporal object to diff with + + Returns: + A :class:`PeriodSet` instance. + + MEOS Functions: + minus_spanset_span, minus_spanset_spanset, minus_periodset_timestamp + """ + return self.minus(other) def union(self, other: Time) -> PeriodSet: """ @@ -727,21 +903,6 @@ def union(self, other: Time) -> PeriodSet: else: raise TypeError(f'Operation not supported with type {other.__class__}') - def __mul__(self, other): - """ - Returns the temporal intersection of ``self`` and ``other``. - - Args: - other: temporal object to intersect with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - intersection_periodset_timestamp, intersection_spanset_spanset, intersection_spanset_span - """ - return self.intersection(other) - def __add__(self, other): """ Returns the temporal union of ``self`` and ``other``. @@ -757,45 +918,7 @@ def __add__(self, other): """ return self.union(other) - def __sub__(self, other): - """ - Returns the temporal difference of ``self`` and ``other``. - - Args: - other: temporal object to diff with - - Returns: - A :class:`PeriodSet` instance. - - MEOS Functions: - minus_spanset_span, minus_spanset_spanset, minus_periodset_timestamp - """ - return self.minus(other) - - def __contains__(self, item): - """ - Returns whether ``self`` temporally contains ``content``. - - Examples: - >>> PeriodSet('{[2012-01-01, 2012-01-04]}').contains(PeriodSet('{[2012-01-02, 2012-01-03]}')) - >>> True - >>> PeriodSet('{[2012-01-01, 2012-01-02]}').contains(PeriodSet('{(2012-01-01, 2012-01-02)}')) - >>> True - >>> PeriodSet('{(2012-01-01, 2012-01-02)}').contains(PeriodSet('{[2012-01-01, 2012-01-02]}')) - >>> False - - Args: - item: temporal object to compare with - - Returns: - True if contains, False otherwise - - MEOS Functions: - contains_spanset_span, contains_spanset_spanset, contains_periodset_timestamp, - contains_periodset_timestampset, contains_periodset_temporal - """ - return self.contains(item) - + # ------------------------- Comparisons ----------------------------------- def __eq__(self, other): """ Return whether ``self`` and ``other`` are equal. @@ -830,23 +953,6 @@ def __ne__(self, other): return spanset_ne(self._inner, other._inner) return True - def __cmp__(self, other): - """ - Return the result of comparing ``self`` and ``other``. - - Args: - other: temporal object to compare with - - Returns: - -1 if less than, 0 if equal, and 1 if greater than - - MEOS Functions: - spanset_cmp - """ - if isinstance(other, self.__class__): - return spanset_cmp(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - def __lt__(self, other): """ Return whether ``self`` is less than ``other``. @@ -915,6 +1021,12 @@ def __ge__(self, other): return spanset_ge(self._inner, other._inner) raise TypeError(f'Operation not supported with type {other.__class__}') + # ------------------------- Plot Operations ------------------------------- + def plot(self, *args, **kwargs): + from ..plotters import TimePlotter + return TimePlotter.plot_periodset(self, *args, **kwargs) + + # ------------------------- Database Operations --------------------------- @staticmethod def read_from_cursor(value, _=None): """ @@ -925,56 +1037,3 @@ def read_from_cursor(value, _=None): return None return PeriodSet(string=value) - def __copy__(self): - """ - Return a copy of ``self``. - - Returns: - A new :class:`Period` instance - - MEOS Functions: - spanset_copy - """ - inner_copy = spanset_copy(self._inner) - return PeriodSet(_inner=inner_copy) - - def __str__(self): - """ - Return the string representation of the content of ``self``. - - Returns: - A new :class:`str` instance - - MEOS Functions: - periodset_out - """ - return periodset_out(self._inner) - - def __hash__(self) -> int: - """ - Return the hash representation of ``self``. - - Returns: - A new :class:`int` instance - - MEOS Functions: - spanset_hash - """ - return spanset_hash(self._inner) - - def __repr__(self): - """ - Return the string representation of ``self``. - - Returns: - A new :class:`str` instance - - MEOS Functions: - periodset_out - """ - return (f'{self.__class__.__name__}' - f'({self})') - - def plot(self, *args, **kwargs): - from ..plotters import TimePlotter - return TimePlotter.plot_periodset(self, *args, **kwargs) diff --git a/pymeos/pymeos/time/timestampset.py b/pymeos/pymeos/time/timestampset.py index de7a13ca..3997035c 100644 --- a/pymeos/pymeos/time/timestampset.py +++ b/pymeos/pymeos/time/timestampset.py @@ -1,7 +1,7 @@ from __future__ import annotations from datetime import datetime, timedelta -from typing import Optional, List, Union, TYPE_CHECKING, overload +from typing import Optional, List, Union, TYPE_CHECKING, overload, get_args from dateutil.parser import parse from pymeos_cffi import * @@ -11,6 +11,7 @@ from .period import Period from .periodset import PeriodSet from .time import Time + from ..boxes import Box class TimestampSet: @@ -33,6 +34,7 @@ class TimestampSet: __slots__ = ['_inner'] + # ------------------------- Constructors ---------------------------------- def __init__(self, string: Optional[str] = None, *, timestamp_list: Optional[List[Union[str, datetime]]] = None, _inner=None): super().__init__() @@ -41,11 +43,39 @@ def __init__(self, string: Optional[str] = None, *, timestamp_list: Optional[Lis if _inner is not None: self._inner = _inner elif string is not None: - self._inner = tstzset_in(string) + self._inner = timestampset_in(string) else: times = [pg_timestamp_in(ts, -1) if isinstance(ts, str) else datetime_to_timestamptz(ts) for ts in timestamp_list] - self._inner = tstzset_make(times, len(times)) + self._inner = timestampset_make(times, len(times)) + + def __copy__(self): + """ + Return a copy of ``self``. + + Returns: + A new :class:`Period` instance + + MEOS Functions: + set_copy + """ + inner_copy = set_copy(self._inner) + return TimestampSet(_inner=inner_copy) + + @staticmethod + def from_wkb(wkb: bytes) -> TimestampSet: + """ + Returns a `TimestampSet` from its WKB representation. + Args: + wkb: WKB representation + + Returns: + A new :class:`TimestampSet` instance + + MEOS Functions: + set_from_wkb + """ + return TimestampSet(_inner=(set_from_wkb(wkb))) @staticmethod def from_hexwkb(hexwkb: str) -> TimestampSet: @@ -62,43 +92,55 @@ def from_hexwkb(hexwkb: str) -> TimestampSet: """ return TimestampSet(_inner=(set_from_hexwkb(hexwkb))) - def as_hexwkb(self) -> str: + # ------------------------- Output ---------------------------------------- + def __str__(self): """ - Returns the WKB representation of ``self`` in hex-encoded ASCII. + Return the string representation of the content of ``self``. + Returns: - A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. + A new :class:`str` instance MEOS Functions: - set_as_hexwkb + set_out """ - return set_as_hexwkb(self._inner, -1)[0] + return set_out(self._inner, 15) - def timespan(self) -> timedelta: + def __repr__(self): """ - Returns the duration of the time ignoring gaps, i.e. the duration from the - first timestamp to the last one. + Return the string representation of ``self``. Returns: - A :class:`datetime.timedelta` instance representing the duration of the period + A new :class:`str` instance MEOS Functions: - period_duration + set_out """ - return interval_to_timedelta(period_duration(set_to_span(self._inner))) + return (f'{self.__class__.__name__}' + f'({self})') - def period(self) -> Period: + def as_wkb(self) -> bytes: """ - Returns a period that encompasses ``self``. + Returns the WKB representation of ``self``. + Returns: + A :class:`str` object with the WKB representation of ``self``. + MEOS Functions: + set_as_wkb + """ + return set_as_wkb(self._inner, 4) + + def as_hexwkb(self) -> str: + """ + Returns the WKB representation of ``self`` in hex-encoded ASCII. Returns: - A new :class:`Period` instance + A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. MEOS Functions: - set_to_span + set_as_hexwkb """ - from .period import Period - return Period(_inner=set_to_span(self._inner)) + return set_as_hexwkb(self._inner, -1)[0] + # ------------------------- Conversions ----------------------------------- def to_periodset(self) -> PeriodSet: """ Returns a PeriodSet that contains a Period for each Timestamp in ``self``. @@ -112,6 +154,33 @@ def to_periodset(self) -> PeriodSet: from .periodset import PeriodSet return PeriodSet(_inner=set_to_spanset(self._inner)) + # ------------------------- Accessors ------------------------------------- + def duration(self) -> timedelta: + """ + Returns the duration of the time ignoring gaps, i.e. the duration from the + first timestamp to the last one. + + Returns: + A :class:`datetime.timedelta` instance representing the duration of the period + + MEOS Functions: + period_duration + """ + return interval_to_timedelta(period_duration(set_span(self._inner))) + + def period(self) -> Period: + """ + Returns a period that encompasses ``self``. + + Returns: + A new :class:`Period` instance + + MEOS Functions: + set_span + """ + from .period import Period + return Period(_inner=set_span(self._inner)) + def num_timestamps(self) -> int: """ Returns the number of timestamps in ``self``. @@ -130,9 +199,9 @@ def start_timestamp(self) -> datetime: A :class:`datetime` instance MEOS Functions: - tstzset_start_timestamp + timestampset_start_timestamp """ - return timestamptz_to_datetime(tstzset_start_timestamp(self._inner)) + return timestamptz_to_datetime(timestampset_start_timestamp(self._inner)) def end_timestamp(self) -> datetime: """ @@ -141,9 +210,9 @@ def end_timestamp(self) -> datetime: A :class:`datetime` instance MEOS Functions: - tstzset_end_timestamp + timestampset_end_timestamp """ - return timestamptz_to_datetime(tstzset_end_timestamp(self._inner)) + return timestamptz_to_datetime(timestampset_end_timestamp(self._inner)) def timestamp_n(self, n: int) -> datetime: """ @@ -152,9 +221,12 @@ def timestamp_n(self, n: int) -> datetime: A :class:`datetime` instance MEOS Functions: - tstzset_timestamp_n + timestampset_timestamp_n """ - return timestamptz_to_datetime(tstzset_timestamp_n(self._inner, n)) + result = timestampset_timestamp_n(self._inner, n + 1) + if result is None: + raise IndexError(f"Index {n} out of range 0 - {self.num_timestamps() - 1}") + return timestamptz_to_datetime(result) def timestamps(self) -> List[datetime]: """ @@ -163,11 +235,24 @@ def timestamps(self) -> List[datetime]: A :class:`list[datetime]` instance MEOS Functions: - tstzset_timestamps + timestampset_timestamps """ - tss = tstzset_values(self._inner) + tss = timestampset_values(self._inner) return [timestamptz_to_datetime(tss[i]) for i in range(self.num_timestamps())] + def __hash__(self) -> int: + """ + Return the hash representation of ``self``. + + Returns: + A new :class:`int` instance + + MEOS Functions: + set_hash + """ + return set_hash(self._inner) + + # ------------------------- Transformations ------------------------------- def shift(self, delta: timedelta) -> TimestampSet: """ Returns a new TimestampSet that is the result of shifting ``self`` by ``delta`` @@ -183,32 +268,32 @@ def shift(self, delta: timedelta) -> TimestampSet: A new :class:`PeriodSet` instance MEOS Functions: - tstzset_shift_tscale + timestampset_shift_tscale """ return self.shift_tscale(shift=delta) def tscale(self, duration: timedelta) -> TimestampSet: """ - Returns a new TimestampSet that starts as ``self`` but has duration ``duration`` + Returns a new TimestampSet that with the scaled so that the span of ``self`` is ``duration``. Examples: >>> TimestampSet('{2000-01-01, 2000-01-10}').tscale(timedelta(days=2)) >>> 'TimestampSet({2000-01-01 00:00:00+01, 2000-01-03 00:00:00+01})' Args: - duration: :class:`datetime.timedelta` instance representing the duration of the new period + duration: :class:`datetime.timedelta` instance representing the span of the new set Returns: A new :class:`PeriodSet` instance MEOS Functions: - tstzset_shift_tscale + timestampset_shift_tscale """ return self.shift_tscale(duration=duration) def shift_tscale(self, shift: Optional[timedelta] = None, duration: Optional[timedelta] = None) -> TimestampSet: """ - Returns a new TimestampSet that starts at ``self`` shifted by ``shift`` and has duration ``duration`` + Returns a new TimestampSet that is the result of shifting and scaling ``self``. Examples: >>> TimestampSet('{2000-01-01, 2000-01-10}').shift_tscale(shift=timedelta(days=2), duration=timedelta(days=4)) @@ -216,23 +301,24 @@ def shift_tscale(self, shift: Optional[timedelta] = None, duration: Optional[tim Args: shift: :class:`datetime.timedelta` instance to shift - duration: :class:`datetime.timedelta` instance representing the duration of the new period + duration: :class:`datetime.timedelta` instance representing the span of the new set Returns: A new :class:`PeriodSet` instance MEOS Functions: - tstzset_shift_tscale + timestampset_shift_tscale """ assert shift is not None or duration is not None, 'shift and scale deltas must not be both None' - tss = tstzset_shift_tscale( + tss = timestampset_shift_tscale( self._inner, timedelta_to_interval(shift) if shift else None, timedelta_to_interval(duration) if duration else None ) return TimestampSet(_inner=tss) - def is_adjacent(self, other: Union[Period, PeriodSet, Temporal]) -> bool: + # ------------------------- Topological Operations ------------------------ + def is_adjacent(self, other: Union[Period, PeriodSet, Temporal, Box]) -> bool: """ Returns whether ``self`` is temporally adjacent to ``other``. That is, they share a bound but only one of them contains it. @@ -257,16 +343,19 @@ def is_adjacent(self, other: Union[Period, PeriodSet, Temporal]) -> bool: from .period import Period from .periodset import PeriodSet from ..temporal import Temporal + from ..boxes import Box if isinstance(other, Period): - return adjacent_span_span(set_to_span(self._inner), other._inner) + return adjacent_span_span(set_span(self._inner), other._inner) elif isinstance(other, PeriodSet): - return adjacent_spanset_span(other._inner, set_to_span(self._inner)) + return adjacent_spanset_spanset(other._inner, set_to_spanset(self._inner)) elif isinstance(other, Temporal): - return adjacent_span_span(set_to_span(self._inner), temporal_to_period(other._inner)) + return adjacent_span_span(set_span(self._inner), temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return adjacent_span_span(set_span(self._inner), other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_contained_in(self, container: Union[Period, PeriodSet, TimestampSet, Temporal]) -> bool: + def is_contained_in(self, container: Union[Period, PeriodSet, TimestampSet, Temporal, Box]) -> bool: """ Returns whether ``self`` is temporally contained in ``container``. @@ -290,14 +379,17 @@ def is_contained_in(self, container: Union[Period, PeriodSet, TimestampSet, Temp from .period import Period from .periodset import PeriodSet from ..temporal import Temporal + from ..boxes import Box if isinstance(container, Period): - return contained_span_span(set_to_span(self._inner), container._inner) + return contained_span_span(set_span(self._inner), container._inner) elif isinstance(container, PeriodSet): - return contained_span_spanset(set_to_span(self._inner), container._inner) + return contained_span_spanset(set_span(self._inner), container._inner) elif isinstance(container, TimestampSet): return contained_set_set(self._inner, container._inner) elif isinstance(container, Temporal): return contained_spanset_spanset(set_to_spanset(self._inner), temporal_time(container._inner)) + elif isinstance(container, Box): + return contained_span_span(set_span(self._inner), container.to_period()._inner) else: raise TypeError(f'Operation not supported with type {container.__class__}') @@ -322,6 +414,7 @@ def contains(self, content: Union[datetime, TimestampSet, Temporal]) -> bool: MEOS Functions: contains_timestampset_timestamp, contains_set_set, contains_spanset_spanset """ + from ..temporal import Temporal if isinstance(content, datetime): return contains_timestampset_timestamp(self._inner, datetime_to_timestamptz(content)) elif isinstance(content, TimestampSet): @@ -331,7 +424,30 @@ def contains(self, content: Union[datetime, TimestampSet, Temporal]) -> bool: else: raise TypeError(f'Operation not supported with type {content.__class__}') - def overlaps(self, other: Union[Period, PeriodSet, TimestampSet, Temporal]) -> bool: + def __contains__(self, item): + """ + Returns whether ``self`` temporally contains ``content``. + + Examples: + >>> TimestampSet('{2012-01-01, 2012-01-04}').contains(parse('2012-01-01]')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').contains(TimestampSet('{2012-01-01}')) + >>> True + >>> TimestampSet('{2012-01-01, 2012-01-02}').contains(TimestampSet('{2012-01-01, 2012-01-03}')) + >>> False + + Args: + item: temporal object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_timestampset_timestamp, contains_set_set, contains_spanset_spanset + """ + return self.contains(item) + + def overlaps(self, other: Union[Period, PeriodSet, TimestampSet, Temporal, Box]) -> bool: """ Returns whether ``self`` temporally overlaps ``other``. That is, both share at least an instant @@ -355,18 +471,39 @@ def overlaps(self, other: Union[Period, PeriodSet, TimestampSet, Temporal]) -> b from .period import Period from .periodset import PeriodSet from ..temporal import Temporal - if isinstance(other, TimestampSet): + from ..boxes import Box + if isinstance(other, datetime): + return contains_timestampset_timestamp(self._inner, datetime_to_timestamptz(other)) + elif isinstance(other, TimestampSet): return overlaps_set_set(self._inner, other._inner) elif isinstance(other, Period): - return overlaps_span_span(self._inner, other._inner) + return overlaps_span_span(set_span(self._inner), other._inner) elif isinstance(other, PeriodSet): return overlaps_spanset_spanset(set_to_spanset(self._inner), other._inner) elif isinstance(other, Temporal): return overlaps_spanset_spanset(set_to_spanset(self._inner), temporal_time(other._inner)) + elif isinstance(other, Box): + return overlaps_span_span(set_span(self._inner), other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_after(self, other: Time) -> bool: + def is_same(self, other: Union[Time, Temporal, Box]) -> bool: + """ + Returns whether the bounding period of `self` is the same as the bounding period of `other`. + + Args: + other: A time or temporal object to compare to `self`. + + Returns: + True if same, False otherwise. + + See Also: + :meth:`Period.is_same` + """ + return self.to_period().is_same(other) + + # ------------------------- Position Operations --------------------------- + def is_after(self, other: Union[Time, Temporal, Box]) -> bool: """ Returns whether ``self`` is strictly after ``other``. That is, the first timestamp in ``self`` is after ``other``. @@ -390,20 +527,24 @@ def is_after(self, other: Time) -> bool: """ from .period import Period from .periodset import PeriodSet + from ..temporal import Temporal + from ..boxes import Box if isinstance(other, datetime): return overbefore_timestamp_timestampset(datetime_to_timestamptz(other), self._inner) elif isinstance(other, TimestampSet): return right_set_set(self._inner, other._inner) elif isinstance(other, Period): - return right_span_span(set_to_span(self._inner), other._inner) + return right_span_span(set_span(self._inner), other._inner) elif isinstance(other, PeriodSet): - return right_span_spanset(set_to_span(self._inner), other._inner) + return right_span_spanset(set_span(self._inner), other._inner) elif isinstance(other, Temporal): - return right_span_span(set_to_span(self._inner), temporal_to_period(other._inner)) + return right_span_span(set_span(self._inner), temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return right_span_span(set_span(self._inner), other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_before(self, other: Time) -> bool: + def is_before(self, other: Union[Time, Temporal, Box]) -> bool: """ Returns whether ``self`` is strictly before ``other``. That is, ``self`` ends before ``other`` starts. @@ -426,20 +567,24 @@ def is_before(self, other: Time) -> bool: """ from .period import Period from .periodset import PeriodSet + from ..temporal import Temporal + from ..boxes import Box if isinstance(other, datetime): - return overafter_timestamp_period(datetime_to_timestamptz(other), set_to_span(self._inner)) + return overafter_timestamp_period(datetime_to_timestamptz(other), set_span(self._inner)) elif isinstance(other, TimestampSet): - return left_span_span(set_to_span(self._inner), set_to_span(other._inner)) + return left_set_set(self._inner, other._inner) elif isinstance(other, Period): - return left_span_span(set_to_span(self._inner), other._inner) + return left_span_span(set_span(self._inner), other._inner) elif isinstance(other, PeriodSet): - return left_span_spanset(set_to_span(self._inner), other._inner) + return left_span_spanset(set_span(self._inner), other._inner) elif isinstance(other, Temporal): - return left_span_span(self._inner, temporal_to_period(other._inner)) + return left_span_span(set_span(self._inner), temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return left_span_span(set_span(self._inner), other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_over_or_after(self, other: Time) -> bool: + def is_over_or_after(self, other: Union[Time, Temporal, Box]) -> bool: """ Returns whether ``self`` is after ``other`` allowing overlap. That is, ``self`` starts after ``other`` starts (or at the same time). @@ -463,20 +608,24 @@ def is_over_or_after(self, other: Time) -> bool: """ from .period import Period from .periodset import PeriodSet + from ..temporal import Temporal + from ..boxes import Box if isinstance(other, datetime): - return overafter_period_timestamp(set_to_span(self._inner), datetime_to_timestamptz(other)) + return overafter_period_timestamp(set_span(self._inner), datetime_to_timestamptz(other)) elif isinstance(other, TimestampSet): - return overright_span_span(set_to_span(self._inner), set_to_span(other._inner)) + return overright_set_set(self._inner, other._inner) elif isinstance(other, Period): - return overright_span_span(set_to_span(self._inner), other._inner) + return overright_span_span(set_span(self._inner), other._inner) elif isinstance(other, PeriodSet): - return overright_span_spanset(set_to_span(self._inner), other._inner) + return overright_span_spanset(set_span(self._inner), other._inner) elif isinstance(other, Temporal): - return overright_span_span(set_to_span(self._inner), temporal_to_period(other._inner)) + return overright_span_span(set_span(self._inner), temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return overright_span_span(set_span(self._inner), other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_over_or_before(self, other: Time) -> bool: + def is_over_or_before(self, other: Union[Time, Temporal, Box]) -> bool: """ Returns whether ``self`` is before ``other`` allowing overlap. That is, ``self`` ends before ``other`` ends (or at the same time). @@ -500,39 +649,25 @@ def is_over_or_before(self, other: Time) -> bool: """ from .period import Period from .periodset import PeriodSet + from ..temporal import Temporal + from ..boxes import Box if isinstance(other, datetime): - return overbefore_period_timestamp(set_to_span(self._inner), datetime_to_timestamptz(other)) + return overbefore_period_timestamp(set_span(self._inner), datetime_to_timestamptz(other)) if isinstance(other, TimestampSet): - return overleft_span_span(set_to_span(self._inner), set_to_span(other._inner)) + return overleft_set_set(self._inner, other._inner) if isinstance(other, Period): - return overleft_span_span(set_to_span(self._inner), other._inner) + return overleft_span_span(set_span(self._inner), other._inner) if isinstance(other, PeriodSet): - return overleft_span_spanset(set_to_span(self._inner), other._inner) + return overleft_span_spanset(set_span(self._inner), other._inner) elif isinstance(other, Temporal): - return overleft_span_span(set_to_span(self._inner), temporal_to_period(other._inner)) + return overleft_span_span(set_span(self._inner), temporal_to_period(other._inner)) + elif isinstance(other, get_args(Box)): + return overleft_span_span(set_span(self._inner), other.to_period()._inner) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def is_same(self, other: Temporal) -> bool: - """ - Returns whether ``self`` and ``other`` have the same temporal dimension. - - Args: - other: temporal object to compare with - - Returns: - True if equal, False otherwise - - MEOS Functions: - spanset_eq - """ - from ..temporal import Temporal - if isinstance(other, Temporal): - return spanset_eq(set_to_spanset(self._inner), temporal_time(other._inner)) - else: - raise TypeError(f'Operation not supported with type {other.__class__}') - - def distance(self, other: Union[Time, Temporal]) -> timedelta: + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[Time, Temporal, Box]) -> timedelta: """ Returns the temporal distance between ``self`` and ``other``. @@ -547,32 +682,37 @@ def distance(self, other: Union[Time, Temporal]) -> timedelta: """ from .period import Period from .periodset import PeriodSet + from ..temporal import Temporal + from ..boxes import Box if isinstance(other, datetime): return timedelta(seconds=distance_timestampset_timestamp(self._inner, datetime_to_timestamptz(other))) elif isinstance(other, TimestampSet): return timedelta(seconds=distance_set_set(self._inner, other._inner)) elif isinstance(other, Period): - return timedelta(seconds=distance_spanset_span(set_to_spanset(self._inner), other._inner)) + return timedelta(seconds=distance_span_span(set_span(self._inner), other._inner)) elif isinstance(other, PeriodSet): - return timedelta(seconds=distance_spanset_spanset(set_to_spanset(self._inner), other._inner)) + return timedelta(seconds=distance_spanset_span(other._inner, set_span(self._inner))) elif isinstance(other, Temporal): - return timedelta(seconds=distance_spanset_spanset(set_to_spanset(self._inner), temporal_time(other._inner))) + return timedelta(seconds=distance_span_span(set_span(self._inner), temporal_to_period(other._inner))) + elif isinstance(other, get_args(Box)): + return timedelta(seconds=distance_span_span(set_span(self._inner), other.to_period()._inner)) else: raise TypeError(f'Operation not supported with type {other.__class__}') + # ------------------------- Set Operations -------------------------------- @overload - def intersection(self, other: datetime) -> datetime: + def intersection(self, other: datetime) -> Optional[datetime]: ... @overload - def intersection(self, other: TimestampSet) -> TimestampSet: + def intersection(self, other: TimestampSet) -> Optional[TimestampSet]: ... @overload - def intersection(self, other: Union[Period, PeriodSet, Temporal]) -> PeriodSet: + def intersection(self, other: Union[Period, PeriodSet, Temporal]) -> Optional[PeriodSet]: ... - def intersection(self, other: Union[Time, Temporal]) -> Union[datetime, TimestampSet, PeriodSet]: + def intersection(self, other: Union[Time, Temporal]) -> Optional[Time]: """ Returns the temporal intersection of ``self`` and ``other``. @@ -588,29 +728,44 @@ def intersection(self, other: Union[Time, Temporal]) -> Union[datetime, Timestam from .period import Period from .periodset import PeriodSet if isinstance(other, datetime): - return timestamptz_to_datetime( - intersection_set_set(self._inner, timestamp_to_tstzset(datetime_to_timestamptz(other)))) + result = intersection_set_set(self._inner, timestamp_to_tstzset(datetime_to_timestamptz(other))) + return timestamptz_to_datetime(result) if result is not None else None elif isinstance(other, TimestampSet): - return TimestampSet(_inner=intersection_set_set(self._inner, other._inner)) + result = intersection_set_set(self._inner, other._inner) + return TimestampSet(_inner=result) if result is not None else None elif isinstance(other, Period): - return PeriodSet(_inner=intersection_spanset_span(set_to_spanset(self._inner), other._inner)) + result = intersection_spanset_span(set_to_spanset(self._inner), other._inner) + return PeriodSet(_inner=result) if result is not None else None elif isinstance(other, PeriodSet): - return PeriodSet(_inner=intersection_spanset_spanset(set_to_spanset(self._inner), other._inner)) - elif isinstance(other, Temporal): - return PeriodSet( - _inner=intersection_spanset_spanset(set_to_spanset(self._inner), temporal_time(other._inner))) + result = intersection_spanset_spanset(set_to_spanset(self._inner), other._inner) + return PeriodSet(_inner=result) if result is not None else None else: raise TypeError(f'Operation not supported with type {other.__class__}') + def __mul__(self, other): + """ + Returns the temporal intersection of ``self`` and ``other``. + + Args: + other: temporal object to intersect with + + Returns: + A :class:`Time` instance. The actual class depends on ``other``. + + MEOS Functions: + intersection_set_set, intersection_spanset_span, intersection_spanset_spanset + """ + return self.intersection(other) + @overload - def minus(self, other: Union[datetime, TimestampSet]) -> TimestampSet: + def minus(self, other: Union[datetime, TimestampSet]) -> Optional[TimestampSet]: ... @overload - def minus(self, other: Union[Period, PeriodSet]) -> PeriodSet: + def minus(self, other: Union[Period, PeriodSet]) -> Optional[PeriodSet]: ... - def minus(self, other: Time) -> Union[TimestampSet, PeriodSet]: + def minus(self, other: Time) -> Optional[Time]: """ Returns the temporal difference of ``self`` and ``other``. @@ -626,16 +781,35 @@ def minus(self, other: Time) -> Union[TimestampSet, PeriodSet]: from .period import Period from .periodset import PeriodSet if isinstance(other, datetime): - return TimestampSet(_inner=minus_timestampset_timestamp(self._inner, datetime_to_timestamptz(other))) + result = minus_timestampset_timestamp(self._inner, datetime_to_timestamptz(other)) + return TimestampSet(_inner=result) if result is not None else None elif isinstance(other, TimestampSet): - return TimestampSet(_inner=minus_set_set(self._inner, other._inner)) + result = minus_set_set(self._inner, other._inner) + return TimestampSet(_inner=result) if result is not None else None elif isinstance(other, Period): - return PeriodSet(_inner=minus_spanset_span(set_to_spanset(self._inner), other._inner)) + result = minus_spanset_span(set_to_spanset(self._inner), other._inner) + return PeriodSet(_inner=result) if result is not None else None elif isinstance(other, PeriodSet): - return PeriodSet(_inner=minus_spanset_spanset(set_to_spanset(self._inner), other._inner)) + result = minus_spanset_spanset(set_to_spanset(self._inner), other._inner) + return PeriodSet(_inner=result) if result is not None else None else: raise TypeError(f'Operation not supported with type {other.__class__}') + def __sub__(self, other): + """ + Returns the temporal difference of ``self`` and ``other``. + + Args: + other: temporal object to diff with + + Returns: + A :class:`Time` instance. The actual class depends on ``other``. + + MEOS Functions: + minus_timestampset_timestamp, minus_set_set, minus_spanset_span, minus_spanset_spanset + """ + return self.minus(other) + @overload def union(self, other: Union[datetime, TimestampSet]) -> TimestampSet: ... @@ -664,27 +838,12 @@ def union(self, other: Time) -> Union[PeriodSet, TimestampSet]: elif isinstance(other, TimestampSet): return TimestampSet(_inner=union_set_set(self._inner, other._inner)) elif isinstance(other, Period): - return PeriodSet(_inner=union_spanset_span(set_to_spanset(self._inner), self._inner)) + return PeriodSet(_inner=union_spanset_span(set_to_spanset(self._inner), other._inner)) elif isinstance(other, PeriodSet): return PeriodSet(_inner=union_spanset_spanset(set_to_spanset(self._inner), other._inner)) else: raise TypeError(f'Operation not supported with type {other.__class__}') - def __mul__(self, other): - """ - Returns the temporal intersection of ``self`` and ``other``. - - Args: - other: temporal object to intersect with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - intersection_set_set, intersection_spanset_span, intersection_spanset_spanset - """ - return self.intersection(other) - def __add__(self, other): """ Returns the temporal union of ``self`` and ``other``. @@ -700,44 +859,7 @@ def __add__(self, other): """ return self.union(other) - def __sub__(self, other): - """ - Returns the temporal difference of ``self`` and ``other``. - - Args: - other: temporal object to diff with - - Returns: - A :class:`Time` instance. The actual class depends on ``other``. - - MEOS Functions: - minus_timestampset_timestamp, minus_set_set, minus_spanset_span, minus_spanset_spanset - """ - return self.minus(other) - - def __contains__(self, item): - """ - Returns whether ``self`` temporally contains ``content``. - - Examples: - >>> TimestampSet('{2012-01-01, 2012-01-04}').contains(parse('2012-01-01]')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').contains(TimestampSet('{2012-01-01}')) - >>> True - >>> TimestampSet('{2012-01-01, 2012-01-02}').contains(TimestampSet('{2012-01-01, 2012-01-03}')) - >>> False - - Args: - item: temporal object to compare with - - Returns: - True if contains, False otherwise - - MEOS Functions: - contains_timestampset_timestamp, contains_set_set, contains_spanset_spanset - """ - return self.contains(item) - + # ------------------------- Comparisons ----------------------------------- def __eq__(self, other): """ Returns whether ``self`` and ``other`` are equal. @@ -772,23 +894,6 @@ def __ne__(self, other): return set_ne(self._inner, other._inner) return True - def __cmp__(self, other): - """ - Returns the result of comparing ``self`` and ``other``. - - Args: - other: temporal object to compare with - - Returns: - -1 if less than, 0 if equal, and 1 if greater than - - MEOS Functions: - set_cmp - """ - if isinstance(other, self.__class__): - return set_cmp(self._inner, other._inner) - raise TypeError(f'Operation not supported with type {other.__class__}') - def __lt__(self, other): """ Return whether ``self`` is less than ``other``. @@ -857,6 +962,12 @@ def __ge__(self, other): return set_ge(self._inner, other._inner) raise TypeError(f'Operation not supported with type {other.__class__}') + # ------------------------- Plot Operations ------------------------------- + def plot(self, *args, **kwargs): + from ..plotters import TimePlotter + return TimePlotter.plot_timestampset(self, *args, **kwargs) + + # ------------------------- Database Operations --------------------------- @staticmethod def read_from_cursor(value, _=None): """ @@ -867,56 +978,3 @@ def read_from_cursor(value, _=None): return None return TimestampSet(string=value) - def __copy__(self): - """ - Return a copy of ``self``. - - Returns: - A new :class:`Period` instance - - MEOS Functions: - set_copy - """ - inner_copy = set_copy(self._inner) - return TimestampSet(_inner=inner_copy) - - def __str__(self): - """ - Return the string representation of the content of ``self``. - - Returns: - A new :class:`str` instance - - MEOS Functions: - set_out - """ - return set_out(self._inner, 15) - - def __hash__(self) -> int: - """ - Return the hash representation of ``self``. - - Returns: - A new :class:`int` instance - - MEOS Functions: - set_hash - """ - return set_hash(self._inner) - - def __repr__(self): - """ - Return the string representation of ``self``. - - Returns: - A new :class:`str` instance - - MEOS Functions: - set_out - """ - return (f'{self.__class__.__name__}' - f'({self})') - - def plot(self, *args, **kwargs): - from ..plotters import TimePlotter - return TimePlotter.plot_timestampset(self, *args, **kwargs) diff --git a/pymeos/pyproject.toml b/pymeos/pyproject.toml index 09eb76c4..c815a1d7 100644 --- a/pymeos/pyproject.toml +++ b/pymeos/pyproject.toml @@ -4,7 +4,7 @@ build-backend = 'setuptools.build_meta' [project] name = 'pymeos' -version = '1.1.3-alpha' +version = '1.1.3-alpha-2' authors = [ { name = 'Victor Divi', email = 'vdiviloper@gmail.com' }, { name = 'Zhicheng Luo', email = 'zhicheng.luo@ulb.be' }, @@ -34,7 +34,7 @@ license = {file = 'LICENSE'} requires-python = '>=3.7' dependencies = [ - 'pymeos-cffi ==1.1.*', + 'pymeos-cffi >=1.1.0a4', 'python-dateutil', 'spans', 'postgis', diff --git a/pymeos/tests/__init__.py b/pymeos/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymeos/tests/boxes/stbox_test.py b/pymeos/tests/boxes/stbox_test.py new file mode 100644 index 00000000..16d19526 --- /dev/null +++ b/pymeos/tests/boxes/stbox_test.py @@ -0,0 +1,944 @@ +from copy import copy +from datetime import datetime, timezone, timedelta +from shapely import Point, LineString, Polygon +import shapely.geometry + +import pytest +import math + +from pymeos import STBox, TInterpolation, TimestampSet, Period, PeriodSet, \ + TGeomPointInst, TGeomPointSeq, TGeomPointSeqSet, \ + TGeogPointInst, TGeogPointSeq, TGeogPointSeqSet + +from tests.conftest import TestPyMEOS + + +class TestSTBox(TestPyMEOS): + pass + + +class TestSTBoxConstructors(TestSTBox): + stbx = STBox('STBOX X((1,1),(2,2))') + stbz = STBox('STBOX Z((1,1,1),(2,2,2))') + stbt = STBox('STBOX T([2019-09-01,2019-09-02])') + stbxt = STBox('STBOX XT(((1,1),(2,2)),[2019-09-01,2019-09-02])') + stbzt = STBox('STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'source, type, expected', + [ + ('STBox X((1,1),(2,2))', STBox, 'STBOX X((1,1),(2,2))'), + ('STBox T([2019-09-01,2019-09-02])', STBox, + 'STBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + ('STBox XT(((1,1),(2,2)),[2019-09-01,2019-09-02])', STBox, + 'STBOX XT(((1,1),(2,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])') + ], + ids=['STBox X', 'STBox T', 'STBox XT'] + ) + def test_string_constructor(self, source, type, expected): + stb = type(source) + assert isinstance(stb, type) + assert str(stb) == expected + + @pytest.mark.parametrize( + 'geometry, expected', + [ + (Point(1,1), 'STBOX X((1,1),(1,1))'), + (LineString([(1,1), (2,2)]), 'STBOX X((1,1),(2,2))'), + (shapely.set_srid(shapely.Point(1,1), 5676), 'SRID=5676;STBOX X((1,1),(1,1))'), + (shapely.set_srid(shapely.LineString([(1,1), (2,2)]), 5676), 'SRID=5676;STBOX X((1,1),(2,2))'), + ], + ids=['point', 'linestring', 'srid point', 'srid linestring'] + ) + def test_from_geometry_constructor(self, geometry, expected): + stb = STBox.from_geometry(geometry) + assert isinstance(stb, STBox) + assert str(stb) == expected + + # @pytest.mark.parametrize( + # 'time, expected', + # [ + # (datetime(2019, 9, 1), + # 'STBOX T([2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + # (TimestampSet('{2019-09-01, 2019-09-02}'), + # 'STBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + # (Period('[2019-09-01, 2019-09-02]'), + # 'STBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + # (PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}'), + # 'STBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + # ], + # ids=['Timestamp', 'TimestampSet', 'Period', 'PeriodSet'] + # ) + # def test_from_time_constructor(self, time, expected): + # stb = STBox.from_time(time) + # assert isinstance(stb, STBox) + # assert str(stb) == expected + + @pytest.mark.parametrize( + 'geometry, time, expected', + [ + (Point(1,1), datetime(2019, 9, 1), + 'STBOX XT(((1,1),(1,1)),[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + (Point(1,1), Period('[2019-09-01, 2019-09-02]'), + 'STBOX XT(((1,1),(1,1)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + ], + ids=['geometry-Timestamp', 'geometry-Period'] + ) + def test_from_geometry_time_constructor(self, geometry, time, expected): + stb = STBox.from_geometry_time(geometry, time) + assert isinstance(stb, STBox) + assert str(stb) == expected + + @pytest.mark.parametrize( + 'tpoint, expected', + [ + (TGeomPointInst('Point(1 1)@2019-09-01'), + 'STBOX XT(((1,1),(1,1)),[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + (TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}'), + 'STBOX XT(((1,1),(2,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + 'STBOX XT(((1,1),(2,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}'), + 'STBOX XT(((1,1),(2,2)),[2019-09-01 00:00:00+00, 2019-09-05 00:00:00+00])'), + (TGeogPointInst('Point(1 1)@2019-09-01'), + 'SRID=4326;GEODSTBOX XT(((1,1),(1,1)),[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + (TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}'), + 'SRID=4326;GEODSTBOX XT(((1,1),(2,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + 'SRID=4326;GEODSTBOX XT(((1,1),(2,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}'), + 'SRID=4326;GEODSTBOX XT(((1,1),(2,2)),[2019-09-01 00:00:00+00, 2019-09-05 00:00:00+00])'), + ], + ids=['TGeomPoint Instant', 'TGeomPoint Discrete Sequence', 'TGeomPoint Sequence', 'TGeomPoint Sequence Set', + 'TGeogPoint Instant', 'TGeogPoint Discrete Sequence', 'TGeogPoint Sequence', 'TGeogPoint Sequence Set'] + ) + def test_from_tpoint_constructor(self, tpoint, expected): + stb = STBox.from_tpoint(tpoint) + assert isinstance(stb, STBox) + assert str(stb) == expected + + @pytest.mark.parametrize( + 'geo, expected', + [ + (Point(1,1), 'STBOX X((0,0),(2,2))'), + (LineString([(1,1), (2,2)]), 'STBOX X((0,0),(3,3))'), + (shapely.set_srid(shapely.Point(1,1), 5676), 'SRID=5676;STBOX X((0,0),(2,2))'), + (shapely.set_srid(shapely.LineString([(1,1), (2,2)]), 5676), 'SRID=5676;STBOX X((0,0),(3,3))'), + ], + ids=['Point', 'Line string', 'Geodetic Point', 'Geodetic line string'] + ) + def test_from_geo_expand_space_constructor(self, geo, expected): + stb = STBox.from_expanding_bounding_box(geo, 1) + assert isinstance(stb, STBox) + assert str(stb) == expected + + @pytest.mark.parametrize( + 'tpoint, expected', + [ + (TGeomPointInst('Point(1 1)@2019-09-01'), + 'STBOX XT(((0,0),(2,2)),[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + (TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}'), + 'STBOX XT(((0,0),(3,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + 'STBOX XT(((0,0),(3,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}'), + 'STBOX XT(((0,0),(3,3)),[2019-09-01 00:00:00+00, 2019-09-05 00:00:00+00])'), + (TGeogPointInst('Point(1 1)@2019-09-01'), + 'SRID=4326;GEODSTBOX XT(((0,0),(2,2)),[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + (TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}'), + 'SRID=4326;GEODSTBOX XT(((0,0),(3,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + 'SRID=4326;GEODSTBOX XT(((0,0),(3,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}'), + 'SRID=4326;GEODSTBOX XT(((0,0),(3,3)),[2019-09-01 00:00:00+00, 2019-09-05 00:00:00+00])'), + ], + ids=['TGeomPoint Instant', 'TGeomPoint Discrete Sequence', 'TGeomPoint Sequence', 'TGeomPoint Sequence Set', + 'TGeogPoint Instant', 'TGeogPoint Discrete Sequence', 'TGeogPoint Sequence', 'TGeogPoint Sequence Set'] + ) + def test_from_tpoint_expand_space_constructor(self, tpoint, expected): + stb = STBox.from_expanding_bounding_box(tpoint, 1) + assert isinstance(stb, STBox) + assert str(stb) == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (STBox('STBOX X((1,1),(2,2))'), 'STBOX X((0,0),(3,3))'), + (STBox('STBOX Z((1,1,1),(2,2,2))'), 'STBOX Z((0,0,0),(3,3,3))'), + (STBox('STBOX XT(((1,1),(2,2)),[2019-09-01,2019-09-02])'), + 'STBOX XT(((0,0),(3,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (STBox('STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01,2019-09-02])'), + 'STBOX ZT(((0,0,0),(3,3,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + ], + ids=['STBox X', 'STBox Z', 'STBox XT', 'STBox ZT'] + ) + def test_from_geo_expand_space_constructor(self, stbox, expected): + stb = STBox.from_expanding_bounding_box(stbox, 1) + assert isinstance(stb, STBox) + assert str(stb) == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (STBox('STBOX X((1,1),(3,3))'), + [STBox('STBOX X((1,1),(2,2))'), STBox('STBOX X((2,1),(3,2))'), + STBox('STBOX X((1,2),(2,3))'), STBox('STBOX X((2,2),(3,3))')]), + (STBox('STBOX Z((1,1,1),(3,3,3))'), + [STBox('STBOX Z((1,1,1),(2,2,2))'), STBox('STBOX Z((2,1,1),(3,2,2))'), + STBox('STBOX Z((1,2,1),(2,3,2))'), STBox('STBOX Z((2,2,1),(3,3,2))'), + STBox('STBOX Z((1,1,2),(2,2,3))'), STBox('STBOX Z((2,1,2),(3,2,3))'), + STBox('STBOX Z((1,2,2),(2,3,3))'), STBox('STBOX Z((2,2,2),(3,3,3))')]), + (STBox('STBOX XT(((1,1),(3,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + [STBox('STBOX XT(((1,1),(2,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + STBox('STBOX XT(((2,1),(3,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + STBox('STBOX XT(((1,2),(2,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + STBox('STBOX XT(((2,2),(3,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])')]), + (STBox('STBOX ZT(((1,1,1),(3,3,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + [STBox('STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + STBox('STBOX ZT(((2,1,1),(3,2,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + STBox('STBOX ZT(((1,2,1),(2,3,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + STBox('STBOX ZT(((2,2,1),(3,3,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + STBox('STBOX ZT(((1,1,2),(2,2,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + STBox('STBOX ZT(((2,1,2),(3,2,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + STBox('STBOX ZT(((1,2,2),(2,3,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + STBox('STBOX ZT(((2,2,2),(3,3,3)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])')]), + ], + ids=['STBox X', 'STBox Z', 'STBox XT', 'STBox ZT'] + ) + def test_from_quad_split_flat(self, stbox, expected): + stblist = STBox.quad_split_flat(stbox) + assert stblist == expected + + @pytest.mark.parametrize( + 'stbox', + [stbx, stbz, stbt, stbxt, stbzt], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_from_as_constructor(self, stbox): + assert stbox == stbox.from_wkb(stbox.as_wkb()) + assert stbox == stbox.from_hexwkb(stbox.as_hexwkb()) + + @pytest.mark.parametrize( + 'stbox', + [stbx, stbz, stbt, stbxt, stbzt], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_copy_constructor(self, stbox): + other = copy(stbox) + assert stbox == other + assert stbox is not other + + +class TestSTBoxOutputs(TestSTBox): + stbx = STBox('STBOX X((1,1),(2,2))') + stbz = STBox('STBOX Z((1,1,1),(2,2,2))') + stbt = STBox('STBOX T([2019-09-01,2019-09-02])') + stbxt = STBox('STBOX XT(((1,1),(2,2)),[2019-09-01,2019-09-02])') + stbzt = STBox('STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, 'STBOX X((1,1),(2,2))'), + (stbz, 'STBOX Z((1,1,1),(2,2,2))'), + (stbt, 'STBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (stbxt, 'STBOX XT(((1,1),(2,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (stbzt, 'STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_str(self, stbox, expected): + assert str(stbox) == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, 'STBox(STBOX X((1,1),(2,2)))'), + (stbz, 'STBox(STBOX Z((1,1,1),(2,2,2)))'), + (stbt, 'STBox(STBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00]))'), + (stbxt, 'STBox(STBOX XT(((1,1),(2,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00]))'), + (stbzt, 'STBox(STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00]))'), + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_repr(self, stbox, expected): + assert repr(stbox) == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, '0101000000000000F03F0000000000000040000000000000F03F0000000000000040'), + (stbz, '0111000000000000F03F0000000000000040000000000000F03F0000000000000040000000000000F03F0000000000000040'), + (stbt, '010221000300A01E4E713402000000F66B85340200'), + (stbxt, '010321000300A01E4E713402000000F66B85340200000000000000F03F0000000000000040000000000000F03F0000000000000040'), + (stbzt, '011321000300A01E4E713402000000F66B85340200000000000000F03F0000000000000040' + '000000000000F03F0000000000000040000000000000F03F0000000000000040'), + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_as_hexwkb(self, stbox, expected): + assert stbox.as_hexwkb() == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, Polygon([(1,1),(1,2),(2,2),(2,1),(1,1)])), + (stbxt, Polygon([(1,1),(1,2),(2,2),(2,1),(1,1)])), + ], + ids=['STBox X', 'STBox XT'] + ) + def test_to_geometry(self, stbox, expected): + stb = stbox.to_geometry() + assert isinstance(stb, Polygon) + assert stb == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbt, Period('[2019-09-01, 2019-09-02]')), + (stbxt, Period('[2019-09-01, 2019-09-02]')), + ], + ids=['STBox X', 'STBox XT'] + ) + def test_to_period(self, stbox, expected): + stb = stbox.to_period() + assert isinstance(stb, Period) + assert stb == expected + + +class TestSTBoxAccessors(TestSTBox): + stbx = STBox('STBOX X((1,1),(2,2))') + stbz = STBox('STBOX Z((1,1,1),(2,2,2))') + stbt = STBox('STBOX T([2019-09-01,2019-09-02])') + stbxt = STBox('STBOX XT(((1,1),(2,2)),[2019-09-01,2019-09-02])') + stbzt = STBox('STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01,2019-09-02])') + + gstbx = STBox('GEODSTBOX X((1,1),(2,2))') + gstbz = STBox('GEODSTBOX Z((1,1,1),(2,2,2))') + gstbt = STBox('GEODSTBOX T([2019-09-01,2019-09-02])') + gstbxt = STBox('GEODSTBOX XT(((1,1),(2,2)),[2019-09-01,2019-09-02])') + gstbzt = STBox('GEODSTBOX ZT(((1,1,1),(2,2,2)),[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, True), + (stbz, True), + (stbt, False), + (stbxt, True), + (stbzt, True) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_has_xy(self, stbox, expected): + assert stbox.has_xy() == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, False), + (stbz, True), + (stbt, False), + (stbxt, False), + (stbzt, True) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_has_z(self, stbox, expected): + assert stbox.has_z() == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, False), + (stbz, False), + (stbt, True), + (stbxt, True), + (stbzt, True) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_has_t(self, stbox, expected): + assert stbox.has_t() == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, False), + (stbz, False), + (stbt, False), + (stbxt, False), + (stbzt, False), + (gstbx, True), + (gstbz, True), + (gstbt, True), + (gstbxt, True), + (gstbzt, True) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT', + 'Geodetic STBox X', 'Geodetic STBox Z', 'Geodetic STBox T', 'Geodetic STBox XT', 'Geodetic STBox ZT'] + ) + def test_geodetic(self, stbox, expected): + assert stbox.geodetic() == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, 1), + (stbz, 1), + (stbt, None), + (stbxt, 1), + (stbzt, 1) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_xmin_ymin(self, stbox, expected): + assert stbox.xmin() == expected + assert stbox.ymin() == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, None), + (stbz, 1), + (stbt, None), + (stbxt, None), + (stbzt, 1) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_zmin(self, stbox, expected): + assert stbox.zmin() == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, 2), + (stbz, 2), + (stbt, None), + (stbxt, 2), + (stbzt, 2) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_xmax_ymax(self, stbox, expected): + assert stbox.xmax() == expected + assert stbox.ymax() == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, None), + (stbz, 2), + (stbt, None), + (stbxt, None), + (stbzt, 2) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_zmax(self, stbox, expected): + assert stbox.zmax() == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, None), + (stbz, None), + (stbt, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (stbxt, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (stbzt, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_tmin(self, stbox, expected): + assert stbox.tmin() == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, None), + (stbz, None), + (stbt, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (stbxt, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (stbzt, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_tmax(self, stbox, expected): + assert stbox.tmax() == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, None), + (stbz, None), + (stbt, True), + (stbxt, True), + (stbzt, True) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_tmin_tmax_inc(self, stbox, expected): + assert stbox.tmin_inc() == expected + assert stbox.tmax_inc() == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, 0), + (stbz, 0), + (stbt, 0), + (stbxt, 0), + (stbzt, 0), + (gstbx, 4326), + (gstbz, 4326), + (gstbt, 4326), + (gstbxt, 4326), + (gstbzt, 4326) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT', + 'Geodetic STBox X', 'Geodetic STBox Z', 'Geodetic STBox T', 'Geodetic STBox XT', 'Geodetic STBox ZT'] + ) + def test_srid(self, stbox, expected): + assert stbox.srid() == expected + + @pytest.mark.parametrize( + 'stbox', + [stbx, stbz, stbt, stbxt, stbzt], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_set_srid(self, stbox): + assert stbox.set_srid(5676).srid() == 5676 + + +class TestSTBoxTransformations(TestSTBox): + stbx = STBox('STBOX X((1,1),(2,2))') + stbz = STBox('STBOX Z((1,1,1),(2,2,2))') + stbt = STBox('STBOX T([2019-09-01,2019-09-02])') + stbxt = STBox('STBOX XT(((1,1),(2,2)),[2019-09-01,2019-09-02])') + stbzt = STBox('STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbx, STBox('STBOX X((0,0),(3,3))')), + (stbz, STBox('STBOX Z((0,0,0),(3,3,3))')), + (stbxt, STBox('STBOX XT(((0,0),(3,3)),[2019-09-01, 2019-09-02])')), + (stbzt, STBox('STBOX ZT(((0,0,0),(3,3,3)),[2019-09-01, 2019-09-02])')), + ], + ids=['STBox X', 'STBox Z', 'STBox XT', 'STBox ZT'] + ) + def test_expand_float(self, stbox, expected): + stb = stbox.expand(1) + assert isinstance(stb, STBox) + assert stb == expected + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (stbt, STBox('STBOX T([2019-08-31, 2019-09-03])')), + (stbxt, STBox('STBOX XT(((1,1),(2,2)),[2019-08-31, 2019-09-03])')), + (stbzt, STBox('STBOX ZT(((1,1,1),(2,2,2)),[2019-08-31, 2019-09-03])')), + ], + ids=['STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_expand_time(self, stbox, expected): + stb = stbox.expand(timedelta(days=1)) + assert isinstance(stb, STBox) + assert stb == expected + + ###################################### + # THIS TEST DOES NOT WORK CORRECTLY + ###################################### + @pytest.mark.parametrize( + 'stbox, delta, expected', + [(stbt, timedelta(days=4), + STBox('STBOX T([2019-09-01,2019-09-02])')), + (stbt, timedelta(days=-4), + STBox('STBOX T([2019-09-01,2019-09-02])')), + (stbt, timedelta(hours=2), + STBox('STBOX T([2019-09-01,2019-09-02])')), + (stbt, timedelta(hours=-2), + STBox('STBOX T([2019-09-01,2019-09-02])')), + ], + ids=['positive days', 'negative days', 'positive hours', 'negative hours'] + ) + def test_shift(self, stbox, delta, expected): + assert stbox.shift(delta) == expected + + ###################################### + # THIS TEST DOES NOT WORK CORRECTLY + ###################################### + @pytest.mark.parametrize( + 'stbox, delta, expected', + [(stbt, timedelta(days=4), + STBox('STBOX T([2019-09-01,2019-09-02])')), + (stbt, timedelta(hours=2), + STBox('STBOX T([2019-09-01,2019-09-02])')), + ], + ids=['positive days', 'positive hours'] + ) + def test_tscale(self, stbox, delta, expected): + assert stbox.tscale(delta) == expected + + ###################################### + # THIS TEST DOES NOT WORK CORRECTLY + ###################################### + def test_shift_tscale(self): + assert self.stbt.shift_tscale(timedelta(days=4), timedelta(hours=4)) == \ + STBox('STBOX T([2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'stbox, expected', + [ + (STBox('STBOX X((1.123456789,1.123456789),(2.123456789,2.123456789))'), + STBox('STBOX X((1.12,1.12),(2.12,2.12))')), + (STBox('STBOX Z((1.123456789,1.123456789,1.123456789),(2.123456789,2.123456789,2.123456789))'), + STBox('STBOX Z((1.12,1.12,1.12),(2.12,2.12,2.12))')), + (STBox('STBOX XT(((1.123456789,1.123456789),(2.123456789,2.123456789)),[2019-09-01, 2019-09-02])'), + STBox('STBOX XT(((1.12,1.12),(2.12,2.12)),[2019-09-01, 2019-09-03])')), + (STBox('STBOX ZT(((1.123456789,1.123456789,1.123456789),(2.123456789,2.123456789,2.123456789)),' + '[2019-09-01, 2019-09-02])'), + STBox('STBOX ZT(((1.12,1.12,1.12),(2.12,2.12,2.12)),[2019-09-01, 2019-09-02])')), + ], + ids=['STBox X', 'STBox Z', 'STBox XT', 'STBox ZT'] + ) + def test_round(self, stbox, expected): + assert stbox.round(maxdd=2) + + +class TestSTBoxTopologicalFunctions(TestSTBox): + stbx = STBox('STBOX X((1,1),(2,2))') + stbz = STBox('STBOX Z((1,1,1),(2,2,2))') + stbt = STBox('STBOX T([2019-09-01,2019-09-02])') + stbxt = STBox('STBOX XT(((1,1),(2,2)),[2019-09-01,2019-09-02])') + stbzt = STBox('STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'stbox, argument, expected', + [ + (stbx, STBox('STBOX X((1,1),(3,3))'), False), + (stbx, STBox('STBOX X((2,2),(3,3))'), True), + (stbz, STBox('STBOX Z((1,1,1),(3,3,3))'), False), + (stbz, STBox('STBOX Z((2,2,2),(3,3,3))'), True), + (stbt, STBox('STBOX T([2019-09-01,2019-09-03])'), False), + (stbt, STBox('STBOX T([2019-09-02,2019-09-03])'), True), + (stbxt, STBox('STBOX XT(((1,1),(3,3)),[2019-09-01,2019-09-03])'), False), + (stbxt, STBox('STBOX XT(((2,2),(3,3)),[2019-09-02,2019-09-03])'), True), + (stbzt, STBox('STBOX ZT(((1,1,1),(3,3,3)),[2019-09-01,2019-09-03])'), False), + (stbzt, STBox('STBOX ZT(((2,2,2),(3,3,3)),[2019-09-01,2019-09-03])'), True), + ], + ids=['STBox X False', 'STBox X True', 'STBox Z False', 'STBox Z True', + 'STBox T False', 'STBox T True', 'STBox XT False', 'STBox XT True', + 'STBox ZT False', 'STBox ZT True'] + ) + def test_is_adjacent(self, stbox, argument, expected): + assert stbox.is_adjacent(argument) == expected + + @pytest.mark.parametrize( + 'stbox, argument, expected', + [ + (stbx, STBox('STBOX X((2,2),(3,3))'), False), + (stbx, STBox('STBOX X((1,1),(3,3))'), True), + (stbz, STBox('STBOX Z((2,2,2),(3,3,3))'), False), + (stbz, STBox('STBOX Z((1,1,1),(3,3,3))'), True), + (stbt, STBox('STBOX T([2019-09-02,2019-09-03])'), False), + (stbt, STBox('STBOX T([2019-09-01,2019-09-03])'), True), + (stbxt, STBox('STBOX XT(((2,2),(3,3)),[2019-09-02,2019-09-03])'), False), + (stbxt, STBox('STBOX XT(((1,1),(3,3)),[2019-09-01,2019-09-03])'), True), + (stbzt, STBox('STBOX ZT(((2,2,2),(3,3,3)),[2019-09-01,2019-09-03])'), False), + (stbzt, STBox('STBOX ZT(((1,1,1),(3,3,3)),[2019-09-01,2019-09-03])'), True), + ], + ids=['STBox X False', 'STBox X True', 'STBox Z False', 'STBox Z True', + 'STBox T False', 'STBox T True', 'STBox XT False', 'STBox XT True', + 'STBox ZT False', 'STBox ZT True'] + ) + def test_is_contained_in_contains(self, stbox, argument, expected): + assert stbox.is_contained_in(argument) == expected + assert argument.contains(stbox) == expected + + @pytest.mark.parametrize( + 'stbox, argument, expected', + [ + (stbx, STBox('STBOX X((3,3),(3,3))'), False), + (stbx, STBox('STBOX X((1,1),(3,3))'), True), + (stbz, STBox('STBOX Z((3,3,3),(3,3,3))'), False), + (stbz, STBox('STBOX Z((1,1,1),(3,3,3))'), True), + (stbt, STBox('STBOX T([2019-09-03,2019-09-03])'), False), + (stbt, STBox('STBOX T([2019-09-01,2019-09-03])'), True), + (stbxt, STBox('STBOX XT(((3,3),(3,3)),[2019-09-02,2019-09-03])'), False), + (stbxt, STBox('STBOX XT(((1,1),(3,3)),[2019-09-01,2019-09-03])'), True), + (stbzt, STBox('STBOX ZT(((3,3,3),(3,3,3)),[2019-09-01,2019-09-03])'), False), + (stbzt, STBox('STBOX ZT(((1,1,1),(3,3,3)),[2019-09-01,2019-09-03])'), True), + ], + ids=['STBox X False', 'STBox X True', 'STBox Z False', 'STBox Z True', + 'STBox T False', 'STBox T True', 'STBox XT False', 'STBox XT True', + 'STBox ZT False', 'STBox ZT True'] + ) + def test_overlaps(self, stbox, argument, expected): + assert stbox.overlaps(argument) == expected + + @pytest.mark.parametrize( + 'stbox, argument, expected', + [ + (stbx, STBox('STBOX X((3,3),(3,3))'), False), + (stbx, STBox('STBOX X((1,1),(2,2))'), True), + (stbz, STBox('STBOX Z((3,3,3),(3,3,3))'), False), + (stbz, STBox('STBOX Z((1,1,1),(2,2,2))'), True), + (stbt, STBox('STBOX T([2019-09-03,2019-09-03])'), False), + (stbt, STBox('STBOX T([2019-09-01,2019-09-02])'), True), + (stbxt, STBox('STBOX X((3,3),(3,3))'), False), + (stbxt, STBox('STBOX X((1,1),(2,2))'), True), + (stbzt, STBox('STBOX Z((3,3,3),(3,3,3))'), False), + (stbzt, STBox('STBOX Z((1,1,1),(2,2,2))'), True), + ], + ids=['STBox X False', 'STBox X True', 'STBox Z False', 'STBox Z True', + 'STBox T False', 'STBox T True', 'STBox XT False', 'STBox XT True', + 'STBox ZT False', 'STBox ZT True'] + ) + def test_is_same(self, stbox, argument, expected): + assert stbox.is_same(argument) == expected + + +class TestSTBoxPositionFunctions(TestSTBox): + stbx = STBox('STBOX X((1,1),(2,2))') + stbz = STBox('STBOX Z((1,1,1),(2,2,2))') + stbt = STBox('STBOX T([2019-09-01,2019-09-02])') + stbxt = STBox('STBOX XT(((1,1),(2,2)),[2019-09-01,2019-09-02])') + stbzt = STBox('STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'stbox, argument, expected', + [ + (stbx, STBox('STBOX X((1,1),(3,3))'), False), + (stbx, STBox('STBOX X((3,3),(4,4))'), True), + (stbz, STBox('STBOX Z((1,1,1),(3,3,3))'), False), + (stbz, STBox('STBOX Z((3,3,3),(4,4,4))'), True), + (stbxt, STBox('STBOX XT(((1,1),(3,3)),[2019-09-01,2019-09-03])'), False), + (stbxt, STBox('STBOX XT(((3,3),(4,4)),[2019-09-02,2019-09-03])'), True), + (stbzt, STBox('STBOX ZT(((1,1,1),(3,3,3)),[2019-09-01,2019-09-03])'), False), + (stbzt, STBox('STBOX ZT(((3,3,3),(4,4,4)),[2019-09-01,2019-09-03])'), True) + ], + ids=['STBox X False', 'STBox X True', 'STBox Z False', 'STBox Z True', + 'STBox XT False', 'STBox XT True', 'STBox ZT False', 'STBox ZT True'] + ) + def test_is_left_right(self, stbox, argument, expected): + assert stbox.is_left(argument) == expected + assert argument.is_right(stbox) == expected + assert stbox.is_below(argument) == expected + assert argument.is_above(stbox) == expected + + @pytest.mark.parametrize( + 'stbox, argument, expected', + [ + (stbx, STBox('STBOX X((1,1),(1,1))'), False), + (stbx, STBox('STBOX X((3,3),(4,4))'), True), + (stbz, STBox('STBOX Z((1,1,1),(1,1,1))'), False), + (stbz, STBox('STBOX Z((3,3,3),(4,4,4))'), True), + (stbxt, STBox('STBOX XT(((1,1),(1,1)),[2019-09-01,2019-09-03])'), False), + (stbxt, STBox('STBOX XT(((3,3),(4,4)),[2019-09-02,2019-09-03])'), True), + (stbzt, STBox('STBOX ZT(((1,1,1),(1,1,1)),[2019-09-01,2019-09-03])'), False), + (stbzt, STBox('STBOX ZT(((3,3,3),(4,4,4)),[2019-09-01,2019-09-03])'), True) + ], + ids=['STBox X False', 'STBox X True', 'STBox Z False', 'STBox Z True', + 'STBox XT False', 'STBox XT True', 'STBox ZT False', 'STBox ZT True'] + ) + def test_is_over_or_left(self, stbox, argument, expected): + assert stbox.is_over_or_left(argument) == expected + assert stbox.is_over_or_below(argument) == expected + + @pytest.mark.parametrize( + 'stbox, argument, expected', + [ + (stbx, STBox('STBOX X((0,0),(1,1))'), False), + (stbx, STBox('STBOX X((3,3),(4,4))'), True), + (stbz, STBox('STBOX Z((0,0,0),(1,1,1))'), False), + (stbz, STBox('STBOX Z((3,3,3),(4,4,4))'), True), + (stbxt, STBox('STBOX XT(((0,0),(1,1)),[2019-09-01,2019-09-03])'), False), + (stbxt, STBox('STBOX XT(((3,3),(4,4)),[2019-09-02,2019-09-03])'), True), + (stbzt, STBox('STBOX ZT(((0,0,0),(1,1,1)),[2019-09-01,2019-09-03])'), False), + (stbzt, STBox('STBOX ZT(((3,3,3),(4,4,4)),[2019-09-01,2019-09-03])'), True) + ], + ids=['STBox X False', 'STBox X True', 'STBox Z False', 'STBox Z True', + 'STBox XT False', 'STBox XT True', 'STBox ZT False', 'STBox ZT True'] + ) + def test_is_over_or_right(self, stbox, argument, expected): + assert argument.is_over_or_right(stbox) == expected + assert argument.is_over_or_above(stbox) == expected + + @pytest.mark.parametrize( + 'stbox, argument, expected', + [ + (stbz, STBox('STBOX Z((1,1,1),(1,1,1))'), False), + (stbz, STBox('STBOX Z((3,3,3),(4,4,4))'), True), + (stbzt, STBox('STBOX ZT(((1,1,1),(1,1,1)),[2019-09-01,2019-09-03])'), False), + (stbzt, STBox('STBOX ZT(((3,3,3),(4,4,4)),[2019-09-01,2019-09-03])'), True) + ], + ids=['STBox Z False', 'STBox Z True', 'STBox ZT False', 'STBox ZT True'] + ) + def test_is_over_or_front(self, stbox, argument, expected): + assert stbox.is_over_or_front(argument) == expected + + @pytest.mark.parametrize( + 'stbox, argument, expected', + [ + (stbz, STBox('STBOX Z((2,2,2),(2,2,2))'), False), + (stbz, STBox('STBOX Z((1,1,1),(2,2,2))'), True), + (stbzt, STBox('STBOX ZT(((2,2,2),(2,2,2)),[2019-09-01,2019-09-03])'), False), + (stbzt, STBox('STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01,2019-09-03])'), True) + ], + ids=['STBox Z False', 'STBox Z True', 'STBox ZT False', 'STBox ZT True'] + ) + def test_is_over_or_behind(self, stbox, argument, expected): + assert stbox.is_over_or_behind(argument) == expected + + @pytest.mark.parametrize( + 'stbox, argument, expected', + [ + (stbt, STBox('STBOX T([2019-09-01,2019-09-03])'), False), + (stbt, STBox('STBOX T([2019-09-03,2019-09-03])'), True), + (stbxt, STBox('STBOX XT(((1,1),(3,3)),[2019-09-01,2019-09-03])'), False), + (stbxt, STBox('STBOX XT(((3,3),(4,4)),[2019-09-03,2019-09-03])'), True), + (stbzt, STBox('STBOX ZT(((1,1,1),(3,3,3)),[2019-09-01,2019-09-03])'), False), + (stbzt, STBox('STBOX ZT(((3,3,3),(4,4,4)),[2019-09-03,2019-09-03])'), True) + ], + ids=['STBox T False', 'STBox T True', 'STBox XT False', 'STBox XT True', + 'STBox ZT False', 'STBox ZT True'] + ) + def test_is_before_after(self, stbox, argument, expected): + assert stbox.is_before(argument) == expected + assert argument.is_after(stbox) == expected + + @pytest.mark.parametrize( + 'stbox, argument, expected', + [ + (stbt, STBox('STBOX T([2019-09-01,2019-09-01])'), False), + (stbt, STBox('STBOX T([2019-09-03,2019-09-03])'), True), + (stbxt, STBox('STBOX XT(((1,1),(3,3)),[2019-09-01,2019-09-01])'), False), + (stbxt, STBox('STBOX XT(((3,3),(4,4)),[2019-09-03,2019-09-03])'), True), + (stbzt, STBox('STBOX ZT(((1,1,1),(3,3,3)),[2019-09-01,2019-09-01])'), False), + (stbzt, STBox('STBOX ZT(((3,3,3),(4,4,4)),[2019-09-03,2019-09-03])'), True) + ], + ids=['STBox T False', 'STBox T True', 'STBox XT False', 'STBox XT True', + 'STBox ZT False', 'STBox ZT True'] + ) + def test_is_over_or_before(self, stbox, argument, expected): + assert stbox.is_over_or_before(argument) == expected + + @pytest.mark.parametrize( + 'stbox, argument, expected', + [ + (stbt, STBox('STBOX T([2019-09-03,2019-09-03])'), False), + (stbt, STBox('STBOX T([2019-09-01,2019-09-03])'), True), + (stbxt, STBox('STBOX XT(((1,1),(3,3)),[2019-09-02,2019-09-03])'), False), + (stbxt, STBox('STBOX XT(((3,3),(4,4)),[2019-09-01,2019-09-03])'), True), + (stbzt, STBox('STBOX ZT(((1,1,1),(3,3,3)),[2019-09-02,2019-09-03])'), False), + (stbzt, STBox('STBOX ZT(((3,3,3),(4,4,4)),[2019-09-01,2019-09-03])'), True) + ], + ids=['STBox T False', 'STBox T True', 'STBox XT False', 'STBox XT True', + 'STBox ZT False', 'STBox ZT True'] + ) + def test_is_over_or_after(self, stbox, argument, expected): + assert stbox.is_over_or_after(argument) == expected + + +class TestSTBoxSetFunctions(TestSTBox): + stbx1 = STBox('STBOX X((1,1),(2,2))') + stbz1 = STBox('STBOX Z((1,1,1),(2,2,2))') + stbt1 = STBox('STBOX T([2019-09-01,2019-09-02])') + stbxt1 = STBox('STBOX XT(((1,1),(2,2)),[2019-09-01,2019-09-02])') + stbzt1 = STBox('STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01,2019-09-02])') + stbx2 = STBox('STBOX X((2,2),(3,3))') + stbz2 = STBox('STBOX Z((2,2,2),(3,3,3))') + stbt2 = STBox('STBOX T([2019-09-02,2019-09-03])') + stbxt2 = STBox('STBOX XT(((2,2),(3,3)),[2019-09-02,2019-09-03])') + stbzt2 = STBox('STBOX ZT(((2,2,2),(3,3,3)),[2019-09-02,2019-09-03])') + + @pytest.mark.parametrize( + 'stbox1, argument', + [ + (stbx1, STBox('STBOX X((1,1),(3,3))')), + (stbz1, STBox('STBOX Z((1,1,1),(3,3,3))')), + (stbt1, STBox('STBOX T([2019-09-01,2019-09-03])')), + (stbxt1, STBox('STBOX XT(((1,1),(3,3)),[2019-09-01,2019-09-03])')), + (stbzt1, STBox('STBOX ZT(((1,1,1),(3,3,3)),[2019-09-01,2019-09-03])')) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_in(self, stbox1, argument): + assert stbox1 in argument + + @pytest.mark.parametrize( + 'stbox1, stbox2, expected', + [ + (stbx1, stbx2, STBox('STBOX X((1,1),(3,3))')), + (stbz1, stbz2, STBox('STBOX Z((1,1,1),(3,3,3))')), + (stbt1, stbt2, STBox('STBOX T([2019-09-01,2019-09-03])')), + (stbxt1, stbxt2, STBox('STBOX XT(((1,1),(3,3)),[2019-09-01,2019-09-03])')), + (stbzt1, stbzt2, STBox('STBOX ZT(((1,1,1),(3,3,3)),[2019-09-01,2019-09-03])')) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_union(self, stbox1, stbox2, expected): + assert stbox1.union(stbox2) == expected + assert stbox1 + stbox2 == expected + + @pytest.mark.parametrize( + 'stbox1, stbox2, expected', + [ + (stbx1, stbx2, STBox('STBOX X((2,2),(2,2))')), + (stbz1, stbz2, STBox('STBOX Z((2,2,2),(2,2,2))')), + (stbt1, stbt2, STBox('STBOX T([2019-09-02,2019-09-02])')), + (stbxt1, stbxt2, STBox('STBOX XT(((2,2),(2,2)),[2019-09-02,2019-09-02])')), + (stbzt1, stbzt2, STBox('STBOX ZT(((2,2,2),(2,2,2)),[2019-09-02,2019-09-02])')) + ], + ids=['STBox X', 'STBox Z', 'STBox T', 'STBox XT', 'STBox ZT'] + ) + def test_intersection(self, stbox1, stbox2, expected): + assert stbox1.intersection(stbox2) == expected + assert stbox1 * stbox2 == expected + + +class TestSTBoxDistanceFunctions(TestSTBox): + stbx = STBox('STBOX X((1,1),(2,2))') + stbz = STBox('STBOX Z((1,1,1),(2,2,2))') + stbt = STBox('STBOX T([2019-09-01,2019-09-02])') + stbxt = STBox('STBOX XT(((1,1),(2,2)),[2019-09-01,2019-09-02])') + stbzt = STBox('STBOX ZT(((1,1,1),(2,2,2)),[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'stbox, argument, expected', + [ + (stbx, STBox('STBOX X((1,1),(3,3))'), 0), + (stbz, STBox('STBOX Z((3,3,3),(3,3,3))'), math.sqrt(3)), + (stbxt, STBox('STBOX XT(((1,1),(3,3)),[2019-09-01,2019-09-03])'), 0), + (stbzt, STBox('STBOX ZT(((3,3,3),(3,3,3)),[2019-09-01,2019-09-03])'), math.sqrt(3)), + ], + ids=['STBox X', 'STBox Z', 'STBox XT', 'STBox ZT'] + ) + def test_nearest_approach_distance(self, stbox, argument, expected): + assert stbox.nearest_approach_distance(argument) == expected + + +class TestSTBoxComparisons(TestSTBox): + stbxt = STBox('STBOX XT(((1,1),(2,2)),[2019-09-01,2019-09-02])') + other = STBox('STBOX XT(((3,3),(4,4)),[2019-09-01,2019-09-02])') + + def test_eq(self): + _ = self.stbxt == self.other + + def test_ne(self): + _ = self.stbxt != self.other + + def test_lt(self): + _ = self.stbxt < self.other + + def test_le(self): + _ = self.stbxt <= self.other + + def test_gt(self): + _ = self.stbxt > self.other + + def test_ge(self): + _ = self.stbxt >= self.other + diff --git a/pymeos/tests/boxes/tbox_test.py b/pymeos/tests/boxes/tbox_test.py new file mode 100644 index 00000000..c611950e --- /dev/null +++ b/pymeos/tests/boxes/tbox_test.py @@ -0,0 +1,664 @@ +from copy import copy +from datetime import datetime, timezone, timedelta +from spans.types import intrange, floatrange + +import pytest + +from pymeos import TBox, TInterpolation, TimestampSet, Period, PeriodSet, \ + TInt, TIntInst, TIntSeq, TIntSeqSet, TFloat, TFloatInst, TFloatSeq, TFloatSeqSet + +from tests.conftest import TestPyMEOS + + +class TestTBox(TestPyMEOS): + + @staticmethod + def assert_tbox_equality(tbox: TBox, + xmin: float = None, + xmax: float = None, + tmin: datetime = None, + tmax: datetime = None, + xmin_inc: bool = None, + xmax_inc: bool = None, + tmin_inc: bool = None, + tmax_inc: bool = None): + if xmin is not None: + assert tbox.xmin() == xmin + if xmax is not None: + assert tbox.xmax() == xmax + if tmin is not None: + assert tbox.tmin() == tmin + if tmax is not None: + assert tbox.tmax() == tmax + if xmin_inc is not None: + assert tbox.xmin_inc() == xmin_inc + if xmax_inc is not None: + assert tbox.xmax_inc() == xmax_inc + if tmin_inc is not None: + assert tbox.tmin_inc() == tmin_inc + if tmax_inc is not None: + assert tbox.tmax_inc() == tmax_inc + +class TestTBoxConstructors(TestTBox): + tbx = TBox('TBOX X([1,2])') + tbt = TBox('TBOX T([2019-09-01,2019-09-02])') + tbxt = TBox('TBOX XT([1,2],[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'source, type, expected', + [ + ('TBox X([1,2])', TBox, 'TBOX X([1, 2])'), + ('TBox T([2019-09-01,2019-09-02])', TBox, + 'TBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + ('TBox XT([1,2],[2019-09-01,2019-09-02])', TBox, + 'TBOX XT([1, 2],[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])') + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_string_constructor(self, source, type, expected): + tb = type(source) + assert isinstance(tb, type) + assert str(tb) == expected + + def test_hexwkb_constructor(self): + source = '010321000300A01E4E713402000000F66B85340200070003000000000000F03F0000000000000040' + tbox = TBox.from_hexwkb(source) + self.assert_tbox_equality(tbox, 1, 2, + datetime(2019, 9, 1, tzinfo=timezone.utc), + datetime(2019, 9, 2, tzinfo=timezone.utc), + True, True, True, True) + + @pytest.mark.parametrize( + 'value, expected', + [ + (1, 'TBOX X([1, 1])'), + (1.5, 'TBOX X([1.5, 1.5])'), + (intrange(1, 2, True, True), 'TBOX X([1, 2])'), + (floatrange(1.5, 2.5, True, True), 'TBOX X([1.5, 2.5])'), + ], + ids=['int', 'float', 'intrange', 'floatrange'] + ) + def test_from_value_constructor(self, value, expected): + tb = TBox.from_value(value) + assert isinstance(tb, TBox) + assert str(tb) == expected + + # @pytest.mark.parametrize( + # 'time, expected', + # [ + # (datetime(2019, 9, 1), + # 'TBOX T([2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + # (TimestampSet('{2019-09-01, 2019-09-02}'), + # 'TBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + # (Period('[2019-09-01, 2019-09-02]'), + # 'TBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + # (PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}'), + # 'TBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + # ], + # ids=['Timestamp', 'TimestampSet', 'Period', 'PeriodSet'] + # ) + # def test_from_time_constructor(self, time, expected): + # tb = TBox.from_time(time) + # assert isinstance(tb, TBox) + # assert str(tb) == expected + + # @pytest.mark.parametrize( + # 'value, time, expected', + # [ + # (1, datetime(2019, 9, 1), + # 'TBOX XT([1, 1],[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + # (1.5, datetime(2019, 9, 1), + # 'TBOX XT([1.5, 1.5],[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + # (intrange(1, 2, True, True), datetime(2019, 9, 1), + # 'TBOX XT([1, 2],[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + # (floatrange(1.5, 2.5, True, True), datetime(2019, 9, 1), + # 'TBOX XT([1.5, 2.5],[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + # (1, Period('[2019-09-01, 2019-09-02]'), + # 'TBOX XT([1, 1],[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + # (1.5, Period('[2019-09-01, 2019-09-02]'), + # 'TBOX XT([1.5, 1.5],[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + # (intrange(1, 2, True, True), Period('[2019-09-01, 2019-09-02]'), + # 'TBOX XT([1, 2],[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + # (floatrange(1.5, 2.5, True, True), Period('[2019-09-01, 2019-09-02]'), + # 'TBOX XT([1.5, 2.5],[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + # ], + # ids=['int-Timestamp', 'float-Timestamp', 'intrange-Timestamp', 'floatrange-Timestamp', + # 'int-Period', 'float-Period', 'intrange-Period', 'floatrange-Period',] + # ) + # def test_from_value_time_constructor(self, value, time, expected): + # tb = TBox.from_value_time(value, time) + # assert isinstance(tb, TBox) + # assert str(tb) == expected + + @pytest.mark.parametrize( + 'tnumber, expected', + [ + (TIntInst('1@2019-09-01'), 'TBOX XT([1, 1],[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + (TFloatInst('1.5@2019-09-01'), 'TBOX XT([1.5, 1.5],[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00])'), + (TIntSeq('{1@2019-09-01,2@2019-09-02}'), 'TBOX XT([1, 2],[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (TFloatSeq('{1.5@2019-09-01,2.5@2019-09-02}'), 'TBOX XT([1.5, 2.5],[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (TIntSeq('(1@2019-09-01,2@2019-09-02]'), 'TBOX XT([1, 2],(2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (TFloatSeq('[1.5@2019-09-01,2.5@2019-09-02)'), 'TBOX XT([1.5, 2.5),[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00))'), + (TIntSeqSet('{(1@2019-09-01,2@2019-09-02],(1@2019-09-03,2@2019-09-05]}'), + 'TBOX XT([1, 2],(2019-09-01 00:00:00+00, 2019-09-05 00:00:00+00])'), + (TFloatSeqSet('{[1.5@2019-09-01,2.5@2019-09-02),[1.5@2019-09-03,1.5@2019-09-05)}'), + 'TBOX XT([1.5, 2.5),[2019-09-01 00:00:00+00, 2019-09-05 00:00:00+00))'), + ], + ids=['TInt Instant', 'TFloat Instant', 'TInt Discrete Sequence', 'TFloat Discrete Sequence', + 'TInt Sequence', 'TFloat Sequence', 'TInt Sequence Set', 'TFloat Sequence Set'] + ) + def test_from_tnumber_constructor(self, tnumber, expected): + tb = TBox.from_tnumber(tnumber) + assert isinstance(tb, TBox) + assert str(tb) == expected + + @pytest.mark.parametrize( + 'tbox', + [tbx, tbt, tbxt], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_from_as_constructor(self, tbox): + assert tbox == tbox.from_wkb(tbox.as_wkb()) + assert tbox == tbox.from_hexwkb(tbox.as_hexwkb()) + + @pytest.mark.parametrize( + 'tbox', + [tbx, tbt, tbxt], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_copy_constructor(self, tbox): + other = copy(tbox) + assert tbox == other + assert tbox is not other + + +class TestTBoxOutputs(TestTBox): + tbx = TBox('TBOX X([1,2])') + tbt = TBox('TBOX T([2019-09-01,2019-09-02])') + tbxt = TBox('TBOX XT([1,2],[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbx, 'TBOX X([1, 2])'), + (tbt, 'TBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + (tbxt, 'TBOX XT([1, 2],[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00])'), + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_str(self, tbox, expected): + assert str(tbox) == expected + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbx, 'TBox(TBOX X([1, 2]))'), + (tbt, 'TBox(TBOX T([2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00]))'), + (tbxt, 'TBox(TBOX XT([1, 2],[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00]))'), + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_repr(self, tbox, expected): + assert repr(tbox) == expected + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbx, '0101070003000000000000F03F0000000000000040'), + (tbt, '010221000300A01E4E713402000000F66B85340200'), + (tbxt, '010321000300A01E4E713402000000F66B85340200070003000000000000F03F0000000000000040'), + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_as_hexwkb(self, tbox, expected): + assert tbox.as_hexwkb() == expected + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbx, floatrange(1.0, 2.0, True, True)), + (tbxt, floatrange(1.0, 2.0, True, True)), + ], + ids=['TBox X', 'TBox XT'] + ) + def test_to_floatrange(self, tbox, expected): + tb = tbox.to_floatrange() + assert isinstance(tb, floatrange) + assert tb == expected + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbt, Period('[2019-09-01, 2019-09-02]')), + (tbxt, Period('[2019-09-01, 2019-09-02]')), + ], + ids=['TBox X', 'TBox XT'] + ) + def test_to_period(self, tbox, expected): + tb = tbox.to_period() + assert isinstance(tb, Period) + assert tb == expected + + +class TestTBoxAccessors(TestTBox): + tbx = TBox('TBox X([1,2])') + tbt = TBox('TBox T([2019-09-01,2019-09-02])') + tbxt = TBox('TBox XT([1,2],[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbx, True), + (tbt, False), + (tbxt, True) + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_has_x(self, tbox, expected): + assert tbox.has_x() == expected + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbx, False), + (tbt, True), + (tbxt, True) + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_has_t(self, tbox, expected): + assert tbox.has_t() == expected + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbx, 1), + (tbt, None), + (tbxt, 1) + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_xmin(self, tbox, expected): + assert tbox.xmin() == expected + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbx, True), + (tbt, None), + (tbxt, True) + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_xmin_xmax_inc(self, tbox, expected): + assert tbox.xmin_inc() == expected + assert tbox.xmax_inc() == expected + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbx, 2), + (tbt, None), + (tbxt, 2) + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_xmax(self, tbox, expected): + assert tbox.xmax() == expected + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbx, None), + (tbt, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tbxt, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)) + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_tmin(self, tbox, expected): + assert tbox.tmin() == expected + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbx, None), + (tbt, True), + (tbxt, True) + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_tmin_tmax_inc(self, tbox, expected): + assert tbox.tmin_inc() == expected + assert tbox.tmax_inc() == expected + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbx, None), + (tbt, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tbxt, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)) + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_tmax(self, tbox, expected): + assert tbox.tmax() == expected + +class TestTBoxTransformations(TestTBox): + tbx = TBox('TBox X([1,2])') + tbt = TBox('TBox T([2019-09-01,2019-09-02])') + tbxt = TBox('TBox XT([1,2],[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbx, TBox('TBOX X([0, 3])')), + (tbxt, TBox('TBOX XT([0,3],[2019-09-01, 2019-09-02])')), + ], + ids=['TBox X', 'TBox XT'] + ) + def test_expand_float(self, tbox, expected): + tb = tbox.expand(1) + assert isinstance(tb, TBox) + assert tb == expected + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (tbt, TBox('TBOX T([2019-08-31, 2019-09-03])')), + (tbxt, TBox('TBOX XT([1,2],[2019-08-31, 2019-09-03])')), + ], + ids=['TBox T', 'TBox XT'] + ) + def test_expand_time(self, tbox, expected): + tb = tbox.expand(timedelta(days=1)) + assert isinstance(tb, TBox) + assert tb == expected + + ###################################### + # THIS TEST DOES NOT WORK CORRECTLY + ###################################### + @pytest.mark.parametrize( + 'tbox, delta, expected', + [(tbt, timedelta(days=4), + TBox('TBOX T([2019-09-01,2019-09-02])')), + (tbt, timedelta(days=-4), + TBox('TBOX T([2019-09-01,2019-09-02])')), + (tbt, timedelta(hours=2), + TBox('TBOX T([2019-09-01,2019-09-02])')), + (tbt, timedelta(hours=-2), + TBox('TBOX T([2019-09-01,2019-09-02])')), + ], + ids=['positive days', 'negative days', 'positive hours', 'negative hours'] + ) + def test_shift(self, tbox, delta, expected): + assert tbox.shift(delta) == expected + + ###################################### + # THIS TEST DOES NOT WORK CORRECTLY + ###################################### + @pytest.mark.parametrize( + 'tbox, delta, expected', + [(tbt, timedelta(days=4), + TBox('TBOX T([2019-09-01,2019-09-02])')), + (tbt, timedelta(hours=2), + TBox('TBOX T([2019-09-01,2019-09-02])')), + ], + ids=['positive days', 'positive hours'] + ) + def test_tscale(self, tbox, delta, expected): + assert tbox.tscale(delta) == expected + + ###################################### + # THIS TEST DOES NOT WORK CORRECTLY + ###################################### + def test_shift_tscale(self): + assert self.tbt.shift_tscale(timedelta(days=4), timedelta(hours=4)) == \ + TBox('TBOX T([2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'tbox, expected', + [ + (TBox('TBOX X([1.123456789,2.123456789])'), + TBox('TBOX X([1.12,2.12])')), + (TBox('TBOX XT([1.123456789,2.123456789],[2019-09-01, 2019-09-02])'), + TBox('TBOX XT([1.12,2.12],[2019-09-01, 2019-09-03])')), + ], + ids=['TBox X', 'TBox XT'] + ) + def test_round(self, tbox, expected): + assert tbox.round(maxdd=2) + + +class TestTBoxTopologicalFunctions(TestTBox): + tbx = TBox('TBOX X([1,2])') + tbt = TBox('TBOX T([2019-09-01,2019-09-02])') + tbxt = TBox('TBOX XT([1,2],[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'tbox, argument, expected', + [ + (tbx, TBox('TBOX X([1,3])'), False), + (tbx, TBox('TBOX X((2,3])'), True), + (tbt, TBox('TBOX T([2019-09-01,2019-09-03])'), False), + (tbt, TBox('TBOX T((2019-09-02,2019-09-03])'), True), + (tbxt, TBox('TBOX XT([1,3],[2019-09-01,2019-09-03])'), False), + (tbxt, TBox('TBOX XT((2,3],[2019-09-02,2019-09-03])'), True), + ], + ids=['TBox X False', 'TBox X True', 'TBox T False', 'TBox T True', + 'TBox XT False', 'TBox XT True'] + ) + def test_is_adjacent(self, tbox, argument, expected): + assert tbox.is_adjacent(argument) == expected + + @pytest.mark.parametrize( + 'tbox, argument, expected', + [ + (tbx, TBox('TBOX X([2,3])'), False), + (tbx, TBox('TBOX X([1,3])'), True), + (tbt, TBox('TBOX T([2019-09-02,2019-09-03])'), False), + (tbt, TBox('TBOX T([2019-09-01,2019-09-03])'), True), + (tbxt, TBox('TBOX XT([2,3],[2019-09-02,2019-09-03])'), False), + (tbxt, TBox('TBOX XT([1,3],[2019-09-01,2019-09-03])'), True), + ], + ids=['TBox X False', 'TBox X True', 'TBox T False', 'TBox T True', + 'TBox XT False', 'TBox XT True'] + ) + def test_is_contained_in_contains(self, tbox, argument, expected): + assert tbox.is_contained_in(argument) == expected + assert argument.contains(tbox) == expected + + @pytest.mark.parametrize( + 'tbox, argument, expected', + [ + (tbx, TBox('TBOX X([3,3])'), False), + (tbx, TBox('TBOX X([1,3])'), True), + (tbt, TBox('TBOX T([2019-09-03,2019-09-03])'), False), + (tbt, TBox('TBOX T([2019-09-01,2019-09-03])'), True), + (tbxt, TBox('TBOX XT([3,3],[2019-09-02,2019-09-03])'), False), + (tbxt, TBox('TBOX XT([1,3],[2019-09-01,2019-09-03])'), True), + ], + ids=['TBox X False', 'TBox X True', 'TBox T False', 'TBox T True', + 'TBox XT False', 'TBox XT True'] + ) + def test_overlaps(self, tbox, argument, expected): + assert tbox.overlaps(argument) == expected + + @pytest.mark.parametrize( + 'tbox, argument, expected', + [ + (tbx, TBox('TBOX X([3,3])'), False), + (tbx, TBox('TBOX X([1,2])'), True), + (tbt, TBox('TBOX T([2019-09-03,2019-09-03])'), False), + (tbt, TBox('TBOX T([2019-09-01,2019-09-02])'), True), + (tbxt, TBox('TBOX X([3,3])'), False), + (tbxt, TBox('TBOX X([1,2])'), True), + ], + ids=['TBox X False', 'TBox X True', 'TBox T False', 'TBox T True', + 'TBox XT False', 'TBox XT True'] + ) + def test_is_same(self, tbox, argument, expected): + assert tbox.is_same(argument) == expected + + +class TestTBoxPositionFunctions(TestTBox): + tbx = TBox('TBOX X([1,2])') + tbt = TBox('TBOX T([2019-09-01,2019-09-02])') + tbxt = TBox('TBOX XT([1,2],[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'tbox, argument, expected', + [ + (tbx, TBox('TBOX X([1,3])'), False), + (tbx, TBox('TBOX X([3,4])'), True), + (tbxt, TBox('TBOX XT([1,3],[2019-09-01,2019-09-03])'), False), + (tbxt, TBox('TBOX XT([3,4],[2019-09-02,2019-09-03])'), True), + ], + ids=['TBox X False', 'TBox X True', 'TBox XT False', 'TBox XT True'] + ) + def test_is_left_right(self, tbox, argument, expected): + assert tbox.is_left(argument) == expected + assert argument.is_right(tbox) == expected + + @pytest.mark.parametrize( + 'tbox, argument, expected', + [ + (tbx, TBox('TBOX X([1,1])'), False), + (tbx, TBox('TBOX X([3,4])'), True), + (tbxt, TBox('TBOX XT([1,1],[2019-09-01,2019-09-03])'), False), + (tbxt, TBox('TBOX XT([3,4],[2019-09-02,2019-09-03])'), True), + ], + ids=['TBox X False', 'TBox X True', 'TBox XT False', 'TBox XT True'] + ) + def test_is_over_or_left(self, tbox, argument, expected): + assert tbox.is_over_or_left(argument) == expected + + @pytest.mark.parametrize( + 'tbox, argument, expected', + [ + (tbx, TBox('TBOX X([0,1])'), False), + (tbx, TBox('TBOX X([3,4])'), True), + (tbxt, TBox('TBOX XT([0,1],[2019-09-01,2019-09-03])'), False), + (tbxt, TBox('TBOX XT([3,4],[2019-09-02,2019-09-03])'), True), + ], + ids=['TBox X False', 'TBox X True', 'TBox XT False', 'TBox XT True'] + ) + def test_is_over_or_right(self, tbox, argument, expected): + assert argument.is_over_or_right(tbox) == expected + + @pytest.mark.parametrize( + 'tbox, argument, expected', + [ + (tbt, TBox('TBOX T([2019-09-01,2019-09-03])'), False), + (tbt, TBox('TBOX T([2019-09-03,2019-09-03])'), True), + (tbxt, TBox('TBOX XT([1,3],[2019-09-01,2019-09-03])'), False), + (tbxt, TBox('TBOX XT([3,4],[2019-09-03,2019-09-03])'), True), + ], + ids=['TBox T False', 'TBox T True', 'TBox XT False', 'TBox XT True'] + ) + def test_is_before_after(self, tbox, argument, expected): + assert tbox.is_before(argument) == expected + assert argument.is_after(tbox) == expected + + @pytest.mark.parametrize( + 'tbox, argument, expected', + [ + (tbt, TBox('TBOX T([2019-09-01,2019-09-01])'), False), + (tbt, TBox('TBOX T([2019-09-03,2019-09-03])'), True), + (tbxt, TBox('TBOX XT([1,3],[2019-09-01,2019-09-01])'), False), + (tbxt, TBox('TBOX XT([3,4],[2019-09-03,2019-09-03])'), True), + ], + ids=['TBox T False', 'TBox T True', 'TBox XT False', 'TBox XT True'] + ) + def test_is_over_or_before(self, tbox, argument, expected): + assert tbox.is_over_or_before(argument) == expected + + @pytest.mark.parametrize( + 'tbox, argument, expected', + [ + (tbt, TBox('TBOX T([2019-09-03,2019-09-03])'), False), + (tbt, TBox('TBOX T([2019-09-01,2019-09-03])'), True), + (tbxt, TBox('TBOX XT([1,3],[2019-09-02,2019-09-03])'), False), + (tbxt, TBox('TBOX XT([3,4],[2019-09-01,2019-09-03])'), True), + ], + ids=['TBox T False', 'TBox T True', 'TBox XT False', 'TBox XT True'] + ) + def test_is_over_or_after(self, tbox, argument, expected): + assert tbox.is_over_or_after(argument) == expected + + +class TestTBoxSetFunctions(TestTBox): + tbx1 = TBox('TBox X([1,2])') + tbt1 = TBox('TBox T([2019-09-01,2019-09-02])') + tbxt1 = TBox('TBox XT([1,2],[2019-09-01,2019-09-02])') + tbx2 = TBox('TBox X([2,3])') + tbt2 = TBox('TBox T([2019-09-02,2019-09-03])') + tbxt2 = TBox('TBox XT([2,3],[2019-09-02,2019-09-03])') + + @pytest.mark.parametrize( + 'tbox1, tbox2, expected', + [ + (tbx1, tbx2, TBox('TBox X([1,3])')), + (tbt1, tbt2, TBox('TBox T([2019-09-01,2019-09-03])')), + (tbxt1, tbxt2, TBox('TBox XT([1,3],[2019-09-01,2019-09-03])')) + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_union(self, tbox1, tbox2, expected): + assert tbox1.union(tbox2) == expected + assert tbox1 + tbox2 == expected + + @pytest.mark.parametrize( + 'tbox1, tbox2, expected', + [ + (tbx1, tbx2, TBox('TBox X([2,2])')), + (tbt1, tbt2, TBox('TBox T([2019-09-02,2019-09-02])')), + (tbxt1, tbxt2, TBox('TBox XT([2,2],[2019-09-02,2019-09-02])')) + ], + ids=['TBox X', 'TBox T', 'TBox XT'] + ) + def test_intersection(self, tbox1, tbox2, expected): + assert tbox1.intersection(tbox2) == expected + assert tbox1 * tbox2 == expected + + +class TestTBoxDistanceFunctions(TestTBox): + tbx = TBox('TBOX X([1,2])') + tbt = TBox('TBOX T([2019-09-01,2019-09-02])') + tbxt = TBox('TBOX XT([1,2],[2019-09-01,2019-09-02])') + + @pytest.mark.parametrize( + 'tbox, argument, expected', + [ + (tbx, TBox('TBOX X([1,3])'), 0), + (tbx, TBox('TBOX X([3,4])'), 1), + (tbxt, TBox('TBOX XT([1,3],[2019-09-01,2019-09-03])'), 0), + (tbxt, TBox('TBOX XT([3,4],[2019-09-01,2019-09-03])'), 1), + ], + ids=['TBox X Intersection', 'TBox X Distance', + 'TBox XT Intersection', 'TBox XT Distance'] + ) + def test_nearest_approach_distance(self, tbox, argument, expected): + assert tbox.nearest_approach_distance(argument) == expected + + +class TestTBoxComparisons(TestTBox): + tbxt = TBox('TBOX XT([1,2],[2019-09-01,2019-09-02])') + other = TBox('TBOX XT([3,4],[2019-09-03,2019-09-04])') + + def test_eq(self): + _ = self.tbxt == self.other + + def test_ne(self): + _ = self.tbxt != self.other + + def test_lt(self): + _ = self.tbxt < self.other + + def test_le(self): + _ = self.tbxt <= self.other + + def test_gt(self): + _ = self.tbxt > self.other + + def test_ge(self): + _ = self.tbxt >= self.other diff --git a/pymeos/tests/conftest.py b/pymeos/tests/conftest.py new file mode 100644 index 00000000..0db9051b --- /dev/null +++ b/pymeos/tests/conftest.py @@ -0,0 +1,13 @@ +from pymeos import pymeos_initialize, pymeos_finalize + + +def pytest_configure(config): + pymeos_initialize('UTC') + + +def pytest_unconfigure(config): + pymeos_finalize() + + +class TestPyMEOS: + pass diff --git a/pymeos/tests/main/__init__.py b/pymeos/tests/main/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymeos/tests/main/tbool_test.py b/pymeos/tests/main/tbool_test.py new file mode 100644 index 00000000..cb01946a --- /dev/null +++ b/pymeos/tests/main/tbool_test.py @@ -0,0 +1,1428 @@ +from copy import copy +from datetime import datetime, timezone, timedelta + +import pytest + +from pymeos import TBool, TBoolInst, TBoolSeq, TBoolSeqSet, \ + TIntInst, TIntSeq, TIntSeqSet, TInterpolation, \ + TimestampSet, Period, PeriodSet +from tests.conftest import TestPyMEOS + + +class TestTBool(TestPyMEOS): + pass + + +class TestTBoolConstructors(TestTBool): + tbi = TBoolInst('True@2019-09-01') + tbds = TBoolSeq('{True@2019-09-01, False@2019-09-02}') + tbs = TBoolSeq('[True@2019-09-01, False@2019-09-02]') + tbss = TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}') + + @pytest.mark.parametrize( + 'source, type, interpolation', + [ + (TIntInst('1@2019-09-01'), TBoolInst, TInterpolation.NONE), + (TIntSeq('{1@2019-09-01, 0@2019-09-02}'), TBoolSeq, TInterpolation.DISCRETE), + (TIntSeq('[1@2019-09-01, 0@2019-09-02]'), TBoolSeq, TInterpolation.STEPWISE), + (TIntSeqSet('{[1@2019-09-01, 0@2019-09-02],[1@2019-09-03, 1@2019-09-05]}'), + TBoolSeqSet, TInterpolation.STEPWISE) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_from_base_constructor(self, source, type, interpolation): + tb = TBool.from_base_temporal(True, source) + assert isinstance(tb, type) + assert tb.interpolation() == interpolation + + @pytest.mark.parametrize( + 'source, type, interpolation', + [ + (datetime(2000, 1, 1), TBoolInst, TInterpolation.NONE), + (TimestampSet('{2019-09-01, 2019-09-02}'), TBoolSeq, TInterpolation.DISCRETE), + (Period('[2019-09-01, 2019-09-02]'), TBoolSeq, TInterpolation.STEPWISE), + (PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}'), TBoolSeqSet, TInterpolation.STEPWISE) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_from_base_time_constructor(self, source, type, interpolation): + tb = TBool.from_base_time(True, source) + assert isinstance(tb, type) + assert tb.interpolation() == interpolation + + @pytest.mark.parametrize( + 'source, type, interpolation, expected', + [ + ('True@2019-09-01', TBoolInst, TInterpolation.NONE, 't@2019-09-01 00:00:00+00'), + ('{True@2019-09-01, False@2019-09-02}', TBoolSeq, TInterpolation.DISCRETE, + '{t@2019-09-01 00:00:00+00, f@2019-09-02 00:00:00+00}'), + ('[True@2019-09-01, False@2019-09-02]', TBoolSeq, TInterpolation.STEPWISE, + '[t@2019-09-01 00:00:00+00, f@2019-09-02 00:00:00+00]'), + ('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}', TBoolSeqSet, + TInterpolation.STEPWISE, '{[t@2019-09-01 00:00:00+00, f@2019-09-02 00:00:00+00], ' + '[t@2019-09-03 00:00:00+00, t@2019-09-05 00:00:00+00]}'), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_string_constructor(self, source, type, interpolation, expected): + tb = type(source) + assert isinstance(tb, type) + assert tb.interpolation() == interpolation + assert str(tb) == expected + + @pytest.mark.parametrize( + 'source, type, expected', + [ + ('[True@2019-09-01, True@2019-09-02, True@2019-09-03, False@2019-09-05]', TBoolSeq, + '[t@2019-09-01 00:00:00+00, f@2019-09-05 00:00:00+00]'), + ('{[True@2019-09-01, True@2019-09-02, True@2019-09-03, False@2019-09-05],' + '[True@2019-09-07, True@2019-09-08, True@2019-09-09]}', TBoolSeqSet, + '{[t@2019-09-01 00:00:00+00, f@2019-09-05 00:00:00+00], ' + '[t@2019-09-07 00:00:00+00, t@2019-09-09 00:00:00+00]}'), + ], + ids=['Sequence', 'SequenceSet'] + ) + def test_string_constructor_normalization(self, source, type, expected): + tb = type(source, normalize=True) + assert isinstance(tb, type) + assert str(tb) == expected + + @pytest.mark.parametrize( + 'value, timestamp', + [ + (True, datetime(2019, 9, 1, tzinfo=timezone.utc)), + ('TRUE', datetime(2019, 9, 1, tzinfo=timezone.utc)), + (True, '2019-09-01'), + ('TRUE', '2019-09-01'), + ], + ids=['bool-datetime', 'string-datetime', 'bool-string', 'string-string'] + ) + def test_value_timestamp_instant_constructor(self, value, timestamp): + tbi = TBoolInst(value=value, timestamp=timestamp) + assert str(tbi) == 't@2019-09-01 00:00:00+00' + + @pytest.mark.parametrize( + 'list, interpolation, normalize, expected', + [ + (['True@2019-09-01', 'False@2019-09-03'], TInterpolation.DISCRETE, False, + '{t@2019-09-01 00:00:00+00, f@2019-09-03 00:00:00+00}'), + (['True@2019-09-01', 'False@2019-09-03'], TInterpolation.STEPWISE, False, + '[t@2019-09-01 00:00:00+00, f@2019-09-03 00:00:00+00]'), + ([TBoolInst('True@2019-09-01'), TBoolInst('False@2019-09-03')], TInterpolation.DISCRETE, False, + '{t@2019-09-01 00:00:00+00, f@2019-09-03 00:00:00+00}'), + ([TBoolInst('True@2019-09-01'), TBoolInst('False@2019-09-03')], TInterpolation.STEPWISE, False, + '[t@2019-09-01 00:00:00+00, f@2019-09-03 00:00:00+00]'), + (['True@2019-09-01', TBoolInst('False@2019-09-03')], TInterpolation.DISCRETE, False, + '{t@2019-09-01 00:00:00+00, f@2019-09-03 00:00:00+00}'), + (['True@2019-09-01', TBoolInst('False@2019-09-03')], TInterpolation.STEPWISE, False, + '[t@2019-09-01 00:00:00+00, f@2019-09-03 00:00:00+00]'), + + (['True@2019-09-01', 'True@2019-09-02', 'False@2019-09-03'], TInterpolation.STEPWISE, True, + '[t@2019-09-01 00:00:00+00, f@2019-09-03 00:00:00+00]'), + ([TBoolInst('True@2019-09-01'), TBoolInst('True@2019-09-02'), TBoolInst('False@2019-09-03')], + TInterpolation.STEPWISE, True, + '[t@2019-09-01 00:00:00+00, f@2019-09-03 00:00:00+00]'), + (['True@2019-09-01', 'True@2019-09-02', TBoolInst('False@2019-09-03')], TInterpolation.STEPWISE, True, + '[t@2019-09-01 00:00:00+00, f@2019-09-03 00:00:00+00]'), + ], + ids=['String Discrete', 'String Stepwise', 'TBoolInst Discrete', 'TBoolInst Stepwise', 'Mixed Discrete', + 'Mixed Stepwise', 'String Stepwise Normalized', 'TBoolInst Stepwise Normalized', + 'Mixed Stepwise Normalized'] + ) + def test_instant_list_sequence_constructor(self, list, interpolation, normalize, expected): + tbs = TBoolSeq(instant_list=list, interpolation=interpolation, normalize=normalize, upper_inc=True) + assert str(tbs) == expected + assert tbs.interpolation() == interpolation + + tbs2 = TBoolSeq.from_instants(list, interpolation=interpolation, normalize=normalize, upper_inc=True) + assert str(tbs2) == expected + assert tbs2.interpolation() == interpolation + + @pytest.mark.parametrize( + 'temporal', + [tbi, tbds, tbs, tbss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_from_as_constructor(self, temporal): + # assert temporal == temporal.from_wkt(temporal.as_wkt()) + assert temporal == temporal.from_wkb(temporal.as_wkb()) + assert temporal == temporal.from_hexwkb(temporal.as_hexwkb()) + assert temporal == temporal.from_mfjson(temporal.as_mfjson()) + + @pytest.mark.parametrize( + 'temporal', + [tbi, tbds, tbs, tbss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_copy_constructor(self, temporal): + other = copy(temporal) + assert temporal == other + assert temporal is not other + + +class TestTBoolOutputs(TestTBool): + tbi = TBoolInst('True@2019-09-01') + tbds = TBoolSeq('{True@2019-09-01, False@2019-09-02}') + tbs = TBoolSeq('[True@2019-09-01, False@2019-09-02]') + tbss = TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, 't@2019-09-01 00:00:00+00'), + (tbds, '{t@2019-09-01 00:00:00+00, f@2019-09-02 00:00:00+00}'), + (tbs, '[t@2019-09-01 00:00:00+00, f@2019-09-02 00:00:00+00]'), + (tbss, '{[t@2019-09-01 00:00:00+00, f@2019-09-02 00:00:00+00], ' + '[t@2019-09-03 00:00:00+00, t@2019-09-05 00:00:00+00]}') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_str(self, temporal, expected): + assert str(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, 'TBoolInst(t@2019-09-01 00:00:00+00)'), + (tbds, 'TBoolSeq({t@2019-09-01 00:00:00+00, f@2019-09-02 00:00:00+00})'), + (tbs, 'TBoolSeq([t@2019-09-01 00:00:00+00, f@2019-09-02 00:00:00+00])'), + (tbss, 'TBoolSeqSet({[t@2019-09-01 00:00:00+00, f@2019-09-02 00:00:00+00], ' + '[t@2019-09-03 00:00:00+00, t@2019-09-05 00:00:00+00]})') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_repr(self, temporal, expected): + assert repr(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, 't@2019-09-01 00:00:00+00'), + (tbds, '{t@2019-09-01 00:00:00+00, f@2019-09-02 00:00:00+00}'), + (tbs, '[t@2019-09-01 00:00:00+00, f@2019-09-02 00:00:00+00]'), + (tbss, '{[t@2019-09-01 00:00:00+00, f@2019-09-02 00:00:00+00], ' + '[t@2019-09-03 00:00:00+00, t@2019-09-05 00:00:00+00]}') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_wkt(self, temporal, expected): + assert temporal.as_wkt() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, '011400010100A01E4E71340200'), + (tbds, '0114000602000000030100A01E4E71340200000000F66B85340200'), + (tbs, '0114000A02000000030100A01E4E71340200000000F66B85340200'), + (tbss, '0114000B0200000002000000030100A01E4E71340200000000F' + '66B853402000200000003010060CD89993402000100207CC5C1340200') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_hexwkb(self, temporal, expected): + assert temporal.as_hexwkb() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, '{\n' + ' "type": "MovingBoolean",\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-01T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "values": [\n' + ' true\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00"\n' + ' ],\n' + ' "interpolation": "None"\n' + ' }'), + (tbds, '{\n' + ' "type": "MovingBoolean",\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-02T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "values": [\n' + ' true,\n' + ' false\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true,\n' + ' "interpolation": "Discrete"\n' + ' }'), + (tbs, '{\n' + ' "type": "MovingBoolean",\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-02T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "values": [\n' + ' true,\n' + ' false\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true,\n' + ' "interpolation": "Step"\n' + ' }'), + (tbss, '{\n' + ' "type": "MovingBoolean",\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-05T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "sequences": [\n' + ' {\n' + ' "values": [\n' + ' true,\n' + ' false\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' {\n' + ' "values": [\n' + ' true,\n' + ' true\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-03T00:00:00+00",\n' + ' "2019-09-05T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' }\n' + ' ],\n' + ' "interpolation": "Step"\n' + ' }') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_mfjson(self, temporal, expected): + assert temporal.as_mfjson() == expected + + +class TestTBoolAccessors(TestTBool): + tbi = TBoolInst('True@2019-09-01') + tbds = TBoolSeq('{True@2019-09-01, False@2019-09-02}') + tbs = TBoolSeq('[True@2019-09-01, False@2019-09-02]') + tbss = TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, TInterpolation.NONE), + (tbds, TInterpolation.DISCRETE), + (tbs, TInterpolation.STEPWISE), + (tbss, TInterpolation.STEPWISE) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_interpolation(self, temporal, expected): + assert temporal.interpolation() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, {True}), + (tbds, {True, False}), + (tbs, {True, False}), + (tbss, {True, False}) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_set(self, temporal, expected): + assert temporal.value_set() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, [True]), + (tbds, [True, False]), + (tbs, [True, False]), + (tbss, [True, False, True, True]) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_values(self, temporal, expected): + assert temporal.values() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, True), + (tbds, True), + (tbs, True), + (tbss, True) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_value(self, temporal, expected): + assert temporal.start_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, True), + (tbds, False), + (tbs, False), + (tbss, True) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_value(self, temporal, expected): + assert temporal.end_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, True), + (tbds, False), + (tbs, False), + (tbss, False) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_min_value(self, temporal, expected): + assert temporal.min_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, True), + (tbds, True), + (tbs, True), + (tbss, True) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_max_value(self, temporal, expected): + assert temporal.max_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, True), + (tbds, True), + (tbs, True), + (tbss, True) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_at_timestamp(self, temporal, expected): + assert temporal.value_at_timestamp(datetime(2019, 9, 1)) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, PeriodSet('{[2019-09-01, 2019-09-01]}')), + (tbds, PeriodSet('{[2019-09-01, 2019-09-01], [2019-09-02, 2019-09-02]}')), + (tbs, PeriodSet('{[2019-09-01, 2019-09-02]}')), + (tbss, PeriodSet('{[2019-09-01, 2019-09-02], [2019-09-03, 2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_time(self, temporal, expected): + assert temporal.time() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, timedelta()), + (tbds, timedelta()), + (tbs, timedelta(days=1)), + (tbss, timedelta(days=3)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_duration(self, temporal, expected): + assert temporal.duration() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, timedelta()), + (tbds, timedelta(days=1)), + (tbs, timedelta(days=1)), + (tbss, timedelta(days=4)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_duration_ignoring_gaps(self, temporal, expected): + assert temporal.duration(ignore_gaps=True) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, Period('[2019-09-01, 2019-09-01]')), + (tbds, Period('[2019-09-01, 2019-09-02]')), + (tbs, Period('[2019-09-01, 2019-09-02]')), + (tbss, Period('[2019-09-01, 2019-09-05]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_period(self, temporal, expected): + assert temporal.period() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, Period('[2019-09-01, 2019-09-01]')), + (tbds, Period('[2019-09-01, 2019-09-02]')), + (tbs, Period('[2019-09-01, 2019-09-02]')), + (tbss, Period('[2019-09-01, 2019-09-05]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timespan(self, temporal, expected): + assert temporal.timespan() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, 1), + (tbds, 2), + (tbs, 2), + (tbss, 4), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_num_instants(self, temporal, expected): + assert temporal.num_instants() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, tbi), + (tbds, tbi), + (tbs, tbi), + (tbss, tbi), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_instant(self, temporal, expected): + assert temporal.start_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, tbi), + (tbds, TBoolInst('False@2019-09-02')), + (tbs, TBoolInst('False@2019-09-02')), + (tbss, TBoolInst('True@2019-09-05')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_instant(self, temporal, expected): + assert temporal.end_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, tbi), + (tbds, tbi), + (tbs, tbi), + (tbss, tbi), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_max_instant(self, temporal, expected): + assert temporal.max_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, tbi), + (tbds, TBoolInst('False@2019-09-02')), + (tbs, TBoolInst('False@2019-09-02')), + (tbss, TBoolInst('False@2019-09-02')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_min_instant(self, temporal, expected): + assert temporal.min_instant() == expected + + @pytest.mark.parametrize( + 'temporal, n, expected', + [ + (tbi, 0, tbi), + (tbds, 1, TBoolInst('False@2019-09-02')), + (tbs, 1, TBoolInst('False@2019-09-02')), + (tbss, 2, TBoolInst('True@2019-09-03')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_instant_n(self, temporal, n, expected): + assert temporal.instant_n(n) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, [tbi]), + (tbds, [tbi, TBoolInst('False@2019-09-02')]), + (tbs, [tbi, TBoolInst('False@2019-09-02')]), + (tbss, [tbi, TBoolInst('False@2019-09-02'), TBoolInst('True@2019-09-03'), TBoolInst('True@2019-09-05')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_instants(self, temporal, expected): + assert temporal.instants() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, 1), + (tbds, 2), + (tbs, 2), + (tbss, 4), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_num_timestamps(self, temporal, expected): + assert temporal.num_timestamps() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tbds, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tbs, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tbss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_timestamp(self, temporal, expected): + assert temporal.start_timestamp() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tbds, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tbs, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tbss, datetime(year=2019, month=9, day=5, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_timestamp(self, temporal, expected): + assert temporal.end_timestamp() == expected + + @pytest.mark.parametrize( + 'temporal, n, expected', + [ + (tbi, 0, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tbds, 1, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tbs, 1, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tbss, 2, datetime(year=2019, month=9, day=3, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timestamp_n(self, temporal, n, expected): + assert temporal.timestamp_n(n) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)]), + (tbds, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)]), + (tbs, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)]), + (tbss, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=3, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=5, tzinfo=timezone.utc)]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timestamps(self, temporal, expected): + assert temporal.timestamps() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbds, [TBoolSeq('[t@2019-09-01]'), TBoolSeq('[f@2019-09-02]')]), + (tbs, [TBoolSeq('[t@2019-09-01, t@2019-09-02)'), + TBoolSeq('[f@2019-09-02]')]), + (tbss, + [TBoolSeq('[t@2019-09-01, t@2019-09-02)'), + TBoolSeq('[f@2019-09-02]'), + TBoolSeq('[t@2019-09-03, t@2019-09-05]')]), + ], + ids=['Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_segments(self, temporal, expected): + assert temporal.segments() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbds, True), + (tbs, True), + ], + ids=['Discrete Sequence', 'Sequence'] + ) + def test_lower_upper_inc(self, temporal, expected): + assert temporal.lower_inc() == expected + assert temporal.upper_inc() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, 440045287), + (tbds, 2385901957), + (tbs, 2385901957), + (tbss, 1543175996) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_hash(self, temporal, expected): + assert hash(temporal) == expected + + +class TestTBoolTransformations(TestTBool): + tbi = TBoolInst('True@2019-09-01') + tbds = TBoolSeq('{True@2019-09-01, False@2019-09-02}') + tbs = TBoolSeq('[True@2019-09-01, False@2019-09-02]') + tbss = TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TBoolInst('True@2019-09-01'), tbi), + (TBoolSeq('{True@2019-09-01}'), tbi), + (TBoolSeq('[True@2019-09-01]'), tbi), + (TBoolSeqSet('{[True@2019-09-01]}'), tbi), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_instant(self, temporal, expected): + temp = temporal.to_instant() + assert isinstance(temp, TBoolInst) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TBoolInst('True@2019-09-01'), + TBoolSeq('[True@2019-09-01]')), + (TBoolSeq('{True@2019-09-01, False@2019-09-02}'), + TBoolSeq('{True@2019-09-01, False@2019-09-02}')), + (TBoolSeq('[True@2019-09-01, False@2019-09-02]'), + TBoolSeq('[True@2019-09-01, False@2019-09-02]')), + (TBoolSeqSet('{[True@2019-09-01, False@2019-09-02]}'), + TBoolSeq('[True@2019-09-01, False@2019-09-02]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_sequence(self, temporal, expected): + temp = temporal.to_sequence() + assert isinstance(temp, TBoolSeq) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TBoolInst('True@2019-09-01'), + TBoolSeqSet('{[True@2019-09-01]}')), + (TBoolSeq('{True@2019-09-01, False@2019-09-02}'), + TBoolSeqSet('{[True@2019-09-01], [False@2019-09-02]}')), + (TBoolSeq('[True@2019-09-01, False@2019-09-02]'), + TBoolSeqSet('{[True@2019-09-01, False@2019-09-02]}')), + (TBoolSeqSet('{[True@2019-09-01, False@2019-09-02]}'), + TBoolSeqSet('{[True@2019-09-01, False@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_sequenceset(self, temporal, expected): + temp = temporal.to_sequenceset() + assert isinstance(temp, TBoolSeqSet) + assert temp == expected + + @pytest.mark.parametrize( + 'tbool, delta, expected', + [(tbi, timedelta(days=4), TBoolInst('True@2019-09-05')), + (tbi, timedelta(days=-4), TBoolInst('True@2019-08-28')), + (tbi, timedelta(hours=2), TBoolInst('True@2019-09-01 02:00:00')), + (tbi, timedelta(hours=-2), TBoolInst('True@2019-08-31 22:00:00')), + (tbds, timedelta(days=4), TBoolSeq('{True@2019-09-05, False@2019-09-06}')), + (tbds, timedelta(days=-4), TBoolSeq('{True@2019-08-28, False@2019-08-29}')), + (tbds, timedelta(hours=2), TBoolSeq('{True@2019-09-01 02:00:00, False@2019-09-02 02:00:00}')), + (tbds, timedelta(hours=-2), TBoolSeq('{True@2019-08-31 22:00:00, False@2019-09-01 22:00:00}')), + (tbs, timedelta(days=4), TBoolSeq('[True@2019-09-05, False@2019-09-06]')), + (tbs, timedelta(days=-4), TBoolSeq('[True@2019-08-28, False@2019-08-29]')), + (tbs, timedelta(hours=2), TBoolSeq('[True@2019-09-01 02:00:00, False@2019-09-02 02:00:00]')), + (tbs, timedelta(hours=-2), TBoolSeq('[True@2019-08-31 22:00:00, False@2019-09-01 22:00:00]')), + (tbss, timedelta(days=4), + TBoolSeqSet('{[True@2019-09-05, False@2019-09-06],[True@2019-09-07, True@2019-09-09]}')), + (tbss, timedelta(days=-4), + TBoolSeqSet('{[True@2019-08-28, False@2019-08-29],[True@2019-08-30, True@2019-09-01]}')), + (tbss, timedelta(hours=2), + TBoolSeqSet('{[True@2019-09-01 02:00:00, False@2019-09-02 02:00:00],' + '[True@2019-09-03 02:00:00, True@2019-09-05 02:00:00]}')), + (tbss, timedelta(hours=-2), + TBoolSeqSet('{[True@2019-08-31 22:00:00, False@2019-09-01 22:00:00],' + '[True@2019-09-02 22:00:00, True@2019-09-04 22:00:00]}')), + ], + ids=['Instant positive days', 'Instant negative days', + 'Instant positive hours', 'Instant negative hours', + 'Discrete Sequence positive days', 'Discrete Sequence negative days', + 'Discrete Sequence positive hours', 'Discrete Sequence negative hours', + 'Sequence positive days', 'Sequence negative days', + 'Sequence positive hours', 'Sequence negative hours', + 'Sequence Set positive days', 'Sequence Set negative days', + 'Sequence Set positive hours', 'Sequence Set negative hours'] + ) + def test_shift(self, tbool, delta, expected): + assert tbool.shift(delta) == expected + + @pytest.mark.parametrize( + 'tbool, delta, expected', + [(tbi, timedelta(days=4), TBoolInst('True@2019-09-01')), + (tbi, timedelta(hours=2), TBoolInst('True@2019-09-01')), + (tbds, timedelta(days=4), TBoolSeq('{True@2019-09-01, False@2019-09-05}')), + (tbds, timedelta(hours=2), TBoolSeq('{True@2019-09-01 00:00:00, False@2019-09-01 02:00:00}')), + (tbs, timedelta(days=4), TBoolSeq('[True@2019-09-01, False@2019-09-05]')), + (tbs, timedelta(hours=2), TBoolSeq('[True@2019-09-01 00:00:00, False@2019-09-01 02:00:00]')), + (tbss, timedelta(days=4), + TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}')), + (tbss, timedelta(hours=2), + TBoolSeqSet('{[True@2019-09-01 00:00:00, False@2019-09-01 00:30:00],' + '[True@2019-09-01 01:00:00, True@2019-09-01 02:00:00]}')), + ], + ids=['Instant positive days', 'Instant positive hours', + 'Discrete Sequence positive days', 'Discrete Sequence positive hours', + 'Sequence positive days', 'Sequence positive hours', + 'Sequence Set positive days', 'Sequence Set positive hours'] + ) + def test_scale(self, tbool, delta, expected): + assert tbool.tscale(delta) == expected + + def test_shift_tscale(self): + assert self.tbss.shift_tscale(timedelta(days=4), timedelta(hours=2)) == \ + TBoolSeqSet('{[True@2019-09-05 00:00:00, False@2019-09-05 00:30:00],' + '[True@2019-09-05 01:00:00, True@2019-09-05 02:00:00]}') + + +class TestTBoolModifications(TestTBool): + tbi = TBoolInst('True@2019-09-01') + tbds = TBoolSeq('{True@2019-09-01, False@2019-09-02}') + tbs = TBoolSeq('[True@2019-09-01, False@2019-09-02]') + tbss = TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, sequence, expected', + [ + (tbi, TBoolSeq('{True@2019-09-03}'), TBoolSeq('{True@2019-09-01, True@2019-09-03}')), + (tbds, TBoolSeq('{True@2019-09-03}'), TBoolSeq('{True@2019-09-01, False@2019-09-02, True@2019-09-03}')), + (tbs, TBoolSeq('[True@2019-09-03]'), TBoolSeqSet('{[True@2019-09-01, False@2019-09-02, True@2019-09-03]}')), + (tbss, TBoolSeq('[True@2019-09-06]'), + TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05],[True@2019-09-06]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_insert(self, temporal, sequence, expected): + assert temporal.insert(sequence) == expected + + @pytest.mark.parametrize( + 'temporal, instant, expected', + [ + (tbi, TBoolInst('False@2019-09-01'), TBoolInst('False@2019-09-01')), + (tbds, TBoolInst('False@2019-09-01'), TBoolSeq('{False@2019-09-01, False@2019-09-02}')), + (tbs, TBoolInst('False@2019-09-01'), + TBoolSeqSet('{[False@2019-09-01], (True@2019-09-01, False@2019-09-02]}')), + (tbss, TBoolInst('False@2019-09-01'), + TBoolSeqSet('{[False@2019-09-01], (True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_update(self, temporal, instant, expected): + assert temporal.update(instant) == expected + + @pytest.mark.parametrize( + 'temporal, time, expected', + [ + (tbi, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), None), + (tbi, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), tbi), + (tbds, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), TBoolSeq('{False@2019-09-02}')), + (tbs, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + TBoolSeqSet('{(True@2019-09-01, False@2019-09-02]}')), + (tbss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + TBoolSeqSet('{(True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}')), + ], + ids=['Instant intersection', 'Instant disjoint', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_delete(self, temporal, time, expected): + assert temporal.delete(time) == expected + + @pytest.mark.parametrize( + 'temporal, instant, expected', + [ + (tbi, TBoolInst('True@2019-09-02'), TBoolSeq('{True@2019-09-01, True@2019-09-02}')), + (tbds, TBoolInst('True@2019-09-03'), TBoolSeq('{True@2019-09-01, False@2019-09-02, True@2019-09-03}')), + (tbs, TBoolInst('True@2019-09-03'), TBoolSeq('[True@2019-09-01, False@2019-09-02, True@2019-09-03]')), + (tbss, TBoolInst('True@2019-09-06'), + TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-06]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_append_instant(self, temporal, instant, expected): + assert temporal.append_instant(instant) == expected + + @pytest.mark.parametrize( + 'temporal, sequence, expected', + [ + (tbds, TBoolSeq('{True@2019-09-03}'), TBoolSeq('{True@2019-09-01, False@2019-09-02, True@2019-09-03}')), + (tbs, TBoolSeq('[True@2019-09-03]'), TBoolSeqSet('{[True@2019-09-01, False@2019-09-02], [True@2019-09-03]}')), + (tbss, TBoolSeq('[True@2019-09-06]'), + TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05],[True@2019-09-06]}')), + ], + ids=['Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_append_sequence(self, temporal, sequence, expected): + assert temporal.append_sequence(sequence) == expected + + +class TestTBoolManipulationFunctions(TestTBool): + tbi = TBoolInst('True@2019-09-01') + tbds = TBoolSeq('{True@2019-09-01, False@2019-09-02}') + tbs = TBoolSeq('[True@2019-09-01, False@2019-09-02]') + tbss = TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}') + compared = TBoolSeq('[False@2019-09-01, True@2019-09-02, True@2019-09-03]') + + @pytest.mark.parametrize( + 'temporal, shift, expected', + [ + (tbi, timedelta(days=1), TBoolInst('True@2019-09-02')), + (tbds, timedelta(days=1), TBoolSeq('{True@2019-09-02, False@2019-09-03}')), + (tbs, timedelta(days=1), TBoolSeq('[True@2019-09-02, False@2019-09-03]')), + (tbss, timedelta(days=1), + TBoolSeqSet('{[True@2019-09-02, False@2019-09-03],[True@2019-09-04, True@2019-09-06]}')), + (tbi, timedelta(days=-1), TBoolInst('True@2019-08-31')), + (tbds, timedelta(days=-1), TBoolSeq('{True@2019-08-31, False@2019-09-01}')), + (tbs, timedelta(days=-1), TBoolSeq('[True@2019-08-31, False@2019-09-01]')), + (tbss, timedelta(days=-1), + TBoolSeqSet('{[True@2019-08-31, False@2019-09-01],[True@2019-09-02, True@2019-09-04]}')), + ], + ids=['Instant positive', 'Discrete Sequence positive', 'Sequence positive', 'SequenceSet positive', + 'Instant negative', 'Discrete Sequence negative', 'Sequence negative', 'SequenceSet negative'], + ) + def test_shift(self, temporal, shift, expected): + assert temporal.shift(shift) == expected + + @pytest.mark.parametrize( + 'temporal, scale, expected', + [ + (tbi, timedelta(days=10), TBoolInst('True@2019-09-01')), + (tbds, timedelta(days=10), TBoolSeq('{True@2019-09-01, False@2019-09-11}')), + (tbs, timedelta(days=10), TBoolSeq('[True@2019-09-01, False@2019-09-11]')), + (tbss, timedelta(days=10), + TBoolSeqSet('{[True@2019-09-01, False@2019-09-03 12:00:00],[True@2019-09-06, True@2019-09-11]}')), + ], + ids=['Instant positive', 'Discrete Sequence positive', 'Sequence positive', 'SequenceSet positive'], + ) + def test_tscale(self, temporal, scale, expected): + assert temporal.tscale(scale) == expected + + @pytest.mark.parametrize( + 'temporal, shift, scale, expected', + [ + (tbi, timedelta(days=1), timedelta(days=10), TBoolInst('True@2019-09-02')), + (tbds, timedelta(days=1), timedelta(days=10), TBoolSeq('{True@2019-09-02, False@2019-09-12}')), + (tbs, timedelta(days=1), timedelta(days=10), TBoolSeq('[True@2019-09-02, False@2019-09-12]')), + (tbss, timedelta(days=1), timedelta(days=10), + TBoolSeqSet('{[True@2019-09-02, False@2019-09-04 12:00:00],[True@2019-09-07, True@2019-09-12]}')), + (tbi, timedelta(days=-1), timedelta(days=10), TBoolInst('True@2019-08-31')), + (tbds, timedelta(days=-1), timedelta(days=10), TBoolSeq('{True@2019-08-31, False@2019-09-10}')), + (tbs, timedelta(days=-1), timedelta(days=10), TBoolSeq('[True@2019-08-31, False@2019-09-10]')), + (tbss, timedelta(days=-1), timedelta(days=10), + TBoolSeqSet('{[True@2019-08-31, False@2019-09-02 12:00:00],[True@2019-09-05, True@2019-09-010]}')), + ], + ids=['Instant positive', 'Discrete Sequence positive', 'Sequence positive', 'SequenceSet positive', + 'Instant negative', 'Discrete Sequence negative', 'Sequence negative', 'SequenceSet negative'], + ) + def test_shift_tscale(self, temporal, shift, scale, expected): + assert temporal.shift_tscale(shift, scale) == expected + + +class TestTBoolRestrictors(TestTBool): + tbi = TBoolInst('True@2019-09-01') + tbds = TBoolSeq('{True@2019-09-01, False@2019-09-02}') + tbs = TBoolSeq('[True@2019-09-01, False@2019-09-02]') + tbss = TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}') + + timestamp = datetime(2019, 9, 1) + timestamp_set = TimestampSet('{2019-09-01, 2019-09-03}') + period = Period('[2019-09-01, 2019-09-02]') + period_set = PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, PeriodSet('{[2019-09-01, 2019-09-01]}')), + (tbds, PeriodSet('{[2019-09-01, 2019-09-01]}')), + (tbs, PeriodSet('{[2019-09-01, 2019-09-02)}')), + (tbss, PeriodSet('{[2019-09-01, 2019-09-02),[2019-09-03, 2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_when_true(self, temporal, expected): + assert temporal.when_true() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, None), + (tbds, PeriodSet('{[2019-09-02, 2019-09-02]}')), + (tbs, PeriodSet('{[2019-09-02, 2019-09-02]}')), + (tbss, PeriodSet('{[2019-09-02, 2019-09-02]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_when_false(self, temporal, expected): + assert temporal.when_false() == expected + + @pytest.mark.parametrize( + 'temporal, restrictor, expected', + [ + (tbi, timestamp, TBoolInst('True@2019-09-01')), + (tbi, timestamp_set, TBoolInst('True@2019-09-01')), + (tbi, period, TBoolInst('True@2019-09-01')), + (tbi, period_set, TBoolInst('True@2019-09-01')), + (tbi, True, TBoolInst('True@2019-09-01')), + (tbi, False, None), + + (tbds, timestamp, TBoolSeq('{True@2019-09-01}')), + (tbds, timestamp_set, TBoolSeq('{True@2019-09-01}')), + (tbds, period, TBoolSeq('{True@2019-09-01, False@2019-09-02}')), + (tbds, period_set, TBoolSeq('{True@2019-09-01, False@2019-09-02}')), + (tbds, True, TBoolSeq('{True@2019-09-01}')), + (tbds, False, TBoolSeq('{False@2019-09-02}')), + + (tbs, timestamp, TBoolSeq('[True@2019-09-01]')), + (tbs, timestamp_set, TBoolSeq('{True@2019-09-01}')), + (tbs, period, TBoolSeq('[True@2019-09-01, False@2019-09-02]')), + (tbs, period_set, TBoolSeq('[True@2019-09-01, False@2019-09-02]')), + (tbs, True, TBoolSeq('[True@2019-09-01, True@2019-09-02)')), + (tbs, False, TBoolSeq('[False@2019-09-02]')), + + (tbss, timestamp, TBoolSeqSet('[True@2019-09-01]')), + (tbss, timestamp_set, TBoolSeq('{True@2019-09-01, True@2019-09-03}')), + (tbss, period, TBoolSeqSet('{[True@2019-09-01, False@2019-09-02]}')), + (tbss, period_set, + TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}')), + (tbss, True, TBoolSeqSet('{[True@2019-09-01, True@2019-09-02),[True@2019-09-03, True@2019-09-05]}')), + (tbss, False, TBoolSeqSet('{[False@2019-09-02]}')) + + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', 'Instant-PeriodSet', 'Instant-True', + 'Instant-False', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Discrete Sequence-True', + 'Discrete Sequence-False', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-True', 'Sequence-False', 'SequenceSet-Timestamp', + 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-True', + 'SequenceSet-False'] + ) + def test_at(self, temporal, restrictor, expected): + assert temporal.at(restrictor) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, TBoolInst('True@2019-09-01')), + (tbds, TBoolSeq('{True@2019-09-01}')), + (tbs, TBoolSeq('[True@2019-09-01, True@2019-09-02)')), + (tbss, TBoolSeqSet('{[True@2019-09-01, True@2019-09-02),[True@2019-09-03, True@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_max(self, temporal, expected): + assert temporal.at_max() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, TBoolInst('True@2019-09-01')), + (tbds, TBoolSeq('{False@2019-09-02}')), + (tbs, TBoolSeq('[False@2019-09-02]')), + (tbss, TBoolSeqSet('{[False@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_min(self, temporal, expected): + assert temporal.at_min() == expected + + @pytest.mark.parametrize( + 'temporal, restrictor, expected', + [ + (tbi, timestamp, None), + (tbi, timestamp_set, None), + (tbi, period, None), + (tbi, period_set, None), + (tbi, True, None), + (tbi, False, TBoolInst('True@2019-09-01')), + + (tbds, timestamp, TBoolSeq('{False@2019-09-02}')), + (tbds, timestamp_set, TBoolSeq('{False@2019-09-02}')), + (tbds, period, None), + (tbds, period_set, None), + (tbds, True, TBoolSeq('{False@2019-09-02}')), + (tbds, False, TBoolSeq('{True@2019-09-01}')), + + (tbs, timestamp, TBoolSeqSet('{(True@2019-09-01, False@2019-09-02]}')), + (tbs, timestamp_set, TBoolSeqSet('{(True@2019-09-01, False@2019-09-02]}')), + (tbs, period, None), + (tbs, period_set, None), + (tbs, True, TBoolSeqSet('{[False@2019-09-02]}')), + (tbs, False, TBoolSeqSet('{[True@2019-09-01, True@2019-09-02)}')), + + (tbss, timestamp, + TBoolSeqSet('{(True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}')), + (tbss, timestamp_set, + TBoolSeqSet('{(True@2019-09-01, False@2019-09-02],(True@2019-09-03, True@2019-09-05]}')), + (tbss, period, TBoolSeqSet('{[True@2019-09-03, True@2019-09-05]}')), + (tbss, period_set, None), + (tbss, True, TBoolSeqSet('{[False@2019-09-02]}')), + (tbss, False, TBoolSeqSet('{[True@2019-09-01, True@2019-09-02),[True@2019-09-03, True@2019-09-05]}')) + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', 'Instant-PeriodSet', 'Instant-True', + 'Instant-False', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Discrete Sequence-True', + 'Discrete Sequence-False', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-True', 'Sequence-False', 'SequenceSet-Timestamp', + 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-True', + 'SequenceSet-False'] + ) + def test_minus(self, temporal, restrictor, expected): + assert temporal.minus(restrictor) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, None), + (tbds, TBoolSeq('{False@2019-09-02}')), + (tbs, TBoolSeq('[False@2019-09-02]')), + (tbss, TBoolSeqSet('{[False@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_minus_max(self, temporal, expected): + assert temporal.minus_max() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, None), + (tbds, TBoolSeq('{True@2019-09-01}')), + (tbs, TBoolSeq('[True@2019-09-01, True@2019-09-02)')), + (tbss, TBoolSeqSet('{[True@2019-09-01, True@2019-09-02),[True@2019-09-03, True@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_minus_min(self, temporal, expected): + assert temporal.minus_min() == expected + + @pytest.mark.parametrize( + 'temporal, restrictor', + [ + (tbi, timestamp), + (tbi, timestamp_set), + (tbi, period), + (tbi, period_set), + (tbi, True), + (tbi, False), + + (tbds, timestamp), + (tbds, timestamp_set), + (tbds, period), + (tbds, period_set), + (tbds, True), + (tbds, False), + + (tbs, timestamp), + (tbs, timestamp_set), + (tbs, period), + (tbs, period_set), + (tbs, True), + (tbs, False), + + (tbss, timestamp), + (tbss, timestamp_set), + (tbss, period), + (tbss, period_set), + (tbss, True), + (tbss, False), + + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', 'Instant-PeriodSet', 'Instant-True', + 'Instant-False', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Discrete Sequence-True', + 'Discrete Sequence-False', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-True', 'Sequence-False', 'SequenceSet-Timestamp', + 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-True', + 'SequenceSet-False'] + ) + def test_at_minus(self, temporal, restrictor): + assert TBool.merge(temporal.at(restrictor), temporal.minus(restrictor)) == temporal + + @pytest.mark.parametrize( + 'temporal', + [tbi, tbds, tbs, tbss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_minus_min_max(self, temporal): + assert TBool.merge(temporal.at_min(), temporal.minus_min()) == temporal + assert TBool.merge(temporal.at_max(), temporal.minus_max()) == temporal + + +class TestTBoolComparisons(TestTBool): + tb = TBoolSeq('[True@2019-09-01, False@2019-09-02]') + other = TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}') + + def test_eq(self): + _ = self.tb == self.other + + def test_ne(self): + _ = self.tb != self.other + + def test_lt(self): + _ = self.tb < self.other + + def test_le(self): + _ = self.tb <= self.other + + def test_gt(self): + _ = self.tb > self.other + + def test_ge(self): + _ = self.tb >= self.other + + +class TestTBoolEverAlwaysComparisons(TestTBool): + tbi = TBoolInst('True@2019-09-01') + tbds = TBoolSeq('{True@2019-09-01, False@2019-09-02}') + tbs = TBoolSeq('[True@2019-09-01, False@2019-09-02]') + tbss = TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, True), + (tbds, False), + (tbs, False), + (tbss, False) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_always_true(self, temporal, expected): + assert temporal.always_eq(True) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, False), + (tbds, False), + (tbs, False), + (tbss, False) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_always_false(self, temporal, expected): + assert temporal.always_eq(False) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, True), + (tbds, True), + (tbs, True), + (tbss, True) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_ever_true(self, temporal, expected): + assert temporal.ever_eq(True) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, False), + (tbds, True), + (tbs, True), + (tbss, True) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_ever_false(self, temporal, expected): + assert temporal.ever_eq(False) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, False), + (tbds, False), + (tbs, False), + (tbss, False) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_never_true(self, temporal, expected): + assert temporal.never_eq(True) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, True), + (tbds, False), + (tbs, False), + (tbss, False) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_never_false(self, temporal, expected): + assert temporal.never_eq(False) == expected + + +class TestTBoolTemporalComparisons(TestTBool): + tbi = TBoolInst('True@2019-09-01') + tbds = TBoolSeq('{True@2019-09-01, False@2019-09-02}') + tbs = TBoolSeq('[True@2019-09-01, False@2019-09-02]') + tbss = TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}') + compared = TBoolSeq('[False@2019-09-01, True@2019-09-02, True@2019-09-03]') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, TBoolInst('False@2019-09-01')), + (tbds, TBoolSeq('{False@2019-09-01, True@2019-09-02}')), + (tbs, TBoolSeq('[False@2019-09-01, True@2019-09-02]')), + (tbss, TBoolSeqSet('{[False@2019-09-01, True@2019-09-02],[False@2019-09-03, False@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_not(self, temporal, expected): + assert temporal.temporal_not() == expected + assert -temporal == expected + assert ~temporal == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, TBoolInst('False@2019-09-01')), + (tbds, TBoolSeq('{False@2019-09-01, False@2019-09-02}')), + (tbs, TBoolSeq('[False@2019-09-01, False@2019-09-02]')), + (tbss, TBoolSeqSet('{[False@2019-09-01, False@2019-09-02],[True@2019-09-03]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_and_temporal(self, temporal, expected): + assert temporal.temporal_and(self.compared) == expected + assert temporal & self.compared == expected + + @pytest.mark.parametrize( + 'temporal', + [tbi, tbds, tbs, tbss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_and_bool(self, temporal): + assert temporal.temporal_and(True) == temporal + assert (temporal & True) == temporal + + assert temporal.temporal_and(False) == TBool.from_base_temporal(False, temporal) + assert (temporal & False) == TBool.from_base_temporal(False, temporal) + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, TBoolInst('True@2019-09-01')), + (tbds, TBoolSeq('{True@2019-09-01, True@2019-09-02}')), + (tbs, TBoolSeq('[True@2019-09-01, True@2019-09-02]')), + (tbss, TBoolSeqSet('{[True@2019-09-01, True@2019-09-02],[True@2019-09-03]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_or_temporal(self, temporal, expected): + assert temporal.temporal_or(self.compared) == expected + assert temporal | self.compared == expected + + @pytest.mark.parametrize( + 'temporal', + [tbi, tbds, tbs, tbss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_or_bool(self, temporal): + assert temporal.temporal_or(True) == TBool.from_base_temporal(True, temporal) + assert (temporal | True) == TBool.from_base_temporal(True, temporal) + + assert temporal.temporal_or(False) == temporal + assert (temporal | False) == temporal + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, TBoolInst('False@2019-09-01')), + (tbds, TBoolSeq('{False@2019-09-01, False@2019-09-02}')), + (tbs, TBoolSeq('[False@2019-09-01, False@2019-09-02]')), + (tbss, TBoolSeqSet('{[False@2019-09-01, False@2019-09-02],[True@2019-09-03]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_equal_temporal(self, temporal, expected): + assert temporal.temporal_equal(self.compared) == expected + + @pytest.mark.parametrize( + 'temporal', + [tbi, tbds, tbs, tbss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_equal_bool(self, temporal): + assert temporal.temporal_equal(True) == temporal + + assert temporal.temporal_equal(False) == ~temporal + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tbi, TBoolInst('True@2019-09-01')), + (tbds, TBoolSeq('{True@2019-09-01, True@2019-09-02}')), + (tbs, TBoolSeq('[True@2019-09-01, True@2019-09-02]')), + (tbss, TBoolSeqSet('{[True@2019-09-01, True@2019-09-02],[False@2019-09-03]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_not_equal_temporal(self, temporal, expected): + assert temporal.temporal_not_equal(self.compared) == expected + + @pytest.mark.parametrize( + 'temporal', + [tbi, tbds, tbs, tbss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_not_equal_bool(self, temporal): + assert temporal.temporal_not_equal(True) == ~temporal + + assert temporal.temporal_not_equal(False) == temporal + + diff --git a/pymeos/tests/main/tfloat_test.py b/pymeos/tests/main/tfloat_test.py new file mode 100644 index 00000000..258f3653 --- /dev/null +++ b/pymeos/tests/main/tfloat_test.py @@ -0,0 +1,2056 @@ +from copy import copy +from operator import not_ +from datetime import datetime, timezone, timedelta +from spans.types import intrange, floatrange + +import pytest + +from pymeos import TBool, TBoolInst, TBoolSeq, TBoolSeqSet, \ + TFloat, TFloatInst, TFloatSeq, TFloatSeqSet, \ + TInt, TIntInst, TIntSeq, TIntSeqSet, \ + TInterpolation, TBox, TimestampSet, Period, PeriodSet + +from tests.conftest import TestPyMEOS + + +class TestTFloat(TestPyMEOS): + pass + + +class TestTFloatConstructors(TestTFloat): + tfi = TFloatInst('1.5@2019-09-01') + tfds = TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}') + tfs = TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]') + tfss = TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + tfsts = TFloatSeq('Interp=Step;[1.5@2019-09-01, 2.5@2019-09-02]') + tfstss = TFloatSeqSet('Interp=Step;{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + + @pytest.mark.parametrize( + 'source, type, interpolation', + [ + (TIntInst('1@2019-09-01'), TFloatInst, TInterpolation.NONE), + (TIntSeq('{1@2019-09-01, 2@2019-09-02}'), TFloatSeq, TInterpolation.DISCRETE), + (TIntSeq('[1@2019-09-01, 2@2019-09-02]'), TFloatSeq, TInterpolation.STEPWISE), + (TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}'), + TFloatSeqSet, TInterpolation.STEPWISE) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_from_base_constructor(self, source, type, interpolation): + tf = TFloat.from_base_temporal(1.5, source) + assert isinstance(tf, type) + assert tf.interpolation() == interpolation + + @pytest.mark.parametrize( + 'source, type, interpolation', + [ + (TFloatInst('1@2019-09-01'), TFloatInst, TInterpolation.NONE), + (TFloatSeq('{1@2019-09-01, 2@2019-09-02}'), TFloatSeq, TInterpolation.DISCRETE), + (TFloatSeq('[1@2019-09-01, 2@2019-09-02]'), TFloatSeq, TInterpolation.LINEAR), + (TFloatSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}'), + TFloatSeqSet, TInterpolation.LINEAR) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_from_base_constructor(self, source, type, interpolation): + tf = TFloat.from_base_temporal(1.5, source) + assert isinstance(tf, type) + assert tf.interpolation() == interpolation + + @pytest.mark.parametrize( + 'source, type, interpolation', + [ + (datetime(2000, 1, 1), TFloatInst, TInterpolation.NONE), + (TimestampSet('{2019-09-01, 2019-09-02}'), TFloatSeq, TInterpolation.DISCRETE), + (Period('[2019-09-01, 2019-09-02]'), TFloatSeq, TInterpolation.LINEAR), + (PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}'), TFloatSeqSet, TInterpolation.LINEAR) + ], + ids=['Instant', 'Sequence', 'Discrete Sequence', 'SequenceSet'] + ) + def test_from_base_time_constructor(self, source, type, interpolation): + tf = TFloat.from_base_time(1.5, source, interpolation) + assert isinstance(tf, type) + assert tf.interpolation() == interpolation + + @pytest.mark.parametrize( + 'source, type, interpolation, expected', + [ + ('1.5@2019-09-01', TFloatInst, TInterpolation.NONE, '1.5@2019-09-01 00:00:00+00'), + ('{1.5@2019-09-01, 2.5@2019-09-02}', TFloatSeq, TInterpolation.DISCRETE, + '{1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00}'), + ('[1.5@2019-09-01, 2.5@2019-09-02]', TFloatSeq, TInterpolation.LINEAR, + '[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00]'), + ('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}', TFloatSeqSet, + TInterpolation.LINEAR, '{[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00], ' + '[1.5@2019-09-03 00:00:00+00, 1.5@2019-09-05 00:00:00+00]}'), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_string_constructor(self, source, type, interpolation, expected): + tf = type(source) + assert isinstance(tf, type) + assert tf.interpolation() == interpolation + assert str(tf) == expected + + @pytest.mark.parametrize( + 'source, type, expected', + [ + ('[1.5@2019-09-01, 1.75@2019-09-02, 2@2019-09-03, 2.5@2019-09-05]', TFloatSeq, + '[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-05 00:00:00+00]'), + ('{[1.5@2019-09-01, 1.75@2019-09-02, 2@2019-09-03, 2.5@2019-09-05],' + '[1.5@2019-09-07, 1.5@2019-09-08, 1.5@2019-09-09]}', TFloatSeqSet, + '{[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-05 00:00:00+00], ' + '[1.5@2019-09-07 00:00:00+00, 1.5@2019-09-09 00:00:00+00]}'), + ], + ids=['Sequence', 'SequenceSet'] + ) + def test_string_constructor_normalization(self, source, type, expected): + tf = type(source, normalize=1) + assert isinstance(tf, type) + assert str(tf) == expected + + @pytest.mark.parametrize( + 'value, timestamp', + [ + (1.5, datetime(2019, 9, 1, tzinfo=timezone.utc)), + ('1.5', datetime(2019, 9, 1, tzinfo=timezone.utc)), + (1.5, '2019-09-01'), + ('1.5', '2019-09-01'), + ], + ids=['float-datetime', 'string-datetime', 'float-string', 'string-string'] + ) + def test_value_timestamp_instant_constructor(self, value, timestamp): + tfi = TFloatInst(value=value, timestamp=timestamp) + assert str(tfi) == '1.5@2019-09-01 00:00:00+00' + + @pytest.mark.parametrize( + 'list, interpolation, normalize, expected', + [ + (['1.5@2019-09-01', '2.5@2019-09-03'], TInterpolation.DISCRETE, False, + '{1.5@2019-09-01 00:00:00+00, 2.5@2019-09-03 00:00:00+00}'), + (['1.5@2019-09-01', '2.5@2019-09-03'], TInterpolation.LINEAR, False, + '[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-03 00:00:00+00]'), + ([TFloatInst('1.5@2019-09-01'), TFloatInst('2.5@2019-09-03')], TInterpolation.DISCRETE, False, + '{1.5@2019-09-01 00:00:00+00, 2.5@2019-09-03 00:00:00+00}'), + ([TFloatInst('1.5@2019-09-01'), TFloatInst('2.5@2019-09-03')], TInterpolation.LINEAR, False, + '[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-03 00:00:00+00]'), + (['1.5@2019-09-01', TFloatInst('2.5@2019-09-03')], TInterpolation.DISCRETE, False, + '{1.5@2019-09-01 00:00:00+00, 2.5@2019-09-03 00:00:00+00}'), + (['1.5@2019-09-01', TFloatInst('2.5@2019-09-03')], TInterpolation.LINEAR, False, + '[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-03 00:00:00+00]'), + + (['1.5@2019-09-01', '2@2019-09-02', '2.5@2019-09-03'], TInterpolation.LINEAR, True, + '[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-03 00:00:00+00]'), + ([TFloatInst('1.5@2019-09-01'), TFloatInst('2@2019-09-02'), TFloatInst('2.5@2019-09-03')], + TInterpolation.LINEAR, True, + '[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-03 00:00:00+00]'), + (['1.5@2019-09-01', '2@2019-09-02', TFloatInst('2.5@2019-09-03')], TInterpolation.LINEAR, True, + '[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-03 00:00:00+00]'), + ], + ids=['String Discrete', 'String Linear', 'TFloatInst Discrete', 'TFloatInst Linear', 'Mixed Discrete', + 'Mixed Linear', 'String Linear Normalized', 'TFloatInst Linear Normalized', + 'Mixed Linear Normalized'] + ) + def test_instant_list_sequence_constructor(self, list, interpolation, normalize, expected): + tfs = TFloatSeq(instant_list=list, interpolation=interpolation, normalize=normalize, upper_inc=True) + assert str(tfs) == expected + assert tfs.interpolation() == interpolation + + tfs2 = TFloatSeq.from_instants(list, interpolation=interpolation, normalize=normalize, upper_inc=True) + assert str(tfs2) == expected + assert tfs2.interpolation() == interpolation + + @pytest.mark.parametrize( + 'temporal', + [tfi, tfds, tfs, tfss, tfsts, tfstss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet', + 'Stepwise Sequence', 'Stepwise SequenceSet'] + ) + def test_from_as_constructor(self, temporal): + assert temporal == temporal.from_wkb(temporal.as_wkb()) + assert temporal == temporal.from_hexwkb(temporal.as_hexwkb()) + assert temporal == temporal.from_mfjson(temporal.as_mfjson()) + + @pytest.mark.parametrize( + 'temporal', + [tfi, tfds, tfs, tfss, tfsts, tfstss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet', + 'Stepwise Sequence', 'Stepwise SequenceSet'] + ) + def test_copy_constructor(self, temporal): + other = copy(temporal) + assert temporal == other + assert temporal is not other + + +class TestTFloatOutputs(TestTFloat): + tfi = TFloatInst('1.5@2019-09-01') + tfds = TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}') + tfs = TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]') + tfss = TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + tfsts = TFloatSeq('Interp=Step;[1.5@2019-09-01, 2.5@2019-09-02]') + tfstss = TFloatSeqSet('Interp=Step;{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, '1.5@2019-09-01 00:00:00+00'), + (tfds, '{1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00}'), + (tfs, '[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00]'), + (tfss, '{[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00], ' + '[1.5@2019-09-03 00:00:00+00, 1.5@2019-09-05 00:00:00+00]}'), + (tfsts, 'Interp=Step;[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00]'), + (tfstss, 'Interp=Step;{[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00], ' + '[1.5@2019-09-03 00:00:00+00, 1.5@2019-09-05 00:00:00+00]}') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet', + 'Stepwise Sequence', 'Stepwise SequenceSet'] + ) + def test_str(self, temporal, expected): + assert str(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, 'TFloatInst(1.5@2019-09-01 00:00:00+00)'), + (tfds, 'TFloatSeq({1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00})'), + (tfs, 'TFloatSeq([1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00])'), + (tfss, 'TFloatSeqSet({[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00], ' + '[1.5@2019-09-03 00:00:00+00, 1.5@2019-09-05 00:00:00+00]})') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_repr(self, temporal, expected): + assert repr(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, '1.5@2019-09-01 00:00:00+00'), + (tfds, '{1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00}'), + (tfs, '[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00]'), + (tfss, '{[1.5@2019-09-01 00:00:00+00, 2.5@2019-09-02 00:00:00+00], ' + '[1.5@2019-09-03 00:00:00+00, 1.5@2019-09-05 00:00:00+00]}') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_wkt(self, temporal, expected): + assert temporal.as_wkt() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, '011B0001000000000000F83F00A01E4E71340200'), + (tfds, '011B00060200000003000000000000F83F00A01E4E7134020000000000000004400000F66B85340200'), + (tfs, '011B000E0200000003000000000000F83F00A01E4E7134020000000000000004400000F66B85340200'), + (tfss, '011B000F020000000200000003000000000000F83F00A01E4E7134020000000000000004400000F66B85340200' + '0200000003000000000000F83F0060CD8999340200000000000000F83F00207CC5C1340200') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_hexwkb(self, temporal, expected): + assert temporal.as_hexwkb() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, '{\n' + ' "type": "MovingFloat",\n' + ' "bbox": [\n' + ' 1.5,\n' + ' 1.5\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-01T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "values": [\n' + ' 1.5\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00"\n' + ' ],\n' + ' "interpolation": "None"\n' + ' }'), + (tfds, '{\n' + ' "type": "MovingFloat",\n' + ' "bbox": [\n' + ' 1.5,\n' + ' 2.5\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-02T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "values": [\n' + ' 1.5,\n' + ' 2.5\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true,\n' + ' "interpolation": "Discrete"\n' + ' }'), + (tfs, '{\n' + ' "type": "MovingFloat",\n' + ' "bbox": [\n' + ' 1.5,\n' + ' 2.5\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-02T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "values": [\n' + ' 1.5,\n' + ' 2.5\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true,\n' + ' "interpolation": "Linear"\n' + ' }'), + (tfss, '{\n' + ' "type": "MovingFloat",\n' + ' "bbox": [\n' + ' 1.5,\n' + ' 2.5\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-05T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "sequences": [\n' + ' {\n' + ' "values": [\n' + ' 1.5,\n' + ' 2.5\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' {\n' + ' "values": [\n' + ' 1.5,\n' + ' 1.5\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-03T00:00:00+00",\n' + ' "2019-09-05T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' }\n' + ' ],\n' + ' "interpolation": "Linear"\n' + ' }') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_mfjson(self, temporal, expected): + assert temporal.as_mfjson() == expected + + +class TestTFloatAccessors(TestTFloat): + tfi = TFloatInst('1.5@2019-09-01') + tfds = TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}') + tfs = TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]') + tfss = TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + tfsts = TFloatSeq('Interp=Step;[1.5@2019-09-01, 2.5@2019-09-02]') + tfstss = TFloatSeqSet('Interp=Step;{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TInterpolation.NONE), + (tfds, TInterpolation.DISCRETE), + (tfs, TInterpolation.LINEAR), + (tfss, TInterpolation.LINEAR) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_interpolation(self, temporal, expected): + assert temporal.interpolation() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, {1.5}), + (tfds, {1.5, 2.5}), + (tfs, {1.5, 2.5}), + (tfss, {1.5, 2.5}) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_set(self, temporal, expected): + assert temporal.value_set() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, [1.5]), + (tfds, [1.5, 2.5]), + (tfs, [1.5, 2.5]), + (tfss, [1.5, 2.5, 1.5, 1.5]) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_values(self, temporal, expected): + assert temporal.values() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, 1.5), + (tfds, 1.5), + (tfs, 1.5), + (tfss, 1.5) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_value(self, temporal, expected): + assert temporal.start_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, 1.5), + (tfds, 2.5), + (tfs, 2.5), + (tfss, 1.5) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_value(self, temporal, expected): + assert temporal.end_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, 1.5), + (tfds, 1.5), + (tfs, 1.5), + (tfss, 1.5) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_min_value(self, temporal, expected): + assert temporal.min_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, 1.5), + (tfds, 2.5), + (tfs, 2.5), + (tfss, 2.5) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_max_value(self, temporal, expected): + assert temporal.max_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, 1.5), + (tfds, 1.5), + (tfs, 1.5), + (tfss, 1.5) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_at_timestamp(self, temporal, expected): + assert temporal.value_at_timestamp(datetime(2019, 9, 1)) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, PeriodSet('{[2019-09-01, 2019-09-01]}')), + (tfds, PeriodSet('{[2019-09-01, 2019-09-01], [2019-09-02, 2019-09-02]}')), + (tfs, PeriodSet('{[2019-09-01, 2019-09-02]}')), + (tfss, PeriodSet('{[2019-09-01, 2019-09-02], [2019-09-03, 2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_time(self, temporal, expected): + assert temporal.time() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, timedelta()), + (tfds, timedelta()), + (tfs, timedelta(days=1)), + (tfss, timedelta(days=3)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_duration(self, temporal, expected): + assert temporal.duration() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, timedelta()), + (tfds, timedelta(days=1)), + (tfs, timedelta(days=1)), + (tfss, timedelta(days=4)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_duration_ignoring_gaps(self, temporal, expected): + assert temporal.duration(ignore_gaps=True) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, Period('[2019-09-01, 2019-09-01]')), + (tfds, Period('[2019-09-01, 2019-09-02]')), + (tfs, Period('[2019-09-01, 2019-09-02]')), + (tfss, Period('[2019-09-01, 2019-09-05]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_period(self, temporal, expected): + assert temporal.period() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, Period('[2019-09-01, 2019-09-01]')), + (tfds, Period('[2019-09-01, 2019-09-02]')), + (tfs, Period('[2019-09-01, 2019-09-02]')), + (tfss, Period('[2019-09-01, 2019-09-05]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timespan(self, temporal, expected): + assert temporal.timespan() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, 1), + (tfds, 2), + (tfs, 2), + (tfss, 4), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_num_instants(self, temporal, expected): + assert temporal.num_instants() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, tfi), + (tfds, tfi), + (tfs, tfi), + (tfss, tfi), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_instant(self, temporal, expected): + assert temporal.start_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, tfi), + (tfds, TFloatInst('2.5@2019-09-02')), + (tfs, TFloatInst('2.5@2019-09-02')), + (tfss, TFloatInst('1.5@2019-09-05')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_instant(self, temporal, expected): + assert temporal.end_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, tfi), + (tfds, TFloatInst('2.5@2019-09-02')), + (tfs, TFloatInst('2.5@2019-09-02')), + (tfss, TFloatInst('2.5@2019-09-02')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_max_instant(self, temporal, expected): + assert temporal.max_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, tfi), + (tfds, tfi), + (tfs, tfi), + (tfss, tfi), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_min_instant(self, temporal, expected): + assert temporal.min_instant() == expected + + @pytest.mark.parametrize( + 'temporal, n, expected', + [ + (tfi, 0, tfi), + (tfds, 1, TFloatInst('2.5@2019-09-02')), + (tfs, 1, TFloatInst('2.5@2019-09-02')), + (tfss, 2, TFloatInst('1.5@2019-09-03')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_instant_n(self, temporal, n, expected): + assert temporal.instant_n(n) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, [tfi]), + (tfds, [tfi, TFloatInst('2.5@2019-09-02')]), + (tfs, [tfi, TFloatInst('2.5@2019-09-02')]), + (tfss, [tfi, TFloatInst('2.5@2019-09-02'), TFloatInst('1.5@2019-09-03'), TFloatInst('1.5@2019-09-05')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_instants(self, temporal, expected): + assert temporal.instants() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, 1), + (tfds, 2), + (tfs, 2), + (tfss, 4), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_num_timestamps(self, temporal, expected): + assert temporal.num_timestamps() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tfds, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tfs, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tfss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_timestamp(self, temporal, expected): + assert temporal.start_timestamp() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tfds, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tfs, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tfss, datetime(year=2019, month=9, day=5, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_timestamp(self, temporal, expected): + assert temporal.end_timestamp() == expected + + @pytest.mark.parametrize( + 'temporal, n, expected', + [ + (tfi, 0, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tfds, 1, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tfs, 1, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tfss, 2, datetime(year=2019, month=9, day=3, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timestamp_n(self, temporal, n, expected): + assert temporal.timestamp_n(n) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)]), + (tfds, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)]), + (tfs, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)]), + (tfss, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=3, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=5, tzinfo=timezone.utc)]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timestamps(self, temporal, expected): + assert temporal.timestamps() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfds, [TFloatSeq('[1.5@2019-09-01]'), TFloatSeq('[2.5@2019-09-02]')]), + (tfs, [TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]')]), + (tfss, [TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]'), + TFloatSeq('[1.5@2019-09-03, 1.5@2019-09-05]')]), + (tfsts, [TFloatSeq('Interp=Step;[1.5@2019-09-01, 1.5@2019-09-02)'), + TFloatSeq('Interp=Step;[2.5@2019-09-02]')]), + (tfstss, [TFloatSeq('Interp=Step;[1.5@2019-09-01, 1.5@2019-09-02)'), + TFloatSeq('Interp=Step;[2.5@2019-09-02]'), + TFloatSeq('Interp=Step;[1.5@2019-09-03, 1.5@2019-09-05]')]), + ], + ids=['Discrete Sequence', 'Sequence', 'SequenceSet', 'Stepwise Sequence', 'Stepwise SequenceSet'] + ) + def test_segments(self, temporal, expected): + assert temporal.segments() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfds, True), + (tfs, True), + (tfsts, True), + ], + ids=['Discrete Sequence', 'Sequence', 'Stepwise Sequence'] + ) + def test_lower_upper_inc(self, temporal, expected): + assert temporal.lower_inc() == expected + assert temporal.upper_inc() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, 1307112078), + (tfds, 1935376725), + (tfs, 1935376725), + (tfss, 4247071962), + (tfs, 1935376725), + (tfss, 4247071962) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet', + 'Stepwise Sequence', 'Stepwise SequenceSet'] + ) + def test_hash(self, temporal, expected): + assert hash(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TBox('TBOX XT([1.5,1.5],[2019-09-01, 2019-09-01])')), + (tfds, TBox('TBOX XT([1.5,2.5],[2019-09-01, 2019-09-02])')), + (tfs, TBox('TBOX XT([1.5,2.5],[2019-09-01, 2019-09-02])')), + (tfss, TBox('TBOX XT([1.5,2.5],[2019-09-01, 2019-09-05])')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_bounding_box(self, temporal, expected): + assert temporal.bounding_box() == expected + + +class TestTFloatTransformations(TestTFloat): + tfi = TFloatInst('1.5@2019-09-01') + tfds = TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}') + tfs = TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]') + tfsts = TFloatSeq('Interp=Step;[1.5@2019-09-01, 2.5@2019-09-02]') + tfss = TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + tfstss = TFloatSeqSet('Interp=Step;{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TFloatInst('1.5@2019-09-01'), tfi), + (TFloatSeq('{1.5@2019-09-01}'), tfi), + (TFloatSeq('[1.5@2019-09-01]'), tfi), + (TFloatSeqSet('{[1.5@2019-09-01]}'), tfi), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_instant(self, temporal, expected): + temp = temporal.to_instant() + assert isinstance(temp, TFloatInst) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TFloatInst('1.5@2019-09-01'), + TFloatSeq('[1.5@2019-09-01]')), + (TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}'), + TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}')), + (TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]'), + TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]')), + (TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02]}'), + TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_sequence(self, temporal, expected): + temp = temporal.to_sequence() + assert isinstance(temp, TFloatSeq) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TFloatInst('1.5@2019-09-01'), + TFloatSeqSet('{[1.5@2019-09-01]}')), + (TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}'), + TFloatSeqSet('{[1.5@2019-09-01], [2.5@2019-09-02]}')), + (TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]'), + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02]}')), + (TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02]}'), + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_sequenceset(self, temporal, expected): + temp = temporal.to_sequenceset() + assert isinstance(temp, TFloatSeqSet) + assert temp == expected + + @pytest.mark.parametrize( + 'tfloat, delta, expected', + [(tfi, timedelta(days=4), TFloatInst('1.5@2019-09-05')), + (tfi, timedelta(days=-4), TFloatInst('1.5@2019-08-28')), + (tfi, timedelta(hours=2), TFloatInst('1.5@2019-09-01 02:00:00')), + (tfi, timedelta(hours=-2), TFloatInst('1.5@2019-08-31 22:00:00')), + (tfds, timedelta(days=4), TFloatSeq('{1.5@2019-09-05, 2.5@2019-09-06}')), + (tfds, timedelta(days=-4), TFloatSeq('{1.5@2019-08-28, 2.5@2019-08-29}')), + (tfds, timedelta(hours=2), TFloatSeq('{1.5@2019-09-01 02:00:00, 2.5@2019-09-02 02:00:00}')), + (tfds, timedelta(hours=-2), TFloatSeq('{1.5@2019-08-31 22:00:00, 2.5@2019-09-01 22:00:00}')), + (tfs, timedelta(days=4), TFloatSeq('[1.5@2019-09-05, 2.5@2019-09-06]')), + (tfs, timedelta(days=-4), TFloatSeq('[1.5@2019-08-28, 2.5@2019-08-29]')), + (tfs, timedelta(hours=2), TFloatSeq('[1.5@2019-09-01 02:00:00, 2.5@2019-09-02 02:00:00]')), + (tfs, timedelta(hours=-2), TFloatSeq('[1.5@2019-08-31 22:00:00, 2.5@2019-09-01 22:00:00]')), + (tfss, timedelta(days=4), + TFloatSeqSet('{[1.5@2019-09-05, 2.5@2019-09-06],[1.5@2019-09-07, 1.5@2019-09-09]}')), + (tfss, timedelta(days=-4), + TFloatSeqSet('{[1.5@2019-08-28, 2.5@2019-08-29],[1.5@2019-08-30, 1.5@2019-09-01]}')), + (tfss, timedelta(hours=2), + TFloatSeqSet('{[1.5@2019-09-01 02:00:00, 2.5@2019-09-02 02:00:00],' + '[1.5@2019-09-03 02:00:00, 1.5@2019-09-05 02:00:00]}')), + (tfss, timedelta(hours=-2), + TFloatSeqSet('{[1.5@2019-08-31 22:00:00, 2.5@2019-09-01 22:00:00],' + '[1.5@2019-09-02 22:00:00, 1.5@2019-09-04 22:00:00]}')), + ], + ids=['Instant positive days', 'Instant negative days', + 'Instant positive hours', 'Instant negative hours', + 'Discrete Sequence positive days', 'Discrete Sequence negative days', + 'Discrete Sequence positive hours', 'Discrete Sequence negative hours', + 'Sequence positive days', 'Sequence negative days', + 'Sequence positive hours', 'Sequence negative hours', + 'Sequence Set positive days', 'Sequence Set negative days', + 'Sequence Set positive hours', 'Sequence Set negative hours'] + ) + def test_shift(self, tfloat, delta, expected): + assert tfloat.shift(delta) == expected + + @pytest.mark.parametrize( + 'tfloat, delta, expected', + [(tfi, timedelta(days=4), TFloatInst('1.5@2019-09-01')), + (tfi, timedelta(hours=2), TFloatInst('1.5@2019-09-01')), + (tfds, timedelta(days=4), TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-05}')), + (tfds, timedelta(hours=2), TFloatSeq('{1.5@2019-09-01 00:00:00, 2.5@2019-09-01 02:00:00}')), + (tfs, timedelta(days=4), TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-05]')), + (tfs, timedelta(hours=2), TFloatSeq('[1.5@2019-09-01 00:00:00, 2.5@2019-09-01 02:00:00]')), + (tfss, timedelta(days=4), + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), + (tfss, timedelta(hours=2), + TFloatSeqSet('{[1.5@2019-09-01 00:00:00, 2.5@2019-09-01 00:30:00],' + '[1.5@2019-09-01 01:00:00, 1.5@2019-09-01 02:00:00]}')), + ], + ids=['Instant positive days', 'Instant positive hours', + 'Discrete Sequence positive days', 'Discrete Sequence positive hours', + 'Sequence positive days', 'Sequence positive hours', + 'Sequence Set positive days', 'Sequence Set positive hours'] + ) + def test_scale(self, tfloat, delta, expected): + assert tfloat.tscale(delta) == expected + + def test_shift_tscale(self): + assert self.tfss.shift_tscale(timedelta(days=4), timedelta(hours=2)) == \ + TFloatSeqSet('{[1.5@2019-09-05 00:00:00, 2.5@2019-09-05 00:30:00],' + '[1.5@2019-09-05 01:00:00, 1.5@2019-09-05 02:00:00]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TIntInst('1@2019-09-01')), + (tfds, TIntSeq('{1@2019-09-01,2@2019-09-02}')), + (tfsts, TIntSeq('[1@2019-09-01,2@2019-09-02]')), + (tfstss, TIntSeq('{[1@2019-09-01,2@2019-09-02],[1@2019-09-03,1@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Step Sequence', 'Step Sequence Set'] + ) + def test_to_tint(self, temporal, expected): + temp = temporal.to_tint() + assert isinstance(temp, TInt) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, floatrange(1.5, 1.5, True, True)), + (tfds, floatrange(1.5, 2.5, True, True)), + (tfs, floatrange(1.5, 2.5, True, True)), + (tfss, floatrange(1.5, 2.5, True, True)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_floatrange(self, temporal, expected): + assert temporal.to_floatrange() == expected + + +class TestTFloatModifications(TestTFloat): + tfi = TFloatInst('1.5@2019-09-01') + tfds = TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}') + tfs = TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]') + tfss = TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, sequence, expected', + [ + (tfi, TFloatSeq('{1.5@2019-09-03}'), TFloatSeq('{1.5@2019-09-01, 1.5@2019-09-03}')), + (tfds, TFloatSeq('{1.5@2019-09-03}'), TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02, 1.5@2019-09-03}')), + (tfs, TFloatSeq('[1.5@2019-09-03]'), TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02, 1.5@2019-09-03]}')), + (tfss, TFloatSeq('[1.5@2019-09-06]'), + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05],[1.5@2019-09-06]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_insert(self, temporal, sequence, expected): + assert temporal.insert(sequence) == expected + + @pytest.mark.parametrize( + 'temporal, instant, expected', + [ + (tfi, TFloatInst('2.5@2019-09-01'), TFloatInst('2.5@2019-09-01')), + (tfds, TFloatInst('2.5@2019-09-01'), TFloatSeq('{2.5@2019-09-01, 2.5@2019-09-02}')), + (tfs, TFloatInst('2.5@2019-09-01'), + TFloatSeqSet('{[2.5@2019-09-01], (1.5@2019-09-01, 2.5@2019-09-02]}')), + (tfss, TFloatInst('2.5@2019-09-01'), + TFloatSeqSet('{[2.5@2019-09-01], (1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_update(self, temporal, instant, expected): + assert temporal.update(instant) == expected + + @pytest.mark.parametrize( + 'temporal, time, expected', + [ + (tfi, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), None), + (tfi, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), tfi), + (tfds, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), TFloatSeq('{2.5@2019-09-02}')), + (tfs, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), + (tfss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), + ], + ids=['Instant intersection', 'Instant disjoint', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_delete(self, temporal, time, expected): + assert temporal.delete(time) == expected + + @pytest.mark.parametrize( + 'temporal, instant, expected', + [ + (tfi, TFloatInst('1.5@2019-09-02'), TFloatSeq('{1.5@2019-09-01, 1.5@2019-09-02}')), + (tfds, TFloatInst('1.5@2019-09-03'), TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02, 1.5@2019-09-03}')), + (tfs, TFloatInst('1.5@2019-09-03'), TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02, 1.5@2019-09-03]')), + (tfss, TFloatInst('1.5@2019-09-06'), + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-06]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_append_instant(self, temporal, instant, expected): + assert temporal.append_instant(instant) == expected + + @pytest.mark.parametrize( + 'temporal, sequence, expected', + [ + (tfds, TFloatSeq('{1.5@2019-09-03}'), TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02, 1.5@2019-09-03}')), + (tfs, TFloatSeq('[1.5@2019-09-03]'), TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02], [1.5@2019-09-03]}')), + (tfss, TFloatSeq('[1.5@2019-09-06]'), + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05],[1.5@2019-09-06]}')), + ], + ids=['Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_append_sequence(self, temporal, sequence, expected): + assert temporal.append_sequence(sequence) == expected + + +class TestTFloatMathematicalOperations(TestTFloat): + tfi = TFloatInst('1.5@2019-09-01') + tfds = TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}') + tfs = TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]') + tfss = TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + tintarg = TIntSeq('[2@2019-09-01, 1@2019-09-02, 1@2019-09-03]') + tfloatarg = TFloatSeq('[2.5@2019-09-01, 1.5@2019-09-02, 1.5@2019-09-03]') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, tintarg, TFloatInst('3.5@2019-09-01')), + (tfds, tintarg, TFloatSeq('{3.5@2019-09-01, 3.5@2019-09-02}')), + (tfs, tintarg, TFloatSeqSet('{[3.5@2019-09-01, 4.5@2019-09-02),[3.5@2019-09-02]}')), + (tfss, tintarg, TFloatSeqSet('{[3.5@2019-09-01, 4.5@2019-09-02),[3.5@2019-09-02],[2.5@2019-09-03]}')), + (tfi, tfloatarg, TFloatInst('4@2019-09-01')), + (tfds, tfloatarg, TFloatSeq('{4@2019-09-01, 4@2019-09-02}')), + (tfs, tfloatarg, TFloatSeqSet('{[4@2019-09-01, 4@2019-09-02]}')), + (tfss, tfloatarg, TFloatSeqSet('{[4@2019-09-01, 4@2019-09-02],[3@2019-09-03]}')), + ], + ids=['Instant TInt', 'Discrete Sequence TInt', 'Sequence TInt', 'SequenceSet TInt', + 'Instant TFloat', 'Discrete Sequence TFloat', 'Sequence TFloat', 'SequenceSet TFloat'] + ) + def test_temporal_add_temporal(self, temporal, argument, expected): + assert temporal.add(argument) == expected + assert temporal + argument == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, 1, TFloatInst('2.5@2019-09-01')), + (tfds, 1, TFloatSeq('{2.5@2019-09-01, 3.5@2019-09-02}')), + (tfs, 1, TFloatSeq('[2.5@2019-09-01, 3.5@2019-09-02]')), + (tfss, 1, TFloatSeqSet('{[2.5@2019-09-01, 3.5@2019-09-02],[2.5@2019-09-03, 2.5@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_add_int_float(self, temporal, argument, expected): + assert temporal.add(argument) == expected + assert temporal.add(float(argument)) == expected + assert temporal.radd(argument) == expected + assert temporal.radd(float(argument)) == expected + assert (temporal + argument) == expected + assert (temporal + float(argument)) == expected + assert (argument + temporal) == expected + assert (float(argument) + temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, tintarg, TFloatInst('-0.5@2019-09-01')), + (tfds, tintarg, TFloatSeq('{-0.5@2019-09-01, 1.5@2019-09-02}')), + (tfs, tintarg, TFloatSeqSet('{[-0.5@2019-09-01, 0.5@2019-09-02), [1.5@2019-09-02]}')), + (tfss, tintarg, TFloatSeqSet('{[-0.5@2019-09-01, 0.5@2019-09-02), [1.5@2019-09-02],[0.5@2019-09-03]}')), + (tfi, tfloatarg, TFloatInst('-1@2019-09-01')), + (tfds, tfloatarg, TFloatSeq('{-1@2019-09-01, 1@2019-09-02}')), + (tfs, tfloatarg, TFloatSeqSet('{[-1@2019-09-01, 1@2019-09-02]}')), + (tfss, tfloatarg, TFloatSeqSet('{[-1@2019-09-01, 1@2019-09-02],[0@2019-09-03]}')), + ], + ids=['Instant TInt', 'Discrete Sequence TInt', 'Sequence TInt', 'SequenceSet TInt', + 'Instant TFloat', 'Discrete Sequence TFloat', 'Sequence TFloat', 'SequenceSet TFloat'] + ) + def test_temporal_sub_temporal(self, temporal, argument, expected): + assert temporal.sub(argument) == expected + assert temporal - argument == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, 1, TFloatInst('0.5@2019-09-01')), + (tfds, 1, TFloatSeq('{0.5@2019-09-01, 1.5@2019-09-02}')), + (tfs, 1, TFloatSeq('[0.5@2019-09-01, 1.5@2019-09-02]')), + (tfss, 1, TFloatSeqSet('{[0.5@2019-09-01, 1.5@2019-09-02],[0.5@2019-09-03, 0.5@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_sub_int_float(self, temporal, argument, expected): + assert temporal.sub(argument) == expected + assert temporal.sub(float(argument)) == expected + assert temporal.rsub(argument) == -1 * expected + assert temporal.rsub(float(argument)) == -1 * expected + assert (temporal - argument) == expected + assert (temporal - float(argument)) == expected + assert (argument - temporal) == -1 * expected + assert (float(argument) - temporal) == -1 * expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, tintarg, TFloatInst('3@2019-09-01')), + (tfds, tintarg, TFloatSeq('{3@2019-09-01, 2.5@2019-09-02}')), + (tfs, tintarg, TFloatSeqSet('{[3@2019-09-01, 5@2019-09-02 00:00:00+00), [2.5@2019-09-02]}')), + (tfss, tintarg, TFloatSeqSet('{[3@2019-09-01, 5@2019-09-02 00:00:00+00), [2.5@2019-09-02],[1.5@2019-09-03]}')), + (tfi, tfloatarg, TFloatInst('3.75@2019-09-01')), + (tfds, tfloatarg, TFloatSeq('{3.75@2019-09-01, 3.75@2019-09-02}')), + (tfs, tfloatarg, TFloatSeqSet('{[3.75@2019-09-01, 4@2019-09-01 12:00:00+00, 3.75@2019-09-02]}')), + (tfss, tfloatarg, TFloatSeqSet('{[3.75@2019-09-01, 4@2019-09-01 12:00:00+00, 3.75@2019-09-02], [2.25@2019-09-03]}')), + ], + ids=['Instant TInt', 'Discrete Sequence TInt', 'Sequence TInt', 'SequenceSet TInt', + 'Instant TFloat', 'Discrete Sequence TFloat', 'Sequence TFloat', 'SequenceSet TFloat'] + ) + def test_temporal_mul_temporal(self, temporal, argument, expected): + assert temporal.mul(argument) == expected + assert temporal * argument == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, 0, TFloat.from_base_temporal(0, tfi)), + (tfds, 0, TFloat.from_base_temporal(0, tfds)), + (tfs, 0, TFloat.from_base_temporal(0, tfs)), + (tfss, 0, TFloat.from_base_temporal(0, tfss)), + + (tfi, 1, tfi), + (tfds, 1, tfds), + (tfs, 1, tfs), + (tfss, 1, tfss), + + (tfi, 2, TFloatInst('3@2019-09-01')), + (tfds, 2, TFloatSeq('{3@2019-09-01, 5@2019-09-02}')), + (tfs, 2, TFloatSeq('[3@2019-09-01, 5@2019-09-02]')), + (tfss, 2, TFloatSeqSet('{[3@2019-09-01, 5@2019-09-02],[3@2019-09-03, 3@2019-09-05]}')), + ], + ids=['Instant 0', 'Discrete Sequence 0', 'Sequence 0', 'SequenceSet 0', + 'Instant 1', 'Discrete Sequence 1', 'Sequence 1', 'SequenceSet 1', + 'Instant 2', 'Discrete Sequence 2', 'Sequence 2', 'SequenceSet 2'] + ) + def test_temporal_mul_int_float(self, temporal, argument, expected): + assert temporal.mul(argument) == expected + assert temporal.rmul(argument) == expected + assert (temporal * argument) == expected + assert (argument * temporal) == expected + assert temporal.mul(float(argument)) == expected + assert temporal.rmul(float(argument)) == expected + assert (temporal * float(argument)) == expected + assert (float(argument) * temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, tintarg, TFloatInst('0.75@2019-09-01')), + (tfds, tintarg, TFloatSeq('{0.75@2019-09-01, 2.5@2019-09-02}')), + (tfs, tintarg, TFloatSeqSet('{[0.75@2019-09-01, 1.25@2019-09-02 00:00:00+00), [2.5@2019-09-02]}')), + (tfss, tintarg, TFloatSeqSet('{[0.75@2019-09-01, 1.25@2019-09-02 00:00:00+00), [2.5@2019-09-02], [1.5@2019-09-03]}')), + (tfi, tfloatarg, TFloatInst('0.6@2019-09-01')), + (tfds, tfloatarg, TFloatSeq('{0.6@2019-09-01, 1.667@2019-09-02}')), + (tfs, tfloatarg, TFloatSeqSet('{[0.6@2019-09-01, 1@2019-09-01 12:00:00+00, 1.667@2019-09-02]}')), + (tfss, tfloatarg, TFloatSeqSet('{[0.6@2019-09-01, 1@2019-09-01 12:00:00+00, 1.667@2019-09-02], [1@2019-09-03]}')), + ], + ids=['Instant TInt', 'Discrete Sequence TInt', 'Sequence TInt', 'SequenceSet TInt', + 'Instant TFloat', 'Discrete Sequence TFloat', 'Sequence TFloat', 'SequenceSet TFloat'] + ) + def test_temporal_div_temporal(self, temporal, argument, expected): + assert temporal.div(argument).round(3) == expected + assert (temporal / argument).round(3) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, 1, tfi), + (tfds, 1, tfds), + (tfs, 1, tfs), + (tfss, 1, tfss), + (tfi, 2, TFloatInst('0.75@2019-09-01')), + (tfds, 2, TFloatSeq('{0.75@2019-09-01, 1.25@2019-09-02}')), + (tfs, 2, TFloatSeq('[0.75@2019-09-01, 1.25@2019-09-02]')), + (tfss, 2, TFloatSeqSet('{[0.75@2019-09-01, 1.25@2019-09-02],[0.75@2019-09-03, 0.75@2019-09-05]}')), + ], + ids=['Instant 1', 'Discrete Sequence 1', 'Sequence 1', 'SequenceSet 1', + 'Instant 2', 'Discrete Sequence 2', 'Sequence 2', 'SequenceSet 2'] + ) + def test_temporal_div_int_float(self, temporal, argument, expected): + assert temporal.div(argument) == expected + assert temporal.div(float(argument)) == expected + assert (temporal / argument) == expected + assert (temporal / float(argument)) == expected + + @pytest.mark.parametrize( + 'temporal', + [tfi, tfds, tfs, tfss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_abs(self, temporal): + assert temporal.abs() == temporal + assert (-1 * temporal).abs() == temporal + + @pytest.mark.parametrize( + 'temporal, expected', + [ + # (tfi, TFloatInst('1@2019-09-01')), + (tfds, TFloatSeq('{1@2019-09-01, 1@2019-09-02}')), + (tfs, TFloatSeq('Interp=Step;[1@2019-09-01, 1@2019-09-02)')), + (tfss, TFloatSeqSet('Interp=Step;{[1@2019-09-01, 1@2019-09-02),[0@2019-09-03, 0@2019-09-05)}')), + ], + ids=[# 'Instant', + 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_delta_value(self, temporal, expected): + assert temporal.delta_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TFloatInst('0.0262@2019-09-01')), + (tfds, TFloatSeq('{0.0262@2019-09-01, 0.0436@2019-09-02}')), + (tfs, TFloatSeq('[0.0262@2019-09-01, 0.0436@2019-09-02]')), + (tfss, TFloatSeqSet('{[0.0262@2019-09-01, 0.0436@2019-09-02],[0.0262@2019-09-03, 0.0262@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_radians(self, temporal, expected): + assert temporal.to_radians().round(4) == expected + + @pytest.mark.parametrize( + 'temporal', + [tfi, tfds, tfs, tfss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_radians_to_degrees(self, temporal): + assert temporal.to_radians().to_degrees() == temporal + + @pytest.mark.parametrize( + 'temporal, expected', + [ + # (tfi, None), + # (tfds, None), + (tfs, TFloatSeq('Interp=Step;[-1@2019-09-01, -1@2019-09-02]')), + (tfss, TFloatSeqSet('Interp=Step;{[-1@2019-09-01, -1@2019-09-02],[0@2019-09-03, 0@2019-09-05]}')), + ], + ids=[ # 'Instant', 'Discrete Sequence', + 'Sequence', 'SequenceSet'] + ) + def test_derivative(self, temporal, expected): + assert temporal.derivative() * 3600 * 24 == expected + + +class TestTFloatRestrictors(TestTFloat): + tfi = TFloatInst('1.5@2019-09-01') + tfds = TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}') + tfs = TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]') + tfss = TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + + timestamp = datetime(2019, 9, 1) + timestamp_set = TimestampSet('{2019-09-01, 2019-09-03}') + period = Period('[2019-09-01, 2019-09-02]') + period_set = PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, restrictor, expected', + [ + (tfi, timestamp, TFloatInst('1.5@2019-09-01')), + (tfi, timestamp_set, TFloatInst('1.5@2019-09-01')), + (tfi, period, TFloatInst('1.5@2019-09-01')), + (tfi, period_set, TFloatInst('1.5@2019-09-01')), + (tfi, 1.5, TFloatInst('1.5@2019-09-01')), + (tfi, 2.5, None), + # (tfi, [1.5,2.5], TFloatInst('1.5@2019-09-01')), + + (tfds, timestamp, TFloatSeq('{1.5@2019-09-01}')), + (tfds, timestamp_set, TFloatSeq('{1.5@2019-09-01}')), + (tfds, period, TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}')), + (tfds, period_set, TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}')), + (tfds, 1.5, TFloatSeq('{1.5@2019-09-01}')), + (tfds, 2.5, TFloatSeq('{2.5@2019-09-02}')), + # (tfds, [1.5,2.5], TFloatSeq('{1.5@2019-09-01}')), + + (tfs, timestamp, TFloatSeq('[1.5@2019-09-01]')), + (tfs, timestamp_set, TFloatSeq('{1.5@2019-09-01}')), + (tfs, period, TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]')), + (tfs, period_set, TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]')), + (tfs, 1.5, TFloatSeq('[1.5@2019-09-01]')), + (tfs, 2.5, TFloatSeq('[2.5@2019-09-02]')), + # (tfs, [1.5,2.5], TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02)}')), + + (tfss, timestamp, TFloatSeqSet('[1.5@2019-09-01]')), + (tfss, timestamp_set, TFloatSeq('{1.5@2019-09-01, 1.5@2019-09-03}')), + (tfss, period, TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02]}')), + (tfss, period_set, + TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), + (tfss, 1.5, TFloatSeqSet('{[1.5@2019-09-01],[1.5@2019-09-03, 1.5@2019-09-05]}')), + (tfss, 2.5, TFloatSeqSet('{[2.5@2019-09-02]}')) + # (tfss, [1.5,2.5], TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02),[1.5@2019-09-03, 1.5@2019-09-05]}')) + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', + 'Instant-PeriodSet', 'Instant-1_5', 'Instant-2_5', + #'Instant-[1.5,2.5]', + 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', + 'Discrete Sequence-1_5', 'Discrete Sequence-2_5', + # 'Discrete Sequence-[1.5,2.5]' + 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-1_5', 'Sequence-2_5', + # 'Sequence-[1.5,2.5]', + 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', + 'SequenceSet-PeriodSet', 'SequenceSet-1_5', 'SequenceSet-2_5', + #'SequenceSet-[1.5,2.5]' + ] + ) + def test_at(self, temporal, restrictor, expected): + assert temporal.at(restrictor) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TFloatInst('1.5@2019-09-01')), + (tfds, TFloatSeq('{1.5@2019-09-01}')), + (tfs, TFloatSeq('{[1.5@2019-09-01]}')), + (tfss, TFloatSeqSet('{[1.5@2019-09-01],[1.5@2019-09-03, 1.5@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_min(self, temporal, expected): + assert temporal.at_min() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TFloatInst('1.5@2019-09-01')), + (tfds, TFloatSeq('{2.5@2019-09-02}')), + (tfs, TFloatSeq('{[2.5@2019-09-02]}')), + (tfss, TFloatSeqSet('{[2.5@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_max(self, temporal, expected): + assert temporal.at_max() == expected + + @pytest.mark.parametrize( + 'temporal, restrictor, expected', + [ + (tfi, timestamp, None), + (tfi, timestamp_set, None), + (tfi, period, None), + (tfi, period_set, None), + (tfi, 1.5, None), + (tfi, 2.5, TFloatInst('1.5@2019-09-01')), + # (tfi, [1.5,2.5], TFloatInst('1.5@2019-09-01')), + + (tfds, timestamp, TFloatSeq('{2.5@2019-09-02}')), + (tfds, timestamp_set, TFloatSeq('{2.5@2019-09-02}')), + (tfds, period, None), + (tfds, period_set, None), + (tfds, 1.5, TFloatSeq('{2.5@2019-09-02}')), + (tfds, 2.5, TFloatSeq('{1.5@2019-09-01}')), + # (tfds, [1.5,2.5], TFloatSeq('{1.5@2019-09-01}')), + + (tfs, timestamp, TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), + (tfs, timestamp_set, TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), + (tfs, period, None), + (tfs, period_set, None), + (tfs, 1.5, TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), + (tfs, 2.5, TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02)}')), + # (tfs, [1.5,2.5], TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02)}')), + + (tfss, timestamp, + TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}')), + (tfss, timestamp_set, + TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02],(1.5@2019-09-03, 1.5@2019-09-05]}')), + (tfss, period, TFloatSeqSet('{[1.5@2019-09-03, 1.5@2019-09-05]}')), + (tfss, period_set, None), + (tfss, 1.5, TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), + (tfss, 2.5, TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02),[1.5@2019-09-03, 1.5@2019-09-05]}')), + # (tfss, [1.5,2.5], TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02),[1.5@2019-09-03, 1.5@2019-09-05]}')) + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', + 'Instant-PeriodSet', 'Instant-1_5', 'Instant-2_5', + #'Instant-[1.5,2.5]', + 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', + 'Discrete Sequence-1_5', 'Discrete Sequence-2_5', + # 'Discrete Sequence-[1.5,2.5]' + 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-1_5', 'Sequence-2_5', + # 'Sequence-[1.5,2.5]', + 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', + 'SequenceSet-PeriodSet', 'SequenceSet-1_5', 'SequenceSet-2_5', + #'SequenceSet-[1.5,2.5]' + ] + ) + def test_minus(self, temporal, restrictor, expected): + assert temporal.minus(restrictor) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, None), + (tfds, TFloatSeq('{2.5@2019-09-02}')), + (tfs, TFloatSeq('{(1.5@2019-09-01, 2.5@2019-09-02]}')), + (tfss, TFloatSeqSet('{(1.5@2019-09-01, 2.5@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_minus_min(self, temporal, expected): + assert temporal.minus_min() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, None), + (tfds, TFloatSeq('{1.5@2019-09-01}')), + (tfs, TFloatSeq('{[1.5@2019-09-01, 2.5@2019-09-02)}')), + (tfss, TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02), [1.5@2019-09-03, 1.5@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_minus_max(self, temporal, expected): + assert temporal.minus_max() == expected + + @pytest.mark.parametrize( + 'temporal, restrictor', + [ + (tfi, timestamp), + (tfi, timestamp_set), + (tfi, period), + (tfi, period_set), + (tfi, 1.5), + (tfi, 2.5), + # (tfi, [1.5,2.5]), + + (tfds, timestamp), + (tfds, timestamp_set), + (tfds, period), + (tfds, period_set), + (tfds, 1.5), + (tfds, 2.5), + # (tfds, [1.5,2.5]), + + (tfs, timestamp), + (tfs, timestamp_set), + (tfs, period), + (tfs, period_set), + (tfs, 1.5), + (tfs, 2.5), + # (tfs, [1.5,2.5]), + + (tfss, timestamp), + (tfss, timestamp_set), + (tfss, period), + (tfss, period_set), + (tfss, 1.5), + (tfss, 2.5), + # (tfss, [1.5,2.5]), + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', + 'Instant-PeriodSet', 'Instant-1_5', 'Instant-2_5', # 'Instant-[1.5,2.5]' + 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', + 'Discrete Sequence-1_5', 'Discrete Sequence-2_5', # 'Discrete Sequence-[1.5,2.5]', + 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-1_5', 'Sequence-2_5', # 'Sequence-[1.5,2.5]' + 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', + 'SequenceSet-PeriodSet', 'SequenceSet-1_5', 'SequenceSet-2_5', + # 'SequenceSet-[1.5,2.5]' + ] + ) + def test_at_minus(self, temporal, restrictor): + assert TFloat.merge(temporal.at(restrictor), temporal.minus(restrictor)) == temporal + + @pytest.mark.parametrize( + 'temporal', + [tfi, tfds, tfs, tfss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_minus_min_max(self, temporal): + assert TFloat.merge(temporal.at_min(), temporal.minus_min()) == temporal + assert TFloat.merge(temporal.at_max(), temporal.minus_max()) == temporal + + +class TestTFloatTopologicalFunctions(TestTFloat): + tfi = TFloatInst('1@2019-09-01') + tfds = TFloatSeq('{1@2019-09-01, 2@2019-09-02}') + tfs = TFloatSeq('[1@2019-09-01, 2@2019-09-02]') + tfss = TFloatSeqSet('{[1@2019-09-01, 2@2019-09-02], [1@2019-09-03, 1@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('1@2019-09-02'), False), + (tfi, TFloatSeq('(1@2019-09-01, 2@2019-09-02]'), True), + (tfds, TFloatInst('1@2019-09-03'), False), + (tfds, TFloatSeq('(2@2019-09-01, 3@2019-09-02]'), True), + (tfs, TFloatInst('1@2019-09-03'), False), + (tfs, TFloatSeq('(2@2019-09-01, 3@2019-09-02]'), True), + (tfss, TFloatInst('1@2019-09-08'), False), + (tfss, TFloatSeq('(2@2019-09-01, 3@2019-09-02]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_adjacent(self, temporal, argument, expected): + assert temporal.is_adjacent(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('1@2019-09-02'), False), + (tfi, TFloatSeq('(1@2019-09-01, 2@2019-09-02]'), True), + (tfds, TFloatInst('1@2019-09-03'), False), + (tfds, TFloatSeq('(1@2019-09-02, 2@2019-09-03]'), True), + (tfs, TFloatInst('1@2019-09-03'), False), + (tfs, TFloatSeq('(1@2019-09-02, 2@2019-09-03]'), True), + (tfss, TFloatInst('1@2019-09-08'), False), + (tfss, TFloatSeq('(1@2019-09-05, 2@2019-09-06]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_temporally_adjacent(self, temporal, argument, expected): + assert temporal.is_temporally_adjacent(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('1@2019-09-02'), False), + (tfi, TFloatSeq('[1@2019-09-01,2@2019-09-02]'), True), + (tfds, TFloatInst('1@2019-09-02'), False), + (tfds, TFloatSeq('[1@2019-09-01,2@2019-09-02]'), True), + (tfs, TFloatInst('1@2019-09-02'), False), + (tfs, TFloatSeq('[1@2019-09-01,2@2019-09-05]'), True), + (tfss, TFloatInst('1@2019-09-02'), False), + (tfss, TFloatSeq('[1@2019-09-01,2@2019-09-05]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_contained_in_contains(self, temporal, argument, expected): + assert temporal.is_contained_in(argument) == expected + assert argument.contains(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('1@2019-09-02'), False), + (tfi, TFloatSeq('[1@2019-09-01,2@2019-09-02]'), True), + (tfds, TFloatInst('1@2019-09-02'), False), + (tfds, TFloatSeq('[1@2019-09-01,2@2019-09-02]'), True), + (tfs, TFloatInst('1@2019-09-02'), False), + (tfs, TFloatSeq('[1@2019-09-01,2@2019-09-05]'), True), + (tfss, TFloatInst('1@2019-09-02'), False), + (tfss, TFloatSeq('[1@2019-09-01,2@2019-09-05]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_temporally_contained_in_contains(self, temporal, argument, expected): + assert temporal.is_temporally_contained_in(argument) == expected + assert argument.temporally_contains(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('1@2019-09-02'), False), + (tfi, TFloatSeq('[1@2019-09-01]'), True), + (tfds, TFloatInst('3@2019-09-02'), False), + (tfds, TFloatSeq('[1@2019-09-01,2@2019-09-02]'), True), + (tfs, TFloatInst('3@2019-09-02'), False), + (tfs, TFloatSeq('[1@2019-09-01,2@2019-09-02]'), True), + (tfss, TFloatInst('3@2019-09-02'), False), + (tfss, TFloatSeq('[1@2019-09-01,2@2019-09-05]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_overlaps_is_same(self, temporal, argument, expected): + assert temporal.overlaps(argument) == expected + assert temporal.is_same(argument) == expected + + +class TestTFloatPositionFunctions(TestTFloat): + tfi = TFloatInst('1@2019-09-01') + tfds = TFloatSeq('{1@2019-09-01, 2@2019-09-02}') + tfs = TFloatSeq('[1@2019-09-01, 2@2019-09-02]') + tfss = TFloatSeqSet('{[1@2019-09-01, 2@2019-09-02], [1@2019-09-03, 1@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('1@2019-09-01'), False), + (tfi, TFloatInst('1@2019-10-01'), True), + (tfds, TFloatInst('1@2019-09-01'), False), + (tfds, TFloatInst('1@2019-10-01'), True), + (tfs, TFloatInst('1@2019-09-01'), False), + (tfs, TFloatInst('1@2019-10-01'), True), + (tfss, TFloatInst('1@2019-09-01'), False), + (tfss, TFloatInst('1@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_before_after(self, temporal, argument, expected): + assert temporal.is_before(argument) == expected + assert argument.is_after(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('1@2019-08-01'), False), + (tfi, TFloatInst('1@2019-10-01'), True), + (tfds, TFloatInst('1@2019-08-01'), False), + (tfds, TFloatInst('1@2019-10-01'), True), + (tfs, TFloatInst('1@2019-08-01'), False), + (tfs, TFloatInst('1@2019-10-01'), True), + (tfss, TFloatInst('1@2019-08-01'), False), + (tfss, TFloatInst('1@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_before(self, temporal, argument, expected): + assert temporal.is_over_or_before(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('1@2019-10-01'), False), + (tfi, TFloatInst('1@2019-09-01'), True), + (tfds, TFloatInst('1@2019-10-01'), False), + (tfds, TFloatInst('1@2019-09-01'), True), + (tfs, TFloatInst('1@2019-10-01'), False), + (tfs, TFloatInst('1@2019-09-01'), True), + (tfss, TFloatInst('1@2019-10-01'), False), + (tfss, TFloatInst('1@2019-09-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_after(self, temporal, argument, expected): + assert temporal.is_over_or_after(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('1@2019-09-01'), False), + (tfi, TFloatInst('3@2019-10-01'), True), + (tfds, TFloatInst('1@2019-09-01'), False), + (tfds, TFloatInst('3@2019-10-01'), True), + (tfs, TFloatInst('1@2019-09-01'), False), + (tfs, TFloatInst('3@2019-10-01'), True), + (tfss, TFloatInst('1@2019-09-01'), False), + (tfss, TFloatInst('3@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_left_right(self, temporal, argument, expected): + assert temporal.is_left(argument) == expected + assert argument.is_right(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('0@2019-09-01'), False), + (tfi, TFloatInst('3@2019-10-01'), True), + (tfds, TFloatInst('1@2019-09-01'), False), + (tfds, TFloatInst('3@2019-10-01'), True), + (tfs, TFloatInst('1@2019-09-01'), False), + (tfs, TFloatInst('3@2019-10-01'), True), + (tfss, TFloatInst('1@2019-09-01'), False), + (tfss, TFloatInst('3@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_left(self, temporal, argument, expected): + assert temporal.is_over_or_left(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('0@2019-09-01'), False), + (tfi, TFloatInst('3@2019-10-01'), True), + (tfds, TFloatInst('0@2019-09-01'), False), + (tfds, TFloatInst('3@2019-10-01'), True), + (tfs, TFloatInst('0@2019-09-01'), False), + (tfs, TFloatInst('3@2019-10-01'), True), + (tfss, TFloatInst('0@2019-09-01'), False), + (tfss, TFloatInst('3@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_right(self, temporal, argument, expected): + assert argument.is_over_or_right(temporal) == expected + + +class TestTFloatSimilarityFunctions(TestTFloat): + tfi = TFloatInst('1@2019-09-01') + tfds = TFloatSeq('{1@2019-09-01, 2@2019-09-02}') + tfs = TFloatSeq('[1@2019-09-01, 2@2019-09-02]') + tfss = TFloatSeqSet('{[1@2019-09-01, 2@2019-09-02], [1@2019-09-03, 1@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('3@2019-09-02'), 2.0), + (tfds, TFloatInst('3@2019-09-03'), 2.0), + (tfs, TFloatInst('3@2019-09-03'), 2.0), + (tfss, TFloatInst('3@2019-09-08'), 2.0), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_frechet_distance(self, temporal, argument, expected): + assert temporal.frechet_distance(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('3@2019-09-02'), 2.0), + (tfds, TFloatInst('3@2019-09-03'), 3.0), + (tfs, TFloatInst('3@2019-09-03'), 3.0), + (tfss, TFloatInst('3@2019-09-08'), 7.0), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_dyntimewarp_distance(self, temporal, argument, expected): + assert temporal.dyntimewarp_distance(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, TFloatInst('3@2019-09-02'), 2.0), + (tfds, TFloatInst('3@2019-09-03'), 2.0), + (tfs, TFloatInst('3@2019-09-03'), 2.0), + (tfss, TFloatInst('3@2019-09-08'), 2.0), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_hausdorff_distance(self, temporal, argument, expected): + assert temporal.hausdorff_distance(argument) == expected + + +class TestTFloatSplitOperations(TestTFloat): + tfi = TFloatInst('1@2019-09-01') + tfds = TFloatSeq('{1@2019-09-01, 2@2019-09-02}') + tfs = TFloatSeq('[1@2019-09-01, 2@2019-09-02]') + tfss = TFloatSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, [TFloatInst('1@2019-09-01')]), + (tfds, [TFloatSeq('{1@2019-09-01}'),TFloatSeq('{2@2019-09-02}')]), + (tfs, [TFloatSeq('[1@2019-09-01, 2@2019-09-02)'),TFloatSeq('[2@2019-09-02]')]), + (tfss, [TFloatSeqSet('{[1@2019-09-01, 2@2019-09-02),[1@2019-09-03, 1@2019-09-05]}'),TFloatSeq('[2@2019-09-02]')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_split(self, temporal, expected): + assert temporal.value_split(2) == expected + + # The PyMEOS function uses as default origin the initial timestamp of the + # temporal value while in MEOS the default origin is Monday Janury 3, 2000 + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, [TFloatInst('1@2019-09-01')]), + (tfds, [TFloatSeq('{1@2019-09-01, 2@2019-09-02}')]), + (tfs, [TFloatSeq('[1@2019-09-01, 2@2019-09-02]')]), + (tfss, [TFloatSeq('[1@2019-09-01,2@2019-09-02]'), + TFloatSeq('[1@2019-09-03, 1@2019-09-05]')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_time_split(self, temporal, expected): + assert temporal.time_split(timedelta(days=2)) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, [TFloatInst('1@2019-09-01')]), + (tfds, [TFloatSeq('{1@2019-09-01}'), + TFloatSeq('{2@2019-09-02}')]), + (tfs, [TFloatSeq('[1@2019-09-01, 1.5@2019-09-01 12:00:00+00)'), + TFloatSeq('[1.5@2019-09-01 12:00:00+00, 2@2019-09-02]')]), + (tfss, [TFloatSeq('[1@2019-09-01,2@2019-09-02]'), + TFloatSeq('[1@2019-09-03, 1@2019-09-05]')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'], + ) + def test_time_split_n(self, temporal, expected): + assert temporal.time_split_n(2) == expected + + +class TestTFloatComparisons(TestTFloat): + tf = TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]') + other = TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + + def test_eq(self): + _ = self.tf == self.other + + def test_ne(self): + _ = self.tf != self.other + + def test_lt(self): + _ = self.tf < self.other + + def test_le(self): + _ = self.tf <= self.other + + def test_gt(self): + _ = self.tf > self.other + + def test_ge(self): + _ = self.tf >= self.other + + +class TestTFloatEverAlwaysComparisons(TestTFloat): + tfi = TFloatInst('1.5@2019-09-01') + tfds = TFloatSeq('{1.5@2019-09-01,2.5@2019-09-02}') + tfs = TFloatSeq('[1.5@2019-09-01,2.5@2019-09-02]') + tfss = TFloatSeqSet('{[1.5@2019-09-01,2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, 1.5, True), + (tfi, 2.5, False), + (tfds, 1.5, False), + (tfds, 2.5, False), + (tfs, 1.5, False), + (tfs, 2.5, False), + (tfss, 1.5, False), + (tfss, 2.5, False), + ], + ids=['Instant 1.5', 'Instant 2.5', 'Discrete Sequence 1.5', 'Discrete Sequence 2.5', + 'Sequence 1.5', 'Sequence 2.5', 'SequenceSet 1.5', 'SequenceSet 2.5'] + ) + def test_always_equal_ever_not_equal(self, temporal, argument, expected): + assert temporal.always_equal(argument) == expected + assert temporal.never_not_equal(argument) == expected + assert temporal.ever_not_equal(argument) == not_(expected) + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, 1.5, True), + (tfi, 2.5, False), + (tfds, 1.5, True), + (tfds, 2.5, True), + (tfs, 1.5, True), + (tfs, 2.5, True), + (tfss, 1.5, True), + (tfss, 2.5, True) + ], + ids=['Instant 1.5', 'Instant 2.5', 'Discrete Sequence 1.5', 'Discrete Sequence 2.5', + 'Sequence 1.5', 'Sequence 2.5', 'SequenceSet 1.5', 'SequenceSet 2.5'] + ) + def test_ever_equal_always_not_equal(self, temporal, argument, expected): + assert temporal.ever_equal(argument) == expected + assert temporal.always_not_equal(argument) == not_(expected) + assert temporal.never_equal(argument) == not_(expected) + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, 1.5, False), + (tfi, 2.5, True), + (tfds, 1.5, False), + (tfds, 2.5, False), + (tfs, 1.5, False), + (tfs, 2.5, False), + (tfss, 1.5, False), + (tfss, 2.5, False), + ], + ids=['Instant 1.5', 'Instant 2.5', 'Discrete Sequence 1.5', 'Discrete Sequence 2.5', + 'Sequence 1.5', 'Sequence 2.5', 'SequenceSet 1.5', 'SequenceSet 2.5'] + ) + def test_always_less_ever_greater_or_equal(self, temporal, argument, expected): + assert temporal.always_less(argument) == expected + assert temporal.never_greater_or_equal(argument) == expected + assert temporal.ever_greater_or_equal(argument) == not_(expected) + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, 1.5, False), + (tfi, 2.5, True), + (tfds, 1.5, False), + (tfds, 2.5, True), + (tfs, 1.5, False), + (tfs, 2.5, True), + (tfss, 1.5, False), + (tfss, 2.5, True), + ], + ids=['Instant 1.5', 'Instant 2.5', 'Discrete Sequence 1.5', 'Discrete Sequence 2.5', + 'Sequence 1.5', 'Sequence 2.5', 'SequenceSet 1.5', 'SequenceSet 2.5'] + ) + def test_ever_less_always_greater_or_equal(self, temporal, argument, expected): + assert temporal.ever_less(argument) == expected + assert temporal.always_greater_or_equal(argument) == not_(expected) + assert temporal.never_less(argument) == not_(expected) + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, 1.5, True), + (tfi, 2.5, True), + (tfds, 1.5, False), + (tfds, 2.5, True), + (tfs, 1.5, False), + (tfs, 2.5, True), + (tfss, 1.5, False), + (tfss, 2.5, True), + ], + ids=['Instant 1.5', 'Instant 2.5', 'Discrete Sequence 1.5', 'Discrete Sequence 2.5', + 'Sequence 1.5', 'Sequence 2.5', 'SequenceSet 1.5', 'SequenceSet 2.5'] + ) + def test_always_less_or_equal_ever_greater(self, temporal, argument, expected): + assert temporal.always_less_or_equal(argument) == expected + assert temporal.never_greater(argument) == expected + assert temporal.ever_greater(argument) == not_(expected) + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tfi, 1.5, True), + (tfi, 2.5, True), + (tfds, 1.5, True), + (tfds, 2.5, True), + (tfs, 1.5, True), + (tfs, 2.5, True), + (tfss, 1.5, True), + (tfss, 2.5, True), + ], + ids=['Instant 1.5', 'Instant 2.5', 'Discrete Sequence 1.5', 'Discrete Sequence 2.5', + 'Sequence 1.5', 'Sequence 2.5', 'SequenceSet 1.5', 'SequenceSet 2.5'] + ) + def test_ever_less_or_equal_always_greater(self, temporal, argument, expected): + assert temporal.ever_less_or_equal(argument) == expected + assert temporal.always_greater(argument) == not_(expected) + assert temporal.never_less_or_equal(argument) == not_(expected) + + +class TestTFloatTemporalComparisons(TestTFloat): + tfi = TFloatInst('1.5@2019-09-01') + tfds = TFloatSeq('{1.5@2019-09-01, 2.5@2019-09-02}') + tfs = TFloatSeq('[1.5@2019-09-01, 2.5@2019-09-02]') + tfss = TFloatSeqSet('{[1.5@2019-09-01, 2.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}') + tintarg = TIntSeq('[2@2019-09-01, 1@2019-09-02, 1@2019-09-03]') + tfloatarg = TFloatSeq('[2.5@2019-09-01, 1.5@2019-09-02, 1.5@2019-09-03]') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TBoolInst('False@2019-09-01')), + (tfds, TBoolSeq('{False@2019-09-01, False@2019-09-02}')), + (tfs, TBoolSeqSet('{[False@2019-09-01, True@2019-09-01 12:00:00+00],' + '(False@2019-09-01 12:00:00+00, False@2019-09-02]}')), + (tfss, TBoolSeqSet('{[False@2019-09-01, True@2019-09-01 12:00:00+00],' + '(False@2019-09-01 12:00:00+00, False@2019-09-02],[True@2019-09-03]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_equal_temporal(self, temporal, expected): + assert temporal.temporal_equal(self.tfloatarg) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TBoolInst('False@2019-09-01')), + (tfds, TBoolSeq('{False@2019-09-01, False@2019-09-02}')), + (tfs, TBoolSeq('[True@2019-09-01, False@2019-09-02]')), + (tfss, TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_equal_int(self, temporal, expected): + assert temporal.temporal_equal(1) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TBoolInst('False@2019-09-01')), + (tfds, TBoolSeq('{False@2019-09-01, False@2019-09-02}')), + (tfs, TBoolSeq('{[False@2019-09-01, True@2019-09-01 12:00:00], (False@2019-09-01 12:00:00, False@2019-09-02]}')), + (tfss, TBoolSeqSet('{[False@2019-09-01, True@2019-09-01 12:00:00],' + '(False@2019-09-01 12:00:00, False@2019-09-02],[False@2019-09-03, False@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_equal_int(self, temporal, expected): + assert temporal.temporal_equal(2) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TBoolInst('True@2019-09-01')), + (tfds, TBoolSeq('{True@2019-09-01, False@2019-09-02}')), + (tfs, TBoolSeq('{[True@2019-09-01], (False@2019-09-01, False@2019-09-02]}')), + (tfss, TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_equal_float(self, temporal, expected): + assert temporal.temporal_equal(1.5) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TBoolInst('False@2019-09-01')), + (tfds, TBoolSeq('{False@2019-09-01, True@2019-09-02}')), + (tfs, TBoolSeq('[False@2019-09-01, True@2019-09-02]')), + (tfss, TBoolSeqSet('{[False@2019-09-01, True@2019-09-02],[False@2019-09-03, False@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_equal_float(self, temporal, expected): + assert temporal.temporal_equal(2.5) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TBoolInst('True@2019-09-01')), + (tfds, TBoolSeq('{True@2019-09-01, True@2019-09-02}')), + (tfs, TBoolSeqSet('{[True@2019-09-01, False@2019-09-01 12:00:00+00],' + '(True@2019-09-01 12:00:00+00, True@2019-09-02]}')), + (tfss, TBoolSeqSet('{[True@2019-09-01, False@2019-09-01 12:00:00+00],' + '(True@2019-09-01 12:00:00+00, True@2019-09-02],[False@2019-09-03]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_not_equal_temporal(self, temporal, expected): + assert temporal.temporal_not_equal(self.tfloatarg) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TBoolInst('False@2019-09-01')), + (tfds, TBoolSeq('{False@2019-09-01, True@2019-09-02}')), + (tfs, TBoolSeq('[False@2019-09-01, True@2019-09-02]')), + (tfss, TBoolSeqSet('{[False@2019-09-01, True@2019-09-02],[False@2019-09-03, False@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_not_equal_int(self, temporal, expected): + assert temporal.temporal_not_equal(1) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tfi, TBoolInst('True@2019-09-01')), + (tfds, TBoolSeq('{True@2019-09-01, True@2019-09-02}')), + (tfs, TBoolSeqSet('{[True@2019-09-01, False@2019-09-01 12:00:00],' + '(True@2019-09-01 12:00:00, True@2019-09-02]}')), + (tfss, TBoolSeqSet('{[True@2019-09-01, False@2019-09-01 12:00:00],' + '(True@2019-09-01 12:00:00, True@2019-09-02], [True@2019-09-03, True@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_not_equal_int(self, temporal, expected): + assert temporal.temporal_not_equal(2) == expected + + diff --git a/pymeos/tests/main/tgeogpoint_test.py b/pymeos/tests/main/tgeogpoint_test.py new file mode 100644 index 00000000..3702582d --- /dev/null +++ b/pymeos/tests/main/tgeogpoint_test.py @@ -0,0 +1,1254 @@ +from copy import copy +from operator import not_ +from datetime import datetime, timezone, timedelta + +import pytest +from shapely import Point +import shapely.geometry + +from pymeos import TBool, TBoolInst, TBoolSeq, TBoolSeqSet, TFloat, TFloatInst, TFloatSeq, TFloatSeqSet, TGeogPoint, \ + TGeogPointInst, TGeogPointSeq, TGeogPointSeqSet, STBox, TInterpolation, TimestampSet, Period, PeriodSet +from tests.conftest import TestPyMEOS + + +class TestTGeogPoint(TestPyMEOS): + pass + + +class TestTGeogPointConstructors(TestTGeogPoint): + tpi = TGeogPointInst('Point(1 1)@2019-09-01') + tpds = TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + tpi3d = TGeogPointInst('Point(1 1 1)@2019-09-01') + tpds3d = TGeogPointSeq('{Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02}') + tps3d = TGeogPointSeq('[Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02]') + tpss3d = TGeogPointSeqSet('{[Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02],' + '[Point(1 1 1)@2019-09-03, Point(1 1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'source, type, interpolation', + [ + (TFloatInst('1.5@2019-09-01'), TGeogPointInst, TInterpolation.NONE), + (TFloatSeq('{1.5@2019-09-01, 0.5@2019-09-02}'), TGeogPointSeq, TInterpolation.DISCRETE), + (TFloatSeq('[1.5@2019-09-01, 0.5@2019-09-02]'), TGeogPointSeq, TInterpolation.LINEAR), + (TFloatSeqSet('{[1.5@2019-09-01, 0.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}'), + TGeogPointSeqSet, TInterpolation.LINEAR) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_from_base_constructor(self, source, type, interpolation): + tp = TGeogPoint.from_base_temporal(Point(1,1), source) + assert isinstance(tp, type) + assert tp.interpolation() == interpolation + + @pytest.mark.parametrize( + 'source, type, interpolation', + [ + (datetime(2000, 1, 1), TGeogPointInst, TInterpolation.NONE), + (TimestampSet('{2019-09-01, 2019-09-02}'), TGeogPointSeq, TInterpolation.DISCRETE), + (Period('[2019-09-01, 2019-09-02]'), TGeogPointSeq, TInterpolation.LINEAR), + (PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}'), TGeogPointSeqSet, TInterpolation.LINEAR) + ], + ids=['Instant', 'Sequence', 'Discrete Sequence', 'SequenceSet'] + ) + def test_from_base_time_constructor(self, source, type, interpolation): + tp = TGeogPoint.from_base_time(Point(1,1), source, interpolation) + assert isinstance(tp, type) + assert tp.interpolation() == interpolation + + @pytest.mark.parametrize( + 'source, type, interpolation, expected', + [ + ('Point(1 1)@2019-09-01', TGeogPointInst, TInterpolation.NONE, 'POINT(1 1)@2019-09-01 00:00:00+00'), + ('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}', TGeogPointSeq, TInterpolation.DISCRETE, + '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00}'), + ('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]', TGeogPointSeq, TInterpolation.LINEAR, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00]'), + ('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}', TGeogPointSeqSet, + TInterpolation.LINEAR, '{[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00], ' + '[POINT(1 1)@2019-09-03 00:00:00+00, POINT(1 1)@2019-09-05 00:00:00+00]}'), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_string_constructor(self, source, type, interpolation, expected): + tp = type(source) + assert isinstance(tp, type) + assert tp.interpolation() == interpolation + assert str(tp) == expected + + # @pytest.mark.parametrize( + # 'source, type, expected', + # [ + # ('[Point(1 1)@2019-09-01, Point(1.25 1.25)@2019-09-02, Point(1.5 1.5)@2019-09-03, Point(2 2)@2019-09-05]', TGeogPointSeq, + # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-05 00:00:00+00]'), + # ('{[Point(1 1)@2019-09-01, Point(1.25 1.25)@2019-09-02, Point(1.5 1.5)@2019-09-03, Point(2 2)@2019-09-05],' + # '[Point(1 1)@2019-09-07, Point(1 1)@2019-09-08, Point(1 1)@2019-09-09]}', TGeogPointSeqSet, + # '{[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-05 00:00:00+00], ' + # '[POINT(1 1)@2019-09-07 00:00:00+00, POINT(1 1)@2019-09-09 00:00:00+00]}'), + # ], + # ids=['Sequence', 'SequenceSet'] + # ) + # def test_string_constructor_normalization(self, source, type, expected): + # tp = type(source, normalize=True) + # assert isinstance(tp, type) + # assert str(tp) == expected + + @pytest.mark.parametrize( + 'value, timestamp', + [ + (Point(1,1), datetime(2019, 9, 1, tzinfo=timezone.utc)), + ('POINT(1 1)', datetime(2019, 9, 1, tzinfo=timezone.utc)), + (Point(1,1), '2019-09-01'), + ('POINT(1 1)', '2019-09-01'), + ], + ids=['point-datetime', 'string-datetime', 'point-string', 'string-string'] + ) + def test_value_timestamp_instant_constructor(self, value, timestamp): + tpi = TGeogPointInst(point=value, timestamp=timestamp) + assert str(tpi) == 'POINT(1 1)@2019-09-01 00:00:00+00' + + # @pytest.mark.parametrize( + # 'list, interpolation, normalize, expected', + # [ + # (['Point(1 1)@2019-09-01', 'Point(2 2)@2019-09-03'], TInterpolation.DISCRETE, False, + # '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00}'), + # (['Point(1 1)@2019-09-01', 'Point(2 2)@2019-09-03'], TInterpolation.LINEAR, False, + # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + # ([TGeogPointInst('Point(1 1)@2019-09-01'), TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.DISCRETE, False, + # '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00}'), + # ([TGeogPointInst('Point(1 1)@2019-09-01'), TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.LINEAR, False, + # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + # (['Point(1 1)@2019-09-01', TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.DISCRETE, False, + # '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00}'), + # (['Point(1 1)@2019-09-01', TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.LINEAR, False, + # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + + # (['Point(1 1)@2019-09-01', 'Point(1.5 1.5)@2019-09-02', 'Point(2 2)@2019-09-03'], TInterpolation.LINEAR, True, + # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + # ([TGeogPointInst('Point(1 1)@2019-09-01'), TGeogPointInst('Point(1.5 1.5)@2019-09-02'), TGeogPointInst('Point(2 2)@2019-09-03')], + # TInterpolation.LINEAR, True, + # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + # (['Point(1 1)@2019-09-01', 'Point(1.5 1.5)@2019-09-02', TGeogPointInst('Point(2 2)@2019-09-03')], TInterpolation.LINEAR, True, + # '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + # ], + # ids=['String Discrete', 'String Linear', 'TGeogPointInst Discrete', 'TGeogPointInst Linear', 'Mixed Discrete', + # 'Mixed Linear', 'String Linear Normalized', 'TGeogPointInst Linear Normalized', + # 'Mixed Linear Normalized'] + # ) + # def test_instant_list_sequence_constructor(self, list, interpolation, normalize, expected): + # tps = TGeogPointSeq(instant_list=list, interpolation=interpolation, normalize=normalize, upper_inc=True) + # assert str(tps) == expected + # assert tps.interpolation() == interpolation + + # tps2 = TGeogPointSeq.from_instants(list, interpolation=interpolation, normalize=normalize, upper_inc=True) + # assert str(tps2) == expected + # assert tps2.interpolation() == interpolation + + @pytest.mark.parametrize( + 'temporal', + [tpi, tpds, tps, tpss, tpi3d, tpds3d, tps3d, tpss3d], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet', + 'Instant 3D', 'Discrete Sequence 3D', 'Sequence 3D', 'SequenceSet 3D'] + ) + def test_from_as_constructor(self, temporal): + assert temporal == temporal.from_wkb(temporal.as_wkb()) + assert temporal == temporal.from_hexwkb(temporal.as_hexwkb()) + assert temporal == temporal.from_mfjson(temporal.as_mfjson()) + + @pytest.mark.parametrize( + 'temporal', + [tpi, tpds, tps, tpss, tpi3d, tpds3d, tps3d, tpss3d], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet', + 'Instant 3D', 'Discrete Sequence 3D', 'Sequence 3D', 'SequenceSet 3D'] + ) + def test_copy_constructor(self, temporal): + other = copy(temporal) + assert temporal == other + assert temporal is not other + + +class TestTGeogPointOutputs(TestTGeogPoint): + tpi = TGeogPointInst('Point(1 1)@2019-09-01') + tpds = TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 'POINT(1 1)@2019-09-01 00:00:00+00'), + (tpds, '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00}'), + (tps, '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00]'), + (tpss, '{[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00], ' + '[POINT(1 1)@2019-09-03 00:00:00+00, POINT(1 1)@2019-09-05 00:00:00+00]}') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_str(self, temporal, expected): + assert str(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 'TGeogPointInst(POINT(1 1)@2019-09-01 00:00:00+00)'), + (tpds, 'TGeogPointSeq({POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00})'), + (tps, 'TGeogPointSeq([POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00])'), + (tpss, 'TGeogPointSeqSet({[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00], ' + '[POINT(1 1)@2019-09-03 00:00:00+00, POINT(1 1)@2019-09-05 00:00:00+00]})') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_repr(self, temporal, expected): + assert repr(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 'POINT(1 1)@2019-09-01 00:00:00+00'), + (tpds, '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00}'), + (tps, '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00]'), + (tpss, '{[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00], ' + '[POINT(1 1)@2019-09-03 00:00:00+00, POINT(1 1)@2019-09-05 00:00:00+00]}') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_wkt(self, temporal, expected): + assert temporal.as_wkt() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, '01290061E6100000000000000000F03F000000000000F03F00A01E4E71340200'), + (tpds, '01290066E61000000200000003000000000000F03F000000000000F03F00A01E4E71340200' + '000000000000004000000000000000400000F66B85340200'), + (tps, '0129006EE61000000200000003000000000000F03F000000000000F03F00A01E4E71340200' + '000000000000004000000000000000400000F66B85340200'), + (tpss, '0129006FE6100000020000000200000003000000000000F03F000000000000F03F00A01E4E71340200' + '000000000000004000000000000000400000F66B853402000200000003000000000000F03F000000000' + '000F03F0060CD8999340200000000000000F03F000000000000F03F00207CC5C1340200') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_hexwkb(self, temporal, expected): + assert temporal.as_hexwkb() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, '{\n' + ' "type": "MovingGeogPoint",\n' + ' "bbox": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ]\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-01T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "coordinates": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ]\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00"\n' + ' ],\n' + ' "interpolation": "None"\n' + ' }'), + (tpds, '{\n' + ' "type": "MovingGeogPoint",\n' + ' "bbox": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 2,\n' + ' 2\n' + ' ]\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-02T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "coordinates": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 2,\n' + ' 2\n' + ' ]\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true,\n' + ' "interpolation": "Discrete"\n' + ' }'), + (tps, '{\n' + ' "type": "MovingGeogPoint",\n' + ' "bbox": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 2,\n' + ' 2\n' + ' ]\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-02T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "coordinates": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 2,\n' + ' 2\n' + ' ]\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true,\n' + ' "interpolation": "Linear"\n' + ' }'), + (tpss, '{\n' + ' "type": "MovingGeogPoint",\n' + ' "bbox": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 2,\n' + ' 2\n' + ' ]\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-05T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "sequences": [\n' + ' {\n' + ' "coordinates": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 2,\n' + ' 2\n' + ' ]\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' {\n' + ' "coordinates": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ]\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-03T00:00:00+00",\n' + ' "2019-09-05T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' }\n' + ' ],\n' + ' "interpolation": "Linear"\n' + ' }') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_mfjson(self, temporal, expected): + assert temporal.as_mfjson() == expected + + +class TestTGeogPointAccessors(TestTGeogPoint): + tpi = TGeogPointInst('Point(1 1)@2019-09-01') + tpds = TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TInterpolation.NONE), + (tpds, TInterpolation.DISCRETE), + (tps, TInterpolation.LINEAR), + (tpss, TInterpolation.LINEAR) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_interpolation(self, temporal, expected): + assert temporal.interpolation() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, {Point(1,1)}), + (tpds, {Point(1,1), Point(2,2)}), + (tps, {Point(1,1), Point(2,2)}), + (tpss, {Point(1,1), Point(2,2)}) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_set(self, temporal, expected): + assert temporal.value_set() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, [Point(1,1)]), + (tpds, [Point(1,1), Point(2,2)]), + (tps, [Point(1,1), Point(2,2)]), + (tpss, [Point(1,1), Point(2,2)]) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_values(self, temporal, expected): + assert temporal.values() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, Point(1,1)), + (tpds, Point(1,1)), + (tps, Point(1,1)), + (tpss, Point(1,1)) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_value(self, temporal, expected): + assert temporal.start_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, Point(1,1)), + (tpds, Point(2,2)), + (tps, Point(2,2)), + (tpss, Point(1,1)) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_value(self, temporal, expected): + assert temporal.end_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, Point(1,1)), + (tpds, Point(1,1)), + (tps, Point(1,1)), + (tpss, Point(1,1)) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_at_timestamp(self, temporal, expected): + assert temporal.value_at_timestamp(datetime(2019, 9, 1)) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, PeriodSet('{[2019-09-01, 2019-09-01]}')), + (tpds, PeriodSet('{[2019-09-01, 2019-09-01], [2019-09-02, 2019-09-02]}')), + (tps, PeriodSet('{[2019-09-01, 2019-09-02]}')), + (tpss, PeriodSet('{[2019-09-01, 2019-09-02], [2019-09-03, 2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_time(self, temporal, expected): + assert temporal.time() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, timedelta()), + (tpds, timedelta()), + (tps, timedelta(days=1)), + (tpss, timedelta(days=3)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_duration(self, temporal, expected): + assert temporal.duration() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, timedelta()), + (tpds, timedelta(days=1)), + (tps, timedelta(days=1)), + (tpss, timedelta(days=4)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_duration_ignoring_gaps(self, temporal, expected): + assert temporal.duration(ignore_gaps=True) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, Period('[2019-09-01, 2019-09-01]')), + (tpds, Period('[2019-09-01, 2019-09-02]')), + (tps, Period('[2019-09-01, 2019-09-02]')), + (tpss, Period('[2019-09-01, 2019-09-05]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_period(self, temporal, expected): + assert temporal.period() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, Period('[2019-09-01, 2019-09-01]')), + (tpds, Period('[2019-09-01, 2019-09-02]')), + (tps, Period('[2019-09-01, 2019-09-02]')), + (tpss, Period('[2019-09-01, 2019-09-05]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timespan(self, temporal, expected): + assert temporal.timespan() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 1), + (tpds, 2), + (tps, 2), + (tpss, 4), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_num_instants(self, temporal, expected): + assert temporal.num_instants() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, tpi), + (tpds, tpi), + (tps, tpi), + (tpss, tpi), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_instant(self, temporal, expected): + assert temporal.start_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, tpi), + (tpds, TGeogPointInst('Point(2 2)@2019-09-02')), + (tps, TGeogPointInst('Point(2 2)@2019-09-02')), + (tpss, TGeogPointInst('Point(1 1)@2019-09-05')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_instant(self, temporal, expected): + assert temporal.end_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, tpi), + (tpds, TGeogPointInst('Point(2 2)@2019-09-02')), + (tps, TGeogPointInst('Point(2 2)@2019-09-02')), + (tpss, TGeogPointInst('Point(2 2)@2019-09-02')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_max_instant(self, temporal, expected): + assert temporal.max_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, tpi), + (tpds, tpi), + (tps, tpi), + (tpss, tpi), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_min_instant(self, temporal, expected): + assert temporal.min_instant() == expected + + @pytest.mark.parametrize( + 'temporal, n, expected', + [ + (tpi, 0, tpi), + (tpds, 1, TGeogPointInst('Point(2 2)@2019-09-02')), + (tps, 1, TGeogPointInst('Point(2 2)@2019-09-02')), + (tpss, 2, TGeogPointInst('Point(1 1)@2019-09-03')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_instant_n(self, temporal, n, expected): + assert temporal.instant_n(n) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, [tpi]), + (tpds, [tpi, TGeogPointInst('Point(2 2)@2019-09-02')]), + (tps, [tpi, TGeogPointInst('Point(2 2)@2019-09-02')]), + (tpss, [tpi, TGeogPointInst('Point(2 2)@2019-09-02'), TGeogPointInst('Point(1 1)@2019-09-03'), TGeogPointInst('Point(1 1)@2019-09-05')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_instants(self, temporal, expected): + assert temporal.instants() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 1), + (tpds, 2), + (tps, 2), + (tpss, 4), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_num_timestamps(self, temporal, expected): + assert temporal.num_timestamps() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tpds, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tps, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tpss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_timestamp(self, temporal, expected): + assert temporal.start_timestamp() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tpds, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tps, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tpss, datetime(year=2019, month=9, day=5, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_timestamp(self, temporal, expected): + assert temporal.end_timestamp() == expected + + @pytest.mark.parametrize( + 'temporal, n, expected', + [ + (tpi, 0, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tpds, 1, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tps, 1, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tpss, 2, datetime(year=2019, month=9, day=3, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timestamp_n(self, temporal, n, expected): + assert temporal.timestamp_n(n) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)]), + (tpds, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)]), + (tps, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)]), + (tpss, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=3, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=5, tzinfo=timezone.utc)]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timestamps(self, temporal, expected): + assert temporal.timestamps() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpds, [TGeogPointSeq('[Point(1 1)@2019-09-01]'), TGeogPointSeq('[Point(2 2)@2019-09-02]')]), + (tps, [TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')]), + (tpss, + [TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + TGeogPointSeq('[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]')]), + ], + ids=['Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_segments(self, temporal, expected): + assert temporal.segments() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpds, True), + (tps, True), + ], + ids=['Discrete Sequence', 'Sequence'] + ) + def test_lower_upper_inc(self, temporal, expected): + assert temporal.lower_inc() == expected + assert temporal.upper_inc() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 1181779687), + (tpds, 1545137628), + (tps, 1545137628), + (tpss, 1008965061) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_hash(self, temporal, expected): + assert hash(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 4326), + (tpds, 4326), + (tps, 4326), + (tpss, 4326) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_srid(self, temporal, expected): + assert temporal.srid() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, STBox('GEODSTBOX XT(((1,1),(1,1)),[2019-09-01, 2019-09-01])')), + (tpds, STBox('GEODSTBOX XT(((1,1),(2,2)),[2019-09-01, 2019-09-02])')), + (tps, STBox('GEODSTBOX XT(((1,1),(2,2)),[2019-09-01, 2019-09-02])')), + (tpss, STBox('GEODSTBOX XT(((1,1),(2,2)),[2019-09-01, 2019-09-05])')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_bounding_box(self, temporal, expected): + assert temporal.bounding_box() == expected + + +class TestTGeogPointTransformations(TestTGeogPoint): + tpi = TGeogPointInst('Point(1 1)@2019-09-01') + tpds = TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TGeogPointInst('Point(1 1)@2019-09-01'), tpi), + (TGeogPointSeq('{Point(1 1)@2019-09-01}'), tpi), + (TGeogPointSeq('[Point(1 1)@2019-09-01]'), tpi), + (TGeogPointSeqSet('{[Point(1 1)@2019-09-01]}'), tpi), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_instant(self, temporal, expected): + temp = temporal.to_instant() + assert isinstance(temp, TGeogPointInst) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TGeogPointInst('Point(1 1)@2019-09-01'), + TGeogPointSeq('[Point(1 1)@2019-09-01]')), + (TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}'), + TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}')), + (TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), + (TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}'), + TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_sequence(self, temporal, expected): + temp = temporal.to_sequence() + assert isinstance(temp, TGeogPointSeq) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TGeogPointInst('Point(1 1)@2019-09-01'), + TGeogPointSeqSet('{[Point(1 1)@2019-09-01]}')), + (TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}'), + TGeogPointSeqSet('{[Point(1 1)@2019-09-01], [Point(2 2)@2019-09-02]}')), + (TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}'), + TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_sequenceset(self, temporal, expected): + temp = temporal.to_sequenceset() + assert isinstance(temp, TGeogPointSeqSet) + assert temp == expected + + @pytest.mark.parametrize( + 'tpoint, delta, expected', + [(tpi, timedelta(days=4), TGeogPointInst('Point(1 1)@2019-09-05')), + (tpi, timedelta(days=-4), TGeogPointInst('Point(1 1)@2019-08-28')), + (tpi, timedelta(hours=2), TGeogPointInst('Point(1 1)@2019-09-01 02:00:00')), + (tpi, timedelta(hours=-2), TGeogPointInst('Point(1 1)@2019-08-31 22:00:00')), + (tpds, timedelta(days=4), TGeogPointSeq('{Point(1 1)@2019-09-05, Point(2 2)@2019-09-06}')), + (tpds, timedelta(days=-4), TGeogPointSeq('{Point(1 1)@2019-08-28, Point(2 2)@2019-08-29}')), + (tpds, timedelta(hours=2), TGeogPointSeq('{Point(1 1)@2019-09-01 02:00:00, Point(2 2)@2019-09-02 02:00:00}')), + (tpds, timedelta(hours=-2), TGeogPointSeq('{Point(1 1)@2019-08-31 22:00:00, Point(2 2)@2019-09-01 22:00:00}')), + (tps, timedelta(days=4), TGeogPointSeq('[Point(1 1)@2019-09-05, Point(2 2)@2019-09-06]')), + (tps, timedelta(days=-4), TGeogPointSeq('[Point(1 1)@2019-08-28, Point(2 2)@2019-08-29]')), + (tps, timedelta(hours=2), TGeogPointSeq('[Point(1 1)@2019-09-01 02:00:00, Point(2 2)@2019-09-02 02:00:00]')), + (tps, timedelta(hours=-2), TGeogPointSeq('[Point(1 1)@2019-08-31 22:00:00, Point(2 2)@2019-09-01 22:00:00]')), + (tpss, timedelta(days=4), + TGeogPointSeqSet('{[Point(1 1)@2019-09-05, Point(2 2)@2019-09-06],[Point(1 1)@2019-09-07, Point(1 1)@2019-09-09]}')), + (tpss, timedelta(days=-4), + TGeogPointSeqSet('{[Point(1 1)@2019-08-28, Point(2 2)@2019-08-29],[Point(1 1)@2019-08-30, Point(1 1)@2019-09-01]}')), + (tpss, timedelta(hours=2), + TGeogPointSeqSet('{[Point(1 1)@2019-09-01 02:00:00, Point(2 2)@2019-09-02 02:00:00],' + '[Point(1 1)@2019-09-03 02:00:00, Point(1 1)@2019-09-05 02:00:00]}')), + (tpss, timedelta(hours=-2), + TGeogPointSeqSet('{[Point(1 1)@2019-08-31 22:00:00, Point(2 2)@2019-09-01 22:00:00],' + '[Point(1 1)@2019-09-02 22:00:00, Point(1 1)@2019-09-04 22:00:00]}')), + ], + ids=['Instant posi(tpve days', 'Instant nega(tpve days', + 'Instant posi(tpve hours', 'Instant nega(tpve hours', + 'Discrete Sequence posi(tpve days', 'Discrete Sequence nega(tpve days', + 'Discrete Sequence posi(tpve hours', 'Discrete Sequence nega(tpve hours', + 'Sequence posi(tpve days', 'Sequence nega(tpve days', + 'Sequence posi(tpve hours', 'Sequence nega(tpve hours', + 'Sequence Set posi(tpve days', 'Sequence Set nega(tpve days', + 'Sequence Set posi(tpve hours', 'Sequence Set nega(tpve hours'] + ) + def test_shift(self, tpoint, delta, expected): + assert tpoint.shift(delta) == expected + + @pytest.mark.parametrize( + 'tpoint, delta, expected', + [(tpi, timedelta(days=4), TGeogPointInst('Point(1 1)@2019-09-01')), + (tpi, timedelta(hours=2), TGeogPointInst('Point(1 1)@2019-09-01')), + (tpds, timedelta(days=4), TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-05}')), + (tpds, timedelta(hours=2), TGeogPointSeq('{Point(1 1)@2019-09-01 00:00:00, Point(2 2)@2019-09-01 02:00:00}')), + (tps, timedelta(days=4), TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-05]')), + (tps, timedelta(hours=2), TGeogPointSeq('[Point(1 1)@2019-09-01 00:00:00, Point(2 2)@2019-09-01 02:00:00]')), + (tpss, timedelta(days=4), + TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + (tpss, timedelta(hours=2), + TGeogPointSeqSet('{[Point(1 1)@2019-09-01 00:00:00, Point(2 2)@2019-09-01 00:30:00],' + '[Point(1 1)@2019-09-01 01:00:00, Point(1 1)@2019-09-01 02:00:00]}')), + ], + ids=['Instant posi(tpve days', 'Instant posi(tpve hours', + 'Discrete Sequence posi(tpve days', 'Discrete Sequence posi(tpve hours', + 'Sequence posi(tpve days', 'Sequence posi(tpve hours', + 'Sequence Set posi(tpve days', 'Sequence Set posi(tpve hours'] + ) + def test_scale(self, tpoint, delta, expected): + assert tpoint.tscale(delta) == expected + + def test_shift_tscale(self): + assert self.tpss.shift_tscale(timedelta(days=4), timedelta(hours=2)) == \ + TGeogPointSeqSet('{[Point(1 1)@2019-09-05 00:00:00, Point(2 2)@2019-09-05 00:30:00],' + '[Point(1 1)@2019-09-05 01:00:00, Point(1 1)@2019-09-05 02:00:00]}') + + +class TestTGeogPointModifications(TestTGeogPoint): + tpi = TGeogPointInst('Point(1 1)@2019-09-01') + tpds = TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, sequence, expected', + [ + (tpi, TGeogPointSeq('{Point(1 1)@2019-09-03}'), TGeogPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-03}')), + (tpds, TGeogPointSeq('{Point(1 1)@2019-09-03}'), TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02, Point(1 1)@2019-09-03}')), + (tps, TGeogPointSeq('[Point(1 1)@2019-09-03]'), TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02, Point(1 1)@2019-09-03]}')), + (tpss, TGeogPointSeq('[Point(1 1)@2019-09-06]'), + TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05],[Point(1 1)@2019-09-06]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_insert(self, temporal, sequence, expected): + assert temporal.insert(sequence) == expected + + @pytest.mark.parametrize( + 'temporal, instant, expected', + [ + (tpi, TGeogPointInst('Point(2 2)@2019-09-01'), TGeogPointInst('Point(2 2)@2019-09-01')), + (tpds, TGeogPointInst('Point(2 2)@2019-09-01'), TGeogPointSeq('{Point(2 2)@2019-09-01, Point(2 2)@2019-09-02}')), + (tps, TGeogPointInst('Point(2 2)@2019-09-01'), + TGeogPointSeqSet('{[Point(2 2)@2019-09-01], (Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tpss, TGeogPointInst('Point(2 2)@2019-09-01'), + TGeogPointSeqSet('{[Point(2 2)@2019-09-01], (Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_update(self, temporal, instant, expected): + assert temporal.update(instant) == expected + + @pytest.mark.parametrize( + 'temporal, time, expected', + [ + (tpi, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), None), + (tpi, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), tpi), + (tpds, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), TGeogPointSeq('{Point(2 2)@2019-09-02}')), + (tps, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + TGeogPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tpss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + TGeogPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + ], + ids=['Instant intersection', 'Instant disjoint', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_delete(self, temporal, time, expected): + assert temporal.delete(time) == expected + + @pytest.mark.parametrize( + 'temporal, instant, expected', + [ + (tpi, TGeogPointInst('Point(1 1)@2019-09-02'), TGeogPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-02}')), + (tpds, TGeogPointInst('Point(1 1)@2019-09-03'), TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02, Point(1 1)@2019-09-03}')), + (tps, TGeogPointInst('Point(1 1)@2019-09-03'), TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02, Point(1 1)@2019-09-03]')), + (tpss, TGeogPointInst('Point(1 1)@2019-09-06'), + TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-06]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_append_instant(self, temporal, instant, expected): + assert temporal.append_instant(instant) == expected + + @pytest.mark.parametrize( + 'temporal, sequence, expected', + [ + (tpds, TGeogPointSeq('{Point(1 1)@2019-09-03}'), TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02, Point(1 1)@2019-09-03}')), + (tps, TGeogPointSeq('[Point(1 1)@2019-09-03]'), TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02], [Point(1 1)@2019-09-03]}')), + (tpss, TGeogPointSeq('[Point(1 1)@2019-09-06]'), + TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05],[Point(1 1)@2019-09-06]}')), + ], + ids=['Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_append_sequence(self, temporal, sequence, expected): + assert temporal.append_sequence(sequence) == expected + + +class TestTGeogPointRestrictors(TestTGeogPoint): + tpi = TGeogPointInst('Point(1 1)@2019-09-01') + tpds = TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + timestamp = datetime(2019, 9, 1) + timestamp_set = TimestampSet('{2019-09-01, 2019-09-03}') + period = Period('[2019-09-01, 2019-09-02]') + period_set = PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, restrictor, expected', + [ + (tpi, timestamp, TGeogPointInst('Point(1 1)@2019-09-01')), + (tpi, timestamp_set, TGeogPointInst('Point(1 1)@2019-09-01')), + (tpi, period, TGeogPointInst('Point(1 1)@2019-09-01')), + (tpi, period_set, TGeogPointInst('Point(1 1)@2019-09-01')), + (tpi, shapely.set_srid(shapely.Point(1,1), 4326), TGeogPointInst('Point(1 1)@2019-09-01')), + (tpi, shapely.set_srid(shapely.Point(2,2), 4326), None), + + (tpds, timestamp, TGeogPointSeq('{Point(1 1)@2019-09-01}')), + (tpds, timestamp_set, TGeogPointSeq('{Point(1 1)@2019-09-01}')), + (tpds, period, TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}')), + (tpds, period_set, TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}')), + (tpds, shapely.set_srid(shapely.Point(1,1), 4326), TGeogPointSeq('{Point(1 1)@2019-09-01}')), + (tpds, shapely.set_srid(shapely.Point(2,2), 4326), TGeogPointSeq('{Point(2 2)@2019-09-02}')), + + (tps, timestamp, TGeogPointSeq('[Point(1 1)@2019-09-01]')), + (tps, timestamp_set, TGeogPointSeq('{Point(1 1)@2019-09-01}')), + (tps, period, TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), + (tps, period_set, TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), + (tps, shapely.set_srid(shapely.Point(1,1), 4326), TGeogPointSeq('[Point(1 1)@2019-09-01]')), + (tps, shapely.set_srid(shapely.Point(2,2), 4326), TGeogPointSeq('[Point(2 2)@2019-09-02]')), + + (tpss, timestamp, TGeogPointSeqSet('[Point(1 1)@2019-09-01]')), + (tpss, timestamp_set, TGeogPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-03}')), + (tpss, period, TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tpss, period_set, TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],' + '[Point(1 1)@2019-09-03,Point(1 1)@2019-09-05]}')), + (tpss, shapely.set_srid(shapely.Point(1,1), 4326), + TGeogPointSeqSet('{[Point(1 1)@2019-09-01],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + (tpss, shapely.set_srid(shapely.Point(2,2), 4326), TGeogPointSeqSet('{[Point(2 2)@2019-09-02]}')) + + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', 'Instant-PeriodSet', 'Instant-True', + 'Instant-Point(2,2)', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Discrete Sequence-True', + 'Discrete Sequence-Point(2,2)', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-True', 'Sequence-Point(2,2)', 'SequenceSet-Timestamp', + 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-True', + 'SequenceSet-Point(2,2)'] + ) + def test_at(self, temporal, restrictor, expected): + assert temporal.at(restrictor) == expected + + + @pytest.mark.parametrize( + 'temporal, restrictor, expected', + [ + (tpi, timestamp, None), + (tpi, timestamp_set, None), + (tpi, period, None), + (tpi, period_set, None), + (tpi, shapely.set_srid(shapely.Point(1,1), 4326), None), + (tpi, shapely.set_srid(shapely.Point(2,2), 4326), TGeogPointInst('Point(1 1)@2019-09-01')), + + (tpds, timestamp, TGeogPointSeq('{Point(2 2)@2019-09-02}')), + (tpds, timestamp_set, TGeogPointSeq('{Point(2 2)@2019-09-02}')), + (tpds, period, None), + (tpds, period_set, None), + (tpds, shapely.set_srid(shapely.Point(1,1), 4326), TGeogPointSeq('{Point(2 2)@2019-09-02}')), + (tpds, shapely.set_srid(shapely.Point(2,2), 4326), TGeogPointSeq('{Point(1 1)@2019-09-01}')), + + (tps, timestamp, TGeogPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tps, timestamp_set, TGeogPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tps, period, None), + (tps, period_set, None), + (tps, shapely.set_srid(shapely.Point(1,1), 4326), + TGeogPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tps, shapely.set_srid(shapely.Point(2,2), 4326), + TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02)}')), + + (tpss, timestamp, + TGeogPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + (tpss, timestamp_set, + TGeogPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],(Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + (tpss, period, TGeogPointSeqSet('{[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + (tpss, period_set, None), + (tpss, shapely.set_srid(shapely.Point(1,1), 4326), + TGeogPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tpss, shapely.set_srid(shapely.Point(2,2), 4326), + TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02),[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')) + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', 'Instant-PeriodSet', 'Instant-Point(1,1)', + 'Instant-Point(2,2)', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Discrete Sequence-Point(1,1)', + 'Discrete Sequence-Point(2,2)', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-Point(1,1)', 'Sequence-Point(2,2)', 'SequenceSet-Timestamp', + 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-Point(1,1)', + 'SequenceSet-Point(2,2)'] + ) + def test_minus(self, temporal, restrictor, expected): + assert temporal.minus(restrictor) == expected + + @pytest.mark.parametrize( + 'temporal, restrictor', + [ + (tpi, timestamp), + (tpi, timestamp_set), + (tpi, period), + (tpi, period_set), + (tpi, shapely.set_srid(shapely.Point(1,1), 4326)), + (tpi, shapely.set_srid(shapely.Point(2,2), 4326)), + + (tpds, timestamp), + (tpds, timestamp_set), + (tpds, period), + (tpds, period_set), + (tpds, shapely.set_srid(shapely.Point(1,1), 4326)), + (tpds, shapely.set_srid(shapely.Point(2,2), 4326)), + + (tps, timestamp), + (tps, timestamp_set), + (tps, period), + (tps, period_set), + (tps, shapely.set_srid(shapely.Point(1,1), 4326)), + (tps, shapely.set_srid(shapely.Point(2,2), 4326)), + + (tpss, timestamp), + (tpss, timestamp_set), + (tpss, period), + (tpss, period_set), + (tpss, shapely.set_srid(shapely.Point(1,1), 4326)), + (tpss, shapely.set_srid(shapely.Point(2,2), 4326)), + + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', 'Instant-PeriodSet', 'Instant-Point(1,1)', + 'Instant-Point(2,2)', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Discrete Sequence-Point(1,1)', + 'Discrete Sequence-Point(2,2)', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-Point(1,1)', 'Sequence-Point(2,2)', 'SequenceSet-Timestamp', + 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-Point(1,1)', + 'SequenceSet-Point(2,2)'] + ) + def test_at_minus(self, temporal, restrictor): + assert TGeogPoint.merge(temporal.at(restrictor), temporal.minus(restrictor)) == temporal + + +class TestTGeogPointComparisons(TestTGeogPoint): + tp = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + other = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + def test_eq(self): + _ = self.tp == self.other + + def test_ne(self): + _ = self.tp != self.other + + def test_lt(self): + _ = self.tp < self.other + + def test_le(self): + _ = self.tp <= self.other + + def test_gt(self): + _ = self.tp > self.other + + def test_ge(self): + _ = self.tp >= self.other + + +class TestTGeogPointEverAlwaysComparisons(TestTGeogPoint): + tpi = TGeogPointInst('Point(1 1)@2019-09-01') + tpds = TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02], [Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, shapely.set_srid(shapely.Point(1,1), 4326), True), + (tpi, shapely.set_srid(shapely.Point(2,2), 4326), False), + (tpds, shapely.set_srid(shapely.Point(1,1), 4326), False), + (tpds, shapely.set_srid(shapely.Point(2,2), 4326), False), + (tps, shapely.set_srid(shapely.Point(1,1), 4326), False), + (tps, shapely.set_srid(shapely.Point(2,2), 4326), False), + (tpss, shapely.set_srid(shapely.Point(1,1), 4326), False), + (tpss, shapely.set_srid(shapely.Point(2,2), 4326), False), + ], + ids=['Instant Point(1,1)', 'Instant Point(2,2)', 'Discrete Sequence Point(1,1)', 'Discrete Sequence Point(2,2)', + 'Sequence Point(1,1)', 'Sequence Point(2,2)', 'SequenceSet Point(1,1)', 'SequenceSet Point(2,2)'] + ) + def test_always_equal_ever_not_equal(self, temporal, argument, expected): + assert temporal.always_equal(argument) == expected + assert temporal.never_not_equal(argument) == expected + assert temporal.ever_not_equal(argument) == not_(expected) + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, shapely.set_srid(shapely.Point(1,1), 4326), True), + (tpi, shapely.set_srid(shapely.Point(2,2), 4326), False), + (tpds, shapely.set_srid(shapely.Point(1,1), 4326), True), + (tpds, shapely.set_srid(shapely.Point(2,2), 4326), True), + (tps, shapely.set_srid(shapely.Point(1,1), 4326), True), + (tps, shapely.set_srid(shapely.Point(2,2), 4326), True), + (tpss, shapely.set_srid(shapely.Point(1,1), 4326), True), + (tpss, shapely.set_srid(shapely.Point(2,2), 4326), True) + ], + ids=['Instant Point(1,1)', 'Instant Point(2,2)', 'Discrete Sequence Point(1,1)', 'Discrete Sequence Point(2,2)', + 'Sequence Point(1,1)', 'Sequence Point(2,2)', 'SequenceSet Point(1,1)', 'SequenceSet Point(2,2)'] + ) + def test_ever_equal_always_not_equal(self, temporal, argument, expected): + assert temporal.ever_equal(argument) == expected + assert temporal.always_not_equal(argument) == not_(expected) + assert temporal.never_equal(argument) == not_(expected) + + +class TestTGeogPointTemporalComparisons(TestTGeogPoint): + tpi = TGeogPointInst('Point(1 1)@2019-09-01') + tpds = TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + argument = TGeogPointSeq('[Point(2 2)@2019-09-01, Point(1 1)@2019-09-02, Point(1 1)@2019-09-03]') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TBoolInst('False@2019-09-01')), + (tpds, TBoolSeq('{False@2019-09-01, False@2019-09-02}')), + (tps, TBoolSeqSet('{[False@2019-09-01, True@2019-09-01 12:00:00+00],' + '(False@2019-09-01 12:00:00+00, False@2019-09-02]}')), + (tpss, TBoolSeqSet('{[False@2019-09-01, True@2019-09-01 12:00:00+00],' + '(False@2019-09-01 12:00:00+00, False@2019-09-02],[True@2019-09-03]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_equal_temporal(self, temporal, expected): + assert temporal.temporal_equal(self.argument) == expected + assert temporal.temporal_not_equal(self.argument) == expected.temporal_not() + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, shapely.set_srid(shapely.Point(1,1), 4326), + TBoolInst('True@2019-09-01')), + (tpds, shapely.set_srid(shapely.Point(1,1), 4326), + TBoolSeq('{True@2019-09-01, False@2019-09-02}')), + (tps, shapely.set_srid(shapely.Point(1,1), 4326), + TBoolSeqSet('{[True@2019-09-01], (False@2019-09-01, False@2019-09-02]}')), + (tpss, shapely.set_srid(shapely.Point(1,1), 4326), + TBoolSeqSet('{[True@2019-09-01], (False@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}')), + + (tpi, shapely.set_srid(shapely.Point(2,2), 4326), + TBoolInst('False@2019-09-01')), + (tpds, shapely.set_srid(shapely.Point(2,2), 4326), + TBoolSeq('{False@2019-09-01, True@2019-09-02}')), + (tps, shapely.set_srid(shapely.Point(2,2), 4326), + TBoolSeq('[False@2019-09-01, True@2019-09-02]')), + (tpss, shapely.set_srid(shapely.Point(2,2), 4326), + TBoolSeqSet('{[False@2019-09-01, True@2019-09-02],[False@2019-09-03, False@2019-09-05]}')), + ], + ids=['Instant Point(1,1)', 'Discrete Sequence Point(1,1)', 'Sequence Point(1,1)', 'SequenceSet Point(1,1)', + 'Instant Point(2,2)', 'Discrete Sequence Point(2,2)', 'Sequence Point(2,2)', 'SequenceSet Point(2,2)'] + ) + def test_temporal_equal_point(self, temporal, argument, expected): + assert temporal.temporal_equal(argument) == expected + assert temporal.temporal_not_equal(argument) == expected.temporal_not() + + diff --git a/pymeos/tests/main/tgeompoint_test.py b/pymeos/tests/main/tgeompoint_test.py new file mode 100644 index 00000000..1be059b9 --- /dev/null +++ b/pymeos/tests/main/tgeompoint_test.py @@ -0,0 +1,2197 @@ +from copy import copy +from operator import not_ +from datetime import datetime, timezone, timedelta + +import pytest +import math +from shapely import Point, LineString, Polygon, MultiPoint, GeometryCollection + +from pymeos import TBool, TBoolInst, TBoolSeq, TBoolSeqSet, \ + TFloat, TFloatInst, TFloatSeq, TFloatSeqSet, TGeomPoint, \ + TGeomPointInst, TGeomPointSeq, TGeomPointSeqSet, \ + TGeogPointInst, TGeogPointSeq, TGeogPointSeqSet, \ + TInterpolation, TimestampSet, Period, PeriodSet, STBox +from tests.conftest import TestPyMEOS + + +class TestTGeomPoint(TestPyMEOS): + pass + + +class TestTGeomPointConstructors(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + tpi3d = TGeomPointInst('Point(1 1 1)@2019-09-01') + tpds3d = TGeomPointSeq('{Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02}') + tps3d = TGeomPointSeq('[Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02]') + tpss3d = TGeomPointSeqSet('{[Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02],' + '[Point(1 1 1)@2019-09-03, Point(1 1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'source, type, interpolation', + [ + (TFloatInst('1.5@2019-09-01'), TGeomPointInst, TInterpolation.NONE), + (TFloatSeq('{1.5@2019-09-01, 0.5@2019-09-02}'), TGeomPointSeq, TInterpolation.DISCRETE), + (TFloatSeq('[1.5@2019-09-01, 0.5@2019-09-02]'), TGeomPointSeq, TInterpolation.LINEAR), + (TFloatSeqSet('{[1.5@2019-09-01, 0.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}'), + TGeomPointSeqSet, TInterpolation.LINEAR), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_from_base_constructor(self, source, type, interpolation): + tp = TGeomPoint.from_base_temporal(Point(1,1), source) + assert isinstance(tp, type) + assert tp.interpolation() == interpolation + + @pytest.mark.parametrize( + 'source, type, interpolation', + [ + (datetime(2000, 1, 1), TGeomPointInst, TInterpolation.NONE), + (TimestampSet('{2019-09-01, 2019-09-02}'), TGeomPointSeq, TInterpolation.DISCRETE), + (Period('[2019-09-01, 2019-09-02]'), TGeomPointSeq, TInterpolation.LINEAR), + (PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}'), TGeomPointSeqSet, TInterpolation.LINEAR), + ], + ids=['Instant', 'Sequence', 'Discrete Sequence', 'SequenceSet'] + ) + def test_from_base_time_constructor(self, source, type, interpolation): + tp = TGeomPoint.from_base_time(Point(1,1), source, interpolation) + assert isinstance(tp, type) + assert tp.interpolation() == interpolation + + @pytest.mark.parametrize( + 'source, type, interpolation, expected', + [ + ('Point(1 1)@2019-09-01', TGeomPointInst, TInterpolation.NONE, 'POINT(1 1)@2019-09-01 00:00:00+00'), + ('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}', TGeomPointSeq, TInterpolation.DISCRETE, + '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00}'), + ('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]', TGeomPointSeq, TInterpolation.LINEAR, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00]'), + ('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}', TGeomPointSeqSet, + TInterpolation.LINEAR, '{[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00], ' + '[POINT(1 1)@2019-09-03 00:00:00+00, POINT(1 1)@2019-09-05 00:00:00+00]}'), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_string_constructor(self, source, type, interpolation, expected): + tp = type(source) + assert isinstance(tp, type) + assert tp.interpolation() == interpolation + assert str(tp) == expected + + @pytest.mark.parametrize( + 'source, type, expected', + [ + ('[Point(1 1)@2019-09-01, Point(1.25 1.25)@2019-09-02, Point(1.5 1.5)@2019-09-03, Point(2 2)@2019-09-05]', TGeomPointSeq, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-05 00:00:00+00]'), + ('{[Point(1 1)@2019-09-01, Point(1.25 1.25)@2019-09-02, Point(1.5 1.5)@2019-09-03, Point(2 2)@2019-09-05],' + '[Point(1 1)@2019-09-07, Point(1 1)@2019-09-08, Point(1 1)@2019-09-09]}', TGeomPointSeqSet, + '{[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-05 00:00:00+00], ' + '[POINT(1 1)@2019-09-07 00:00:00+00, POINT(1 1)@2019-09-09 00:00:00+00]}'), + ], + ids=['Sequence', 'SequenceSet'] + ) + def test_string_constructor_normalization(self, source, type, expected): + tp = type(source, normalize=1) + assert isinstance(tp, type) + assert str(tp) == expected + + @pytest.mark.parametrize( + 'value, timestamp', + [ + (Point(1,1), datetime(2019, 9, 1, tzinfo=timezone.utc)), + ('POINT(1 1)', datetime(2019, 9, 1, tzinfo=timezone.utc)), + (Point(1,1), '2019-09-01'), + ('POINT(1 1)', '2019-09-01'), + ], + ids=['point-datetime', 'string-datetime', 'point-string', 'string-string'] + ) + def test_value_timestamp_instant_constructor(self, value, timestamp): + tpi = TGeomPointInst(point=value, timestamp=timestamp) + assert str(tpi) == 'POINT(1 1)@2019-09-01 00:00:00+00' + + @pytest.mark.parametrize( + 'list, interpolation, normalize, expected', + [ + (['Point(1 1)@2019-09-01', 'Point(2 2)@2019-09-03'], TInterpolation.DISCRETE, False, + '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00}'), + (['Point(1 1)@2019-09-01', 'Point(2 2)@2019-09-03'], TInterpolation.LINEAR, False, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + ([TGeomPointInst('Point(1 1)@2019-09-01'), TGeomPointInst('Point(2 2)@2019-09-03')], TInterpolation.DISCRETE, False, + '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00}'), + ([TGeomPointInst('Point(1 1)@2019-09-01'), TGeomPointInst('Point(2 2)@2019-09-03')], TInterpolation.LINEAR, False, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + (['Point(1 1)@2019-09-01', TGeomPointInst('Point(2 2)@2019-09-03')], TInterpolation.DISCRETE, False, + '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00}'), + (['Point(1 1)@2019-09-01', TGeomPointInst('Point(2 2)@2019-09-03')], TInterpolation.LINEAR, False, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + + (['Point(1 1)@2019-09-01', 'Point(1.5 1.5)@2019-09-02', 'Point(2 2)@2019-09-03'], TInterpolation.LINEAR, True, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + ([TGeomPointInst('Point(1 1)@2019-09-01'), TGeomPointInst('Point(1.5 1.5)@2019-09-02'), TGeomPointInst('Point(2 2)@2019-09-03')], + TInterpolation.LINEAR, True, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + (['Point(1 1)@2019-09-01', 'Point(1.5 1.5)@2019-09-02', TGeomPointInst('Point(2 2)@2019-09-03')], TInterpolation.LINEAR, True, + '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-03 00:00:00+00]'), + ], + ids=['String Discrete', 'String Linear', 'TGeomPointInst Discrete', 'TGeomPointInst Linear', 'Mixed Discrete', + 'Mixed Linear', 'String Linear Normalized', 'TGeomPointInst Linear Normalized', + 'Mixed Linear Normalized'] + ) + def test_instant_list_sequence_constructor(self, list, interpolation, normalize, expected): + tps = TGeomPointSeq(instant_list=list, interpolation=interpolation, normalize=normalize, upper_inc=True) + assert str(tps) == expected + assert tps.interpolation() == interpolation + + tps2 = TGeomPointSeq.from_instants(list, interpolation=interpolation, normalize=normalize, upper_inc=True) + assert str(tps2) == expected + assert tps2.interpolation() == interpolation + + @pytest.mark.parametrize( + 'temporal', + [tpi, tpds, tps, tpss, tpi3d, tpds3d, tps3d, tpss3d], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet', + 'Instant 3D', 'Discrete Sequence 3D', 'Sequence 3D', 'SequenceSet 3D'] + ) + def test_from_as_constructor(self, temporal): + assert temporal == temporal.from_wkb(temporal.as_wkb()) + assert temporal == temporal.from_hexwkb(temporal.as_hexwkb()) + assert temporal == temporal.from_mfjson(temporal.as_mfjson()) + + @pytest.mark.parametrize( + 'temporal', + [tpi, tpds, tps, tpss, tpi3d, tpds3d, tps3d, tpss3d], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet', + 'Instant 3D', 'Discrete Sequence 3D', 'Sequence 3D', 'SequenceSet 3D'] + ) + def test_copy_constructor(self, temporal): + other = copy(temporal) + assert temporal == other + assert temporal is not other + + +class TestTGeomPointOutputs(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 'POINT(1 1)@2019-09-01 00:00:00+00'), + (tpds, '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00}'), + (tps, '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00]'), + (tpss, '{[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00], ' + '[POINT(1 1)@2019-09-03 00:00:00+00, POINT(1 1)@2019-09-05 00:00:00+00]}'), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_str(self, temporal, expected): + assert str(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 'TGeomPointInst(POINT(1 1)@2019-09-01 00:00:00+00)'), + (tpds, 'TGeomPointSeq({POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00})'), + (tps, 'TGeomPointSeq([POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00])'), + (tpss, 'TGeomPointSeqSet({[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00], ' + '[POINT(1 1)@2019-09-03 00:00:00+00, POINT(1 1)@2019-09-05 00:00:00+00]})'), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_repr(self, temporal, expected): + assert repr(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 'POINT(1 1)@2019-09-01 00:00:00+00'), + (tpds, '{POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00}'), + (tps, '[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00]'), + (tpss, '{[POINT(1 1)@2019-09-01 00:00:00+00, POINT(2 2)@2019-09-02 00:00:00+00], ' + '[POINT(1 1)@2019-09-03 00:00:00+00, POINT(1 1)@2019-09-05 00:00:00+00]}'), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_wkt(self, temporal, expected): + assert temporal.as_wkt() == expected + assert temporal.as_ewkt() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, '01280001000000000000F03F000000000000F03F00A01E4E71340200'), + (tpds, '012800060200000003000000000000F03F000000000000F03F00A01E4E71340200' + '000000000000004000000000000000400000F66B85340200'), + (tps, '0128000E0200000003000000000000F03F000000000000F03F00A01E4E71340200' + '000000000000004000000000000000400000F66B85340200'), + (tpss, '0128000F020000000200000003000000000000F03F000' + '000000000F03F00A01E4E7134020000000000000000400' + '0000000000000400000F66B853402000200000003000000' + '000000F03F000000000000F03F0060CD8999340200000000000000F03F000000000000F03F00207CC5C1340200'), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_hexwkb(self, temporal, expected): + assert temporal.as_hexwkb() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, '{"type":"Point","bbox":[1.00,1.00,1.00,1.00],"coordinates":[1,1]}'), + (tpds, '{"type":"MultiPoint","bbox":[1.00,1.00,2.00,2.00],"coordinates":[[1,1],[2,2]]}'), + (tps, '{"type":"LineString","bbox":[1.00,1.00,2.00,2.00],"coordinates":[[1,1],[2,2]]}'), + (tpss, '{"type":"GeometryCollection","bbox":[1.00,1.00,2.00,2.00],' + '"geometries":[{"type":"Point","coordinates":[1,1]},{"type":"LineString","coordinates":[[1,1],[2,2]]}]}'), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_geojson(self, temporal, expected): + assert temporal.as_geojson(precision=2) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, '{\n' + ' "type": "MovingGeomPoint",\n' + ' "bbox": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ]\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-01T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "coordinates": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ]\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00"\n' + ' ],\n' + ' "interpolation": "None"\n' + ' }'), + (tpds, '{\n' + ' "type": "MovingGeomPoint",\n' + ' "bbox": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 2,\n' + ' 2\n' + ' ]\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-02T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "coordinates": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 2,\n' + ' 2\n' + ' ]\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true,\n' + ' "interpolation": "Discrete"\n' + ' }'), + (tps, '{\n' + ' "type": "MovingGeomPoint",\n' + ' "bbox": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 2,\n' + ' 2\n' + ' ]\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-02T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "coordinates": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 2,\n' + ' 2\n' + ' ]\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true,\n' + ' "interpolation": "Linear"\n' + ' }'), + (tpss, '{\n' + ' "type": "MovingGeomPoint",\n' + ' "bbox": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 2,\n' + ' 2\n' + ' ]\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-05T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "sequences": [\n' + ' {\n' + ' "coordinates": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 2,\n' + ' 2\n' + ' ]\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' {\n' + ' "coordinates": [\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' [\n' + ' 1,\n' + ' 1\n' + ' ]\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-03T00:00:00+00",\n' + ' "2019-09-05T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' }\n' + ' ],\n' + ' "interpolation": "Linear"\n' + ' }'), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_mfjson(self, temporal, expected): + assert temporal.as_mfjson() == expected + + +class TestTGeomPointTemporalAccessors(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + tpi3d = TGeomPointInst('Point(1 1 1)@2019-09-01') + tpds3d = TGeomPointSeq('{Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02}') + tps3d = TGeomPointSeq('[Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02]') + tpss3d = TGeomPointSeqSet('{[Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02],' + '[Point(1 1 1)@2019-09-03, Point(1 1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TInterpolation.NONE), + (tpds, TInterpolation.DISCRETE), + (tps, TInterpolation.LINEAR), + (tpss, TInterpolation.LINEAR), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_interpolation(self, temporal, expected): + assert temporal.interpolation() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, {Point(1,1)}), + (tpds, {Point(1,1), Point(2,2)}), + (tps, {Point(1,1), Point(2,2)}), + (tpss, {Point(1,1), Point(2,2)}), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_set(self, temporal, expected): + assert temporal.value_set() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, [Point(1,1)]), + (tpds, [Point(1,1), Point(2,2)]), + (tps, [Point(1,1), Point(2,2)]), + (tpss, [Point(1,1), Point(2,2)]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_values(self, temporal, expected): + assert temporal.values() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, Point(1,1)), + (tpds, Point(1,1)), + (tps, Point(1,1)), + (tpss, Point(1,1)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_value(self, temporal, expected): + assert temporal.start_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, Point(1,1)), + (tpds, Point(2,2)), + (tps, Point(2,2)), + (tpss, Point(1,1)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_value(self, temporal, expected): + assert temporal.end_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, Point(1,1)), + (tpds, Point(1,1)), + (tps, Point(1,1)), + (tpss, Point(1,1)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_at_timestamp(self, temporal, expected): + assert temporal.value_at_timestamp(datetime(2019, 9, 1)) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, PeriodSet('{[2019-09-01, 2019-09-01]}')), + (tpds, PeriodSet('{[2019-09-01, 2019-09-01], [2019-09-02, 2019-09-02]}')), + (tps, PeriodSet('{[2019-09-01, 2019-09-02]}')), + (tpss, PeriodSet('{[2019-09-01, 2019-09-02], [2019-09-03, 2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_time(self, temporal, expected): + assert temporal.time() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, timedelta()), + (tpds, timedelta()), + (tps, timedelta(days=1)), + (tpss, timedelta(days=3)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_duration(self, temporal, expected): + assert temporal.duration() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, timedelta()), + (tpds, timedelta(days=1)), + (tps, timedelta(days=1)), + (tpss, timedelta(days=4)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_duration_ignoring_gaps(self, temporal, expected): + assert temporal.duration(ignore_gaps=True) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, Period('[2019-09-01, 2019-09-01]')), + (tpds, Period('[2019-09-01, 2019-09-02]')), + (tps, Period('[2019-09-01, 2019-09-02]')), + (tpss, Period('[2019-09-01, 2019-09-05]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_period(self, temporal, expected): + assert temporal.period() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, Period('[2019-09-01, 2019-09-01]')), + (tpds, Period('[2019-09-01, 2019-09-02]')), + (tps, Period('[2019-09-01, 2019-09-02]')), + (tpss, Period('[2019-09-01, 2019-09-05]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timespan(self, temporal, expected): + assert temporal.timespan() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 1), + (tpds, 2), + (tps, 2), + (tpss, 4), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_num_instants(self, temporal, expected): + assert temporal.num_instants() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, tpi), + (tpds, tpi), + (tps, tpi), + (tpss, tpi), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_instant(self, temporal, expected): + assert temporal.start_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, tpi), + (tpds, TGeomPointInst('Point(2 2)@2019-09-02')), + (tps, TGeomPointInst('Point(2 2)@2019-09-02')), + (tpss, TGeomPointInst('Point(1 1)@2019-09-05')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_instant(self, temporal, expected): + assert temporal.end_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, tpi), + (tpds, TGeomPointInst('Point(2 2)@2019-09-02')), + (tps, TGeomPointInst('Point(2 2)@2019-09-02')), + (tpss, TGeomPointInst('Point(2 2)@2019-09-02')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_max_instant(self, temporal, expected): + assert temporal.max_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, tpi), + (tpds, tpi), + (tps, tpi), + (tpss, tpi), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_min_instant(self, temporal, expected): + assert temporal.min_instant() == expected + + @pytest.mark.parametrize( + 'temporal, n, expected', + [ + (tpi, 0, tpi), + (tpds, 1, TGeomPointInst('Point(2 2)@2019-09-02')), + (tps, 1, TGeomPointInst('Point(2 2)@2019-09-02')), + (tpss, 2, TGeomPointInst('Point(1 1)@2019-09-03')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_instant_n(self, temporal, n, expected): + assert temporal.instant_n(n) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, [tpi]), + (tpds, [tpi, TGeomPointInst('Point(2 2)@2019-09-02')]), + (tps, [tpi, TGeomPointInst('Point(2 2)@2019-09-02')]), + (tpss, [tpi, TGeomPointInst('Point(2 2)@2019-09-02'), TGeomPointInst('Point(1 1)@2019-09-03'), TGeomPointInst('Point(1 1)@2019-09-05')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_instants(self, temporal, expected): + assert temporal.instants() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 1), + (tpds, 2), + (tps, 2), + (tpss, 4), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_num_timestamps(self, temporal, expected): + assert temporal.num_timestamps() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tpds, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tps, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tpss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_timestamp(self, temporal, expected): + assert temporal.start_timestamp() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tpds, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tps, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tpss, datetime(year=2019, month=9, day=5, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_timestamp(self, temporal, expected): + assert temporal.end_timestamp() == expected + + @pytest.mark.parametrize( + 'temporal, n, expected', + [ + (tpi, 0, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tpds, 1, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tps, 1, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tpss, 2, datetime(year=2019, month=9, day=3, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timestamp_n(self, temporal, n, expected): + assert temporal.timestamp_n(n) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)]), + (tpds, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)]), + (tps, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)]), + (tpss, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=3, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=5, tzinfo=timezone.utc)]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timestamps(self, temporal, expected): + assert temporal.timestamps() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpds, [TGeomPointSeq('[Point(1 1)@2019-09-01]'), TGeomPointSeq('[Point(2 2)@2019-09-02]')]), + (tps, [TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')]), + (tpss, + [TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + TGeomPointSeq('[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]')]), + ], + ids=['Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_segments(self, temporal, expected): + assert temporal.segments() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 382694564), + (tpds, 1664033448), + (tps, 1664033448), + (tpss, 2878566103), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_hash(self, temporal, expected): + assert hash(temporal) == expected + + def test_value_timestamp(self): + assert self.tpi.value() == Point(1,1) + assert self.tpi.timestamp() == datetime(year=2019, month=9, day=1, tzinfo=timezone.utc) + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpds, True), + (tps, True), + ], + ids=['Discrete Sequence', 'Sequence'] + ) + def test_lower_upper_inc(self, temporal, expected): + assert temporal.lower_inc() == expected + assert temporal.upper_inc() == expected + + def test_instant_functions(self): + assert self.tpi.value() == Point(1,1) + assert self.tpi.timestamp() == datetime(year=2019, month=9, day=1, tzinfo=timezone.utc) + + def test_sequenceset_functions(self): + tpss1 =TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],' + '[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05], [Point(3 3)@2019-09-06]}') + assert tpss1.num_sequences() == 3 + assert tpss1.start_sequence() == TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + assert tpss1.end_sequence() == TGeomPointSeq('[Point(3 3)@2019-09-06]') + assert tpss1.sequence_n(1) == TGeomPointSeq('[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]') + assert tpss1.sequences() == [TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + TGeomPointSeq('[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]'), + TGeomPointSeq('[Point(3 3)@2019-09-06]')] + + +class TestTGeomPointTPointAccessors(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + tpi3d = TGeomPointInst('Point(1 1 1)@2019-09-01') + tpds3d = TGeomPointSeq('{Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02}') + tps3d = TGeomPointSeq('[Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02]') + tpss3d = TGeomPointSeqSet('{[Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02],' + '[Point(1 1 1)@2019-09-03, Point(1 1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, STBox('STBOX XT(((1,1),(1,1)),[2019-09-01, 2019-09-01])')), + (tpds, STBox('STBOX XT(((1,1),(2,2)),[2019-09-01, 2019-09-02])')), + (tps, STBox('STBOX XT(((1,1),(2,2)),[2019-09-01, 2019-09-02])')), + (tpss, STBox('STBOX XT(((1,1),(2,2)),[2019-09-01, 2019-09-05])')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_bounding_box(self, temporal, expected): + assert temporal.bounding_box() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 0), + (tpds, 0), + (tps, math.sqrt(2)), + (tpss, math.sqrt(2)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_length(self, temporal, expected): + assert temporal.length() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TFloatInst('0@2019-09-01')), + (tpds, TFloatSeq('{0@2019-09-01, 0@2019-09-02}')), + (tps, TFloatSeq('[0@2019-09-01, 1.4142135623730951@2019-09-02]')), + (tpss, TFloatSeqSet('{[0@2019-09-01, 1.4142135623730951@2019-09-02],' + '[1.4142135623730951@2019-09-03, 1.4142135623730951@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_cumulative_length(self, temporal, expected): + assert temporal.cumulative_length() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, None), + (tpds, None), + (tps, TFloatSeq('Interp=Step;[1.4142135623730951@2019-09-01, 1.4142135623730951@2019-09-02]') / 3600 / 24), + (tpss, TFloatSeqSet('Interp=Step;{[1.4142135623730951@2019-09-01, 1.4142135623730951@2019-09-02],' + '[0@2019-09-03, 0@2019-09-05]}') / 3600 / 24), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_speed(self, temporal, expected): + assert temporal.speed() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TFloatInst('1@2019-09-01')), + (tpds, TFloatSeq('{1@2019-09-01, 2@2019-09-02}')), + (tps, TFloatSeq('[1@2019-09-01, 2@2019-09-02]')), + (tpss, TFloatSeqSet('{[1@2019-09-01, 2@2019-09-02],' + '[1@2019-09-03, 1@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_x_y(self, temporal, expected): + assert temporal.x() == expected + assert temporal.y() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi3d, TFloatInst('1@2019-09-01')), + (tpds3d, TFloatSeq('{1@2019-09-01, 2@2019-09-02}')), + (tps3d, TFloatSeq('[1@2019-09-01, 2@2019-09-02]')), + (tpss3d, TFloatSeqSet('{[1@2019-09-01, 2@2019-09-02],' + '[1@2019-09-03, 1@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_x_y_z(self, temporal, expected): + assert temporal.x() == expected + assert temporal.y() == expected + assert temporal.z() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, False), + (tpds, False), + (tps, False), + (tpss, False), + (tpi3d, True), + (tpds3d, True), + (tps3d, True), + (tpss3d, True), + ], + ids=['Instant 2D', 'Discrete Sequence 2D', 'Sequence 2D', 'SequenceSet 2D', + 'Instant 3D', 'Discrete Sequence 3D', 'Sequence 3D', 'SequenceSet 3D'] + ) + def test_has_z(self, temporal, expected): + assert temporal.has_z() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi3d, True), + (tpds3d, True), + (tps3d, True), + (tpss3d, True), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_has_z(self, temporal, expected): + assert temporal.has_z() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, []), + (tpds, []), + (tps, [STBox('STBOX XT(((1,1),(2,2)),[2019-09-01, 2019-09-02])')]), + (tpss, [STBox('STBOX XT(((1,1),(2,2)),[2019-09-01, 2019-09-02])'), + STBox('STBOX XT(((1,1),(1,1)),[2019-09-03, 2019-09-05])')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_stboxes(self, temporal, expected): + assert temporal.stboxes() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, True), + (tpds, True), + (tps, True), + (tpss, True), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_is_simple(self, temporal, expected): + assert temporal.is_simple() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TFloatInst('45@2019-09-01')), + (tpds, TFloatSeq('{45@2019-09-01,45@2019-09-02}')), + (tps, TFloatSeq('[45@2019-09-01,45@2019-09-02]')), + (tpss, TFloatSeqSet('{[45@2019-09-01,45@2019-09-02],[45@2019-09-03,45@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_bearing(self, temporal, expected): + assert temporal.bearing(Point(3,3)).to_degrees() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, None), + (tpds, None), + (tps, TFloatSeqSet('Interp=Step;{[45@2019-09-01,45@2019-09-02]}')), + (tpss, TFloatSeqSet('Interp=Step;{[45@2019-09-01,45@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_azimuth(self, temporal, expected): + res = temporal.azimuth() + result = res.to_degrees() if res is not None else None + assert result == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, None), + (tpds, None), + (tps, TFloatSeq('{0@2019-09-01,0@2019-09-02}')), + (tpss, TFloatSeqSet('{0@2019-09-01,0@2019-09-02}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_angular_difference(self, temporal, expected): + res = temporal.angular_difference() + result = res.to_degrees() if res is not None else None + assert result == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, Point(1,1)), + (tpds, Point(1.5,1.5)), + (tps, Point(1.5,1.5)), + # (tpss, Point(1.167,1.167)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence'] #, 'SequenceSet'] + ) + def test_time_weighted_centroid(self, temporal, expected): + assert temporal.time_weighted_centroid() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 0), + (tpds, 0), + (tps, 0), + (tpss, 0), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_srid(self, temporal, expected): + assert temporal.srid() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, 5676), + (tpds, 5676), + (tps, 5676), + (tpss, 5676), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_set_srid(self, temporal, expected): + assert temporal.set_srid(5676).srid() == expected + + +class TestTGeomPointConversions(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TGeogPointInst('Point(1 1)@2019-09-01')), + (tpds, TGeogPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}')), + (tps, TGeogPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), + (tpss, TGeogPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],' + '[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_geographic(self, temporal, expected): + assert temporal.to_geographic() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, Point(1,1)), + (tpds, MultiPoint([(1,1),(2,2)])), + (tps, LineString([(1,1),(2,2)])), + (tpss, GeometryCollection([Point(1,1),LineString([(1,1),(2,2)])])), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_shapely_geometry(self, temporal, expected): + assert temporal.to_shapely_geometry(precision=2) == expected + + +class TestTGeomPointTransformations(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + tps_d = TGeomPointSeq('[Point(1 1)@2019-09-01]') + tpss_d = TGeomPointSeqSet('{[Point(1 1)@2019-09-01],[Point(2 2)@2019-09-03]}') + tps_s = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02]') + tpss_s = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02],' + '[Point(2 2)@2019-09-03, Point(2 2)@2019-09-05]}') + tps_l = TGeomPointSeq('Interp=Step;[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss_l = TGeomPointSeqSet('Interp=Step;{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],' + '[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, interpolation, expected', + [ + (tpi, TInterpolation.DISCRETE, + TGeomPointSeq('{Point(1 1)@2019-09-01}')), + (tpds, TInterpolation.DISCRETE, tpds), + (tps_d, TInterpolation.DISCRETE, + TGeomPointSeq('{Point(1 1)@2019-09-01}')), + (tpss_d, TInterpolation.DISCRETE, + TGeomPointSeq('{Point(1 1)@2019-09-01,Point(2 2)@2019-09-03}')), + + (tpi, TInterpolation.STEPWISE, + TGeomPointSeq('Interp=Step;[Point(1 1)@2019-09-01]')), + (tpds, TInterpolation.STEPWISE, + TGeomPointSeqSet('Interp=Step;{[Point(1 1)@2019-09-01], [Point(2 2)@2019-09-02]}')), + (tps_s, TInterpolation.STEPWISE, + TGeomPointSeq('Interp=Step;[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02]')), + (tpss_s, TInterpolation.STEPWISE, + TGeomPointSeqSet('Interp=Step;{[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02],' + '[Point(2 2)@2019-09-03, Point(2 2)@2019-09-05]}')), + + (tpi, TInterpolation.LINEAR, + TGeomPointSeq('[Point(1 1)@2019-09-01]')), + (tpds, TInterpolation.LINEAR, + TGeomPointSeqSet('{[Point(1 1)@2019-09-01], [Point(2 2)@2019-09-02]}')), + (tps_l, TInterpolation.LINEAR, + TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02), [Point(2 2)@2019-09-02]}')), + (tpss_l, TInterpolation.LINEAR, + TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02),' + '[Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1 )@2019-09-05]}')), + ], + ids=['Instant to discrete', 'Discrete Sequence to discrete', 'Sequence to discrete', 'SequenceSet to discrete', + 'Instant to step', 'Discrete Sequence to step', 'Sequence to step', 'SequenceSet to step', + 'Instant to linear', 'Discrete Sequence to linear', 'Sequence to linear', 'SequenceSet to linear'] + ) + def test_set_interpolation(self, temporal, interpolation, expected): + assert temporal.set_interpolation(interpolation) == expected + + @pytest.mark.parametrize( + 'tpoint, delta, expected', + [(tpi, timedelta(days=4), TGeomPointInst('Point(1 1)@2019-09-05')), + (tpi, timedelta(days=-4), TGeomPointInst('Point(1 1)@2019-08-28')), + (tpi, timedelta(hours=2), TGeomPointInst('Point(1 1)@2019-09-01 02:00:00')), + (tpi, timedelta(hours=-2), TGeomPointInst('Point(1 1)@2019-08-31 22:00:00')), + (tpds, timedelta(days=4), TGeomPointSeq('{Point(1 1)@2019-09-05, Point(2 2)@2019-09-06}')), + (tpds, timedelta(days=-4), TGeomPointSeq('{Point(1 1)@2019-08-28, Point(2 2)@2019-08-29}')), + (tpds, timedelta(hours=2), TGeomPointSeq('{Point(1 1)@2019-09-01 02:00:00, Point(2 2)@2019-09-02 02:00:00}')), + (tpds, timedelta(hours=-2), TGeomPointSeq('{Point(1 1)@2019-08-31 22:00:00, Point(2 2)@2019-09-01 22:00:00}')), + (tps, timedelta(days=4), TGeomPointSeq('[Point(1 1)@2019-09-05, Point(2 2)@2019-09-06]')), + (tps, timedelta(days=-4), TGeomPointSeq('[Point(1 1)@2019-08-28, Point(2 2)@2019-08-29]')), + (tps, timedelta(hours=2), TGeomPointSeq('[Point(1 1)@2019-09-01 02:00:00, Point(2 2)@2019-09-02 02:00:00]')), + (tps, timedelta(hours=-2), TGeomPointSeq('[Point(1 1)@2019-08-31 22:00:00, Point(2 2)@2019-09-01 22:00:00]')), + (tpss, timedelta(days=4), + TGeomPointSeqSet('{[Point(1 1)@2019-09-05, Point(2 2)@2019-09-06],[Point(1 1)@2019-09-07, Point(1 1)@2019-09-09]}')), + (tpss, timedelta(days=-4), + TGeomPointSeqSet('{[Point(1 1)@2019-08-28, Point(2 2)@2019-08-29],[Point(1 1)@2019-08-30, Point(1 1)@2019-09-01]}')), + (tpss, timedelta(hours=2), + TGeomPointSeqSet('{[Point(1 1)@2019-09-01 02:00:00, Point(2 2)@2019-09-02 02:00:00],' + '[Point(1 1)@2019-09-03 02:00:00, Point(1 1)@2019-09-05 02:00:00]}')), + (tpss, timedelta(hours=-2), + TGeomPointSeqSet('{[Point(1 1)@2019-08-31 22:00:00, Point(2 2)@2019-09-01 22:00:00],' + '[Point(1 1)@2019-09-02 22:00:00, Point(1 1)@2019-09-04 22:00:00]}')), + ], + ids=['Instant posi(tpve days', 'Instant nega(tpve days', + 'Instant posi(tpve hours', 'Instant nega(tpve hours', + 'Discrete Sequence posi(tpve days', 'Discrete Sequence nega(tpve days', + 'Discrete Sequence posi(tpve hours', 'Discrete Sequence nega(tpve hours', + 'Sequence posi(tpve days', 'Sequence nega(tpve days', + 'Sequence posi(tpve hours', 'Sequence nega(tpve hours', + 'Sequence Set posi(tpve days', 'Sequence Set nega(tpve days', + 'Sequence Set posi(tpve hours', 'Sequence Set nega(tpve hours'] + ) + def test_shift(self, tpoint, delta, expected): + assert tpoint.shift(delta) == expected + + @pytest.mark.parametrize( + 'tpoint, delta, expected', + [(tpi, timedelta(days=4), TGeomPointInst('Point(1 1)@2019-09-01')), + (tpi, timedelta(hours=2), TGeomPointInst('Point(1 1)@2019-09-01')), + (tpds, timedelta(days=4), TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-05}')), + (tpds, timedelta(hours=2), TGeomPointSeq('{Point(1 1)@2019-09-01 00:00:00, Point(2 2)@2019-09-01 02:00:00}')), + (tps, timedelta(days=4), TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-05]')), + (tps, timedelta(hours=2), TGeomPointSeq('[Point(1 1)@2019-09-01 00:00:00, Point(2 2)@2019-09-01 02:00:00]')), + (tpss, timedelta(days=4), + TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + (tpss, timedelta(hours=2), + TGeomPointSeqSet('{[Point(1 1)@2019-09-01 00:00:00, Point(2 2)@2019-09-01 00:30:00],' + '[Point(1 1)@2019-09-01 01:00:00, Point(1 1)@2019-09-01 02:00:00]}')), + ], + ids=['Instant posi(tpve days', 'Instant posi(tpve hours', + 'Discrete Sequence posi(tpve days', 'Discrete Sequence posi(tpve hours', + 'Sequence posi(tpve days', 'Sequence posi(tpve hours', + 'Sequence Set posi(tpve days', 'Sequence Set posi(tpve hours'] + ) + def test_scale(self, tpoint, delta, expected): + assert tpoint.tscale(delta) == expected + + def test_shift_tscale(self): + assert self.tpss.shift_tscale(timedelta(days=4), timedelta(hours=2)) == \ + TGeomPointSeqSet('{[Point(1 1)@2019-09-05 00:00:00, Point(2 2)@2019-09-05 00:30:00],' + '[Point(1 1)@2019-09-05 01:00:00, Point(1 1)@2019-09-05 02:00:00]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TGeomPointInst('Point(1 1)@2019-09-01'), tpi), + (TGeomPointSeq('{Point(1 1)@2019-09-01}'), tpi), + (TGeomPointSeq('[Point(1 1)@2019-09-01]'), tpi), + (TGeomPointSeqSet('{[Point(1 1)@2019-09-01]}'), tpi), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_instant(self, temporal, expected): + temp = temporal.to_instant() + assert isinstance(temp, TGeomPointInst) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TGeomPointInst('Point(1 1)@2019-09-01'), + TGeomPointSeq('[Point(1 1)@2019-09-01]')), + (TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}'), + TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}')), + (TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), + (TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}'), + TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_sequence(self, temporal, expected): + temp = temporal.to_sequence() + assert isinstance(temp, TGeomPointSeq) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TGeomPointInst('Point(1 1)@2019-09-01'), + TGeomPointSeqSet('{[Point(1 1)@2019-09-01]}')), + (TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}'), + TGeomPointSeqSet('{[Point(1 1)@2019-09-01], [Point(2 2)@2019-09-02]}')), + (TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}'), + TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_sequenceset(self, temporal, expected): + temp = temporal.to_sequenceset() + assert isinstance(temp, TGeomPointSeqSet) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TGeomPointInst('Point(1.123456789 1.123456789)@2019-09-01'), + TGeomPointInst('Point(1.12 1.12)@2019-09-01')), + (TGeomPointSeq('{Point(1.123456789 1.123456789)@2019-09-01,' + 'Point(2.123456789 2.123456789)@2019-09-02}'), + TGeomPointSeq('{Point(1.12 1.12)@2019-09-01,Point(2.12 2.12)@2019-09-02}')), + (TGeomPointSeq('[Point(1.123456789 1.123456789)@2019-09-01,' + 'Point(2.123456789 2.123456789)@2019-09-02]'), + TGeomPointSeq('[Point(1.12 1.12)@2019-09-01,Point(2.12 2.12)@2019-09-02]')), + (TGeomPointSeqSet('{[Point(1.123456789 1.123456789)@2019-09-01,' + 'Point(2.123456789 2.123456789)@2019-09-02],' + '[Point(1.123456789 1.123456789)@2019-09-03,' + 'Point(1.123456789 1.123456789)@2019-09-05]}'), + TGeomPointSeq('{[Point(1.12 1.12)@2019-09-01,Point(2.12 2.12)@2019-09-02],' + '[Point(1.12 1.12)@2019-09-03,Point(1.12 1.12)@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_round(self, temporal, expected): + assert temporal.round(maxdd=2) + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, [tpi]), + (tpds, [tpds]), + (tps, [tps]), + (tpss, [TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), + TGeomPointSeq('[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_make_simple(self, temporal, expected): + assert temporal.make_simple() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, STBox('STBOX XT(((-1,-1),(3,3)),[2019-09-01, 2019-09-01])')), + (tpds, STBox('STBOX XT(((-1,-1),(4,4)),[2019-09-01, 2019-09-02])')), + (tps, STBox('STBOX XT(((-1,-1),(4,4)),[2019-09-01, 2019-09-02])')), + (tpss, STBox('STBOX XT(((-1,-1),(4,4)),[2019-09-01, 2019-09-05])')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_expand(self, temporal, expected): + assert temporal.expand(2) == expected + + +class TestTGeomPointModifications(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, sequence, expected', + [ + (tpi, TGeomPointSeq('{Point(1 1)@2019-09-03}'), TGeomPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-03}')), + (tpds, TGeomPointSeq('{Point(1 1)@2019-09-03}'), TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02, Point(1 1)@2019-09-03}')), + (tps, TGeomPointSeq('[Point(1 1)@2019-09-03]'), TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02, Point(1 1)@2019-09-03]}')), + (tpss, TGeomPointSeq('[Point(1 1)@2019-09-06]'), + TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05],[Point(1 1)@2019-09-06]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_insert(self, temporal, sequence, expected): + assert temporal.insert(sequence) == expected + + @pytest.mark.parametrize( + 'temporal, instant, expected', + [ + (tpi, TGeomPointInst('Point(2 2)@2019-09-01'), TGeomPointInst('Point(2 2)@2019-09-01')), + (tpds, TGeomPointInst('Point(2 2)@2019-09-01'), TGeomPointSeq('{Point(2 2)@2019-09-01, Point(2 2)@2019-09-02}')), + (tps, TGeomPointInst('Point(2 2)@2019-09-01'), + TGeomPointSeqSet('{[Point(2 2)@2019-09-01], (Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tpss, TGeomPointInst('Point(2 2)@2019-09-01'), + TGeomPointSeqSet('{[Point(2 2)@2019-09-01], (Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_update(self, temporal, instant, expected): + assert temporal.update(instant) == expected + + @pytest.mark.parametrize( + 'temporal, time, expected', + [ + (tpi, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), None), + (tpi, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), tpi), + (tpds, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), TGeomPointSeq('{Point(2 2)@2019-09-02}')), + (tps, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + TGeomPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tpss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + TGeomPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + ], + ids=['Instant intersection', 'Instant disjoint', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_delete(self, temporal, time, expected): + assert temporal.delete(time) == expected + + @pytest.mark.parametrize( + 'temporal, instant, expected', + [ + (tpi, TGeomPointInst('Point(1 1)@2019-09-02'), TGeomPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-02}')), + (tpds, TGeomPointInst('Point(1 1)@2019-09-03'), TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02, Point(1 1)@2019-09-03}')), + (tps, TGeomPointInst('Point(1 1)@2019-09-03'), TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02, Point(1 1)@2019-09-03]')), + (tpss, TGeomPointInst('Point(1 1)@2019-09-06'), + TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-06]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_append_instant(self, temporal, instant, expected): + assert temporal.append_instant(instant) == expected + + @pytest.mark.parametrize( + 'temporal, sequence, expected', + [ + (tpds, TGeomPointSeq('{Point(1 1)@2019-09-03}'), TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02, Point(1 1)@2019-09-03}')), + (tps, TGeomPointSeq('[Point(1 1)@2019-09-03]'), TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02], [Point(1 1)@2019-09-03]}')), + (tpss, TGeomPointSeq('[Point(1 1)@2019-09-06]'), + TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05],[Point(1 1)@2019-09-06]}')), + ], + ids=['Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_append_sequence(self, temporal, sequence, expected): + assert temporal.append_sequence(sequence) == expected + + +class TestTGeomPointRestrictors(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + timestamp = datetime(2019, 9, 1) + timestamp_set = TimestampSet('{2019-09-01, 2019-09-03}') + period = Period('[2019-09-01, 2019-09-02]') + period_set = PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, restrictor, expected', + [ + (tpi, timestamp, TGeomPointInst('Point(1 1)@2019-09-01')), + (tpi, timestamp_set, TGeomPointInst('Point(1 1)@2019-09-01')), + (tpi, period, TGeomPointInst('Point(1 1)@2019-09-01')), + (tpi, period_set, TGeomPointInst('Point(1 1)@2019-09-01')), + (tpi, Point(1,1), TGeomPointInst('Point(1 1)@2019-09-01')), + (tpi, Point(2,2), None), + + (tpds, timestamp, TGeomPointSeq('{Point(1 1)@2019-09-01}')), + (tpds, timestamp_set, TGeomPointSeq('{Point(1 1)@2019-09-01}')), + (tpds, period, TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}')), + (tpds, period_set, TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}')), + (tpds, Point(1,1), TGeomPointSeq('{Point(1 1)@2019-09-01}')), + (tpds, Point(2,2), TGeomPointSeq('{Point(2 2)@2019-09-02}')), + + (tps, timestamp, TGeomPointSeq('[Point(1 1)@2019-09-01]')), + (tps, timestamp_set, TGeomPointSeq('{Point(1 1)@2019-09-01}')), + (tps, period, TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), + (tps, period_set, TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')), + (tps, Point(1,1), TGeomPointSeq('[Point(1 1)@2019-09-01]')), + (tps, Point(2,2), TGeomPointSeq('[Point(2 2)@2019-09-02]')), + + (tpss, timestamp, TGeomPointSeqSet('[Point(1 1)@2019-09-01]')), + (tpss, timestamp_set, TGeomPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-03}')), + (tpss, period, TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tpss, period_set, + TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + (tpss, Point(1,1), TGeomPointSeqSet('{[Point(1 1)@2019-09-01],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + (tpss, Point(2,2), TGeomPointSeqSet('{[Point(2 2)@2019-09-02]}')), + + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', 'Instant-PeriodSet', 'Instant-True', + 'Instant-Point(2,2)', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Discrete Sequence-True', + 'Discrete Sequence-Point(2,2)', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-True', 'Sequence-Point(2,2)', 'SequenceSet-Timestamp', + 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-True', + 'SequenceSet-Point(2,2)'] + ) + def test_at(self, temporal, restrictor, expected): + assert temporal.at(restrictor) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TGeomPointSeqSet('{[Point(1 1)@2019-09-01]}')), + (tpds, TGeomPointSeqSet('{[Point(1 1)@2019-09-01]}')), + (tps, TGeomPointSeqSet('{[Point(1 1)@2019-09-01]}')), + (tpss, TGeomPointSeqSet('{[Point(1 1)@2019-09-01],[Point(1 1)@2019-09-03,Point(1 1)@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_min(self, temporal, expected): + assert temporal.at_min() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TGeomPointSeqSet('{[Point(1 1)@2019-09-01]}')), + (tpds, TGeomPointSeqSet('{[Point(2 2)@2019-09-02]}')), + (tps, TGeomPointSeqSet('{[Point(2 2)@2019-09-02]}')), + (tpss, TGeomPointSeqSet('{[Point(2 2)@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_max(self, temporal, expected): + assert temporal.at_max() == expected + + @pytest.mark.parametrize( + 'temporal, restrictor, expected', + [ + (tpi, timestamp, None), + (tpi, timestamp_set, None), + (tpi, period, None), + (tpi, period_set, None), + (tpi, Point(1,1), None), + (tpi, Point(2,2), TGeomPointInst('Point(1 1)@2019-09-01')), + + (tpds, timestamp, TGeomPointSeq('{Point(2 2)@2019-09-02}')), + (tpds, timestamp_set, TGeomPointSeq('{Point(2 2)@2019-09-02}')), + (tpds, period, None), + (tpds, period_set, None), + (tpds, Point(1,1), TGeomPointSeq('{Point(2 2)@2019-09-02}')), + (tpds, Point(2,2), TGeomPointSeq('{Point(1 1)@2019-09-01}')), + + (tps, timestamp, TGeomPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tps, timestamp_set, TGeomPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tps, period, None), + (tps, period_set, None), + (tps, Point(1,1), TGeomPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tps, Point(2,2), TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02)}')), + + (tpss, timestamp, + TGeomPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + (tpss, timestamp_set, + TGeomPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],(Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + (tpss, period, TGeomPointSeqSet('{[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + (tpss, period_set, None), + (tpss, Point(1,1), TGeomPointSeqSet('{(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]}')), + (tpss, Point(2,2), TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02),[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', 'Instant-PeriodSet', 'Instant-Point(1,1)', + 'Instant-Point(2,2)', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Discrete Sequence-Point(1,1)', + 'Discrete Sequence-Point(2,2)', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-Point(1,1)', 'Sequence-Point(2,2)', 'SequenceSet-Timestamp', + 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-Point(1,1)', + 'SequenceSet-Point(2,2)'] + ) + def test_minus(self, temporal, restrictor, expected): + assert temporal.minus(restrictor) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, None), + (tpds, TGeomPointSeqSet('{[Point(2 2)@2019-09-02]}')), + (tps, TGeomPointSeqSet('{(Point(1 1)@2019-09-01,Point(2 2)@2019-09-02]}')), + (tpss, TGeomPointSeqSet('{(Point(1 1)@2019-09-01,Point(2 2)@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_minus_min(self, temporal, expected): + if expected is None: + assert temporal.minus_min() is None + else: + assert temporal.minus_min() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, None), + (tpds, TGeomPointSeqSet('{[Point(1 1)@2019-09-01]}')), + (tps, TGeomPointSeqSet('{[Point(1 1)@2019-09-01,Point(2 2)@2019-09-02)}')), + (tpss, TGeomPointSeqSet('{[Point(1 1)@2019-09-01,Point(2 2)@2019-09-02),' + '[Point(1 1)@2019-09-03,Point(1 1)@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_minus_max(self, temporal, expected): + if expected is None: + assert temporal.minus_max() is None + else: + assert temporal.minus_max() == expected + + @pytest.mark.parametrize( + 'temporal, restrictor', + [ + (tpi, timestamp), + (tpi, timestamp_set), + (tpi, period), + (tpi, period_set), + (tpi, Point(1,1)), + (tpi, Point(2,2)), + + (tpds, timestamp), + (tpds, timestamp_set), + (tpds, period), + (tpds, period_set), + (tpds, Point(1,1)), + (tpds, Point(2,2)), + + (tps, timestamp), + (tps, timestamp_set), + (tps, period), + (tps, period_set), + (tps, Point(1,1)), + (tps, Point(2,2)), + + (tpss, timestamp), + (tpss, timestamp_set), + (tpss, period), + (tpss, period_set), + (tpss, Point(1,1)), + (tpss, Point(2,2)), + + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', 'Instant-PeriodSet', 'Instant-Point(1,1)', + 'Instant-Point(2,2)', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Discrete Sequence-Point(1,1)', + 'Discrete Sequence-Point(2,2)', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-Point(1,1)', 'Sequence-Point(2,2)', 'SequenceSet-Timestamp', + 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-Point(1,1)', + 'SequenceSet-Point(2,2)'] + ) + def test_at_minus(self, temporal, restrictor): + assert TGeomPoint.merge(temporal.at(restrictor), temporal.minus(restrictor)) == temporal + + @pytest.mark.parametrize( + 'temporal', + [tpi, tpds, tps, tpss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_minus_min_max(self, temporal): + assert TGeomPoint.merge(temporal.at_min(), temporal.minus_min()) == temporal + assert TGeomPoint.merge(temporal.at_max(), temporal.minus_max()) == temporal + + +class TestTGeomPointTopologicalFunctions(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02], [Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(1 1)@2019-09-02'), False), + (tpi, TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), True), + (tpds, TGeomPointInst('Point(1 1)@2019-09-03'), False), + (tpds, TGeomPointSeq('{Point(1 1)@2019-09-01}'), True), + (tps, TGeomPointInst('Point(1 1)@2019-09-03'), False), + (tps, TGeomPointSeq('{Point(1 1)@2019-09-01}'), True), + (tpss, TGeomPointInst('Point(1 1)@2019-09-08'), False), + (tpss, TGeomPointSeq('{Point(1 1)@2019-09-01}'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_adjacent(self, temporal, argument, expected): + assert temporal.is_adjacent(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(1 1)@2019-09-02'), False), + (tpi, TGeomPointSeq('(Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]'), True), + (tpds, TGeomPointInst('Point(1 1)@2019-09-03'), False), + (tpds, TGeomPointSeq('(Point(1 1)@2019-09-02, Point(2 2)@2019-09-03]'), True), + (tps, TGeomPointInst('Point(1 1)@2019-09-03'), False), + (tps, TGeomPointSeq('(Point(1 1)@2019-09-02, Point(2 2)@2019-09-03]'), True), + (tpss, TGeomPointInst('Point(1 1)@2019-09-08'), False), + (tpss, TGeomPointSeq('(Point(1 1)@2019-09-05, Point(2 2)@2019-09-06]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_temporally_adjacent(self, temporal, argument, expected): + assert temporal.is_temporally_adjacent(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(1 1)@2019-09-02'), False), + (tpi, TGeomPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-02]'), True), + (tpds, TGeomPointInst('Point(1 1)@2019-09-02'), False), + (tpds, TGeomPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-02]'), True), + (tps, TGeomPointInst('Point(1 1)@2019-09-02'), False), + (tps, TGeomPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-05]'), True), + (tpss, TGeomPointInst('Point(1 1)@2019-09-02'), False), + (tpss, TGeomPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-05]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_contained_in_contains(self, temporal, argument, expected): + assert temporal.is_contained_in(argument) == expected + assert argument.contains(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(1 1)@2019-09-02'), False), + (tpi, TGeomPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-02]'), True), + (tpds, TGeomPointInst('Point(1 1)@2019-09-02'), False), + (tpds, TGeomPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-02]'), True), + (tps, TGeomPointInst('Point(1 1)@2019-09-02'), False), + (tps, TGeomPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-05]'), True), + (tpss, TGeomPointInst('Point(1 1)@2019-09-02'), False), + (tpss, TGeomPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-05]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_temporally_contained_in_contains(self, temporal, argument, expected): + assert temporal.is_temporally_contained_in(argument) == expected + assert argument.temporally_contains(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(1 1)@2019-09-02'), False), + (tpi, TGeomPointSeq('[Point(1 1)@2019-09-01]'), True), + (tpds, TGeomPointInst('Point(3 3)@2019-09-02'), False), + (tpds, TGeomPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-02]'), True), + (tps, TGeomPointInst('Point(3 3)@2019-09-02'), False), + (tps, TGeomPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-02]'), True), + (tpss, TGeomPointInst('Point(3 3)@2019-09-02'), False), + (tpss, TGeomPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-05]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_overlaps_is_same(self, temporal, argument, expected): + assert temporal.overlaps(argument) == expected + assert temporal.is_same(argument) == expected + + +class TestTGeomPointPositionFunctions(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02], [Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + tpi3d = TGeomPointInst('Point(1 1 1)@2019-09-01') + tpds3d = TGeomPointSeq('{Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02}') + tps3d = TGeomPointSeq('[Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02]') + tpss3d = TGeomPointSeqSet('{[Point(1 1 1)@2019-09-01, Point(2 2 2)@2019-09-02],' + '[Point(1 1 1)@2019-09-03, Point(1 1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(1 1)@2019-09-01'), False), + (tpi, TGeomPointInst('Point(1 1)@2019-10-01'), True), + (tpds, TGeomPointInst('Point(1 1)@2019-09-01'), False), + (tpds, TGeomPointInst('Point(1 1)@2019-10-01'), True), + (tps, TGeomPointInst('Point(1 1)@2019-09-01'), False), + (tps, TGeomPointInst('Point(1 1)@2019-10-01'), True), + (tpss, TGeomPointInst('Point(1 1)@2019-09-01'), False), + (tpss, TGeomPointInst('Point(1 1)@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_before_after(self, temporal, argument, expected): + assert temporal.is_before(argument) == expected + assert argument.is_after(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(1 1)@2019-08-01'), False), + (tpi, TGeomPointInst('Point(1 1)@2019-10-01'), True), + (tpds, TGeomPointInst('Point(1 1)@2019-08-01'), False), + (tpds, TGeomPointInst('Point(1 1)@2019-10-01'), True), + (tps, TGeomPointInst('Point(1 1)@2019-08-01'), False), + (tps, TGeomPointInst('Point(1 1)@2019-10-01'), True), + (tpss, TGeomPointInst('Point(1 1)@2019-08-01'), False), + (tpss, TGeomPointInst('Point(1 1)@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_before(self, temporal, argument, expected): + assert temporal.is_over_or_before(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(1 1)@2019-10-01'), False), + (tpi, TGeomPointInst('Point(1 1)@2019-09-01'), True), + (tpds, TGeomPointInst('Point(1 1)@2019-10-01'), False), + (tpds, TGeomPointInst('Point(1 1)@2019-09-01'), True), + (tps, TGeomPointInst('Point(1 1)@2019-10-01'), False), + (tps, TGeomPointInst('Point(1 1)@2019-09-01'), True), + (tpss, TGeomPointInst('Point(1 1)@2019-10-01'), False), + (tpss, TGeomPointInst('Point(1 1)@2019-09-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_after(self, temporal, argument, expected): + assert temporal.is_over_or_after(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi3d, TGeomPointInst('Point(1 1 1)@2019-09-01'), False), + (tpi3d, TGeomPointInst('Point(3 3 3)@2019-10-01'), True), + (tpds3d, TGeomPointInst('Point(1 1 1)@2019-09-01'), False), + (tpds3d, TGeomPointInst('Point(3 3 3)@2019-10-01'), True), + (tps3d, TGeomPointInst('Point(1 1 1)@2019-09-01'), False), + (tps3d, TGeomPointInst('Point(3 3 3)@2019-10-01'), True), + (tpss3d, TGeomPointInst('Point(1 1 1)@2019-09-01'), False), + (tpss3d, TGeomPointInst('Point(3 3 3)@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_left_right_below_above_front_behind(self, temporal, argument, expected): + assert temporal.is_left(argument) == expected + assert argument.is_right(temporal) == expected + assert temporal.is_below(argument) == expected + assert argument.is_above(temporal) == expected + assert temporal.is_front(argument) == expected + assert argument.is_behind(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi3d, TGeomPointInst('Point(0 0 0)@2019-09-01'), False), + (tpi3d, TGeomPointInst('Point(3 3 3)@2019-10-01'), True), + (tpds3d, TGeomPointInst('Point(1 1 1)@2019-09-01'), False), + (tpds3d, TGeomPointInst('Point(3 3 3)@2019-10-01'), True), + (tps3d, TGeomPointInst('Point(1 1 1)@2019-09-01'), False), + (tps3d, TGeomPointInst('Point(3 3 3)@2019-10-01'), True), + (tpss3d, TGeomPointInst('Point(1 1 1)@2019-09-01'), False), + (tpss3d, TGeomPointInst('Point(3 3 3)@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_left_below_front(self, temporal, argument, expected): + assert temporal.is_over_or_left(argument) == expected + assert temporal.is_over_or_below(argument) == expected + assert temporal.is_over_or_front(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi3d, TGeomPointInst('Point(0 0 0)@2019-09-01'), False), + (tpi3d, TGeomPointInst('Point(3 3 3)@2019-10-01'), True), + (tpds3d, TGeomPointInst('Point(0 0 0)@2019-09-01'), False), + (tpds3d, TGeomPointInst('Point(3 3 3)@2019-10-01'), True), + (tps3d, TGeomPointInst('Point(0 0 0)@2019-09-01'), False), + (tps3d, TGeomPointInst('Point(3 3 3)@2019-10-01'), True), + (tpss3d, TGeomPointInst('Point(0 0 0)@2019-09-01'), False), + (tpss3d, TGeomPointInst('Point(3 3 3)@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_right_above_behind(self, temporal, argument, expected): + assert argument.is_over_or_right(temporal) == expected + assert argument.is_over_or_above(temporal) == expected + assert argument.is_over_or_behind(temporal) == expected + + +class TestTGeomPointSimilarityFunctions(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02], [Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(2 1)@2019-09-02'), 1.0), + (tpds, TGeomPointInst('Point(2 1)@2019-09-03'), 1.0), + (tps, TGeomPointInst('Point(2 1)@2019-09-03'), 1.0), + (tpss, TGeomPointInst('Point(2 1)@2019-09-08'), 1.0), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_frechet_distance(self, temporal, argument, expected): + assert temporal.frechet_distance(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(2 1)@2019-09-02'), 1.0), + (tpds, TGeomPointInst('Point(2 1)@2019-09-03'), 2.0), + (tps, TGeomPointInst('Point(2 1)@2019-09-03'), 2.0), + (tpss, TGeomPointInst('Point(2 1)@2019-09-08'), 4.0), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_dyntimewarp_distance(self, temporal, argument, expected): + assert temporal.dyntimewarp_distance(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, TGeomPointInst('Point(2 1)@2019-09-02'), 1.0), + (tpds, TGeomPointInst('Point(2 1)@2019-09-03'), 1.0), + (tps, TGeomPointInst('Point(2 1)@2019-09-03'), 1.0), + (tpss, TGeomPointInst('Point(2 1)@2019-09-08'), 1.0), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_hausdorff_distance(self, temporal, argument, expected): + assert temporal.hausdorff_distance(argument) == expected + + +class TestTGeomPointEverSpatialOperations(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, True), + (tpds, True), + (tps, True), + (tpss, True), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_ever_contained_withindist_intersects_touches(self, temporal, expected): + assert temporal.is_ever_contained_in(Polygon([(0,0),(0,2),(2,2),(2,0),(0,0)])) == expected + assert temporal.is_ever_contained_in(STBox('STBOX X((0,0),(2,2))')) == expected + assert temporal.is_ever_within_distance(Point(1,1), 1) == expected + assert temporal.is_ever_within_distance(TGeomPointInst('Point(1 1)@2019-09-01'), 1) == expected + assert temporal.ever_intersects(Point(1,1)) == expected + assert temporal.ever_intersects(TGeomPointInst('Point(1 1)@2019-09-01')) == expected + # assert temporal.ever_touches(Point(1,1)) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, True), + (tpds, True), + (tps, True), + (tpss, True), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_ever_disjoint(self, temporal, expected): + assert temporal.is_ever_disjoint(Point(3,3)) == expected + assert temporal.is_ever_disjoint(TGeomPointInst('Point(3 3)@2019-09-01')) == expected + + +class TestTGeomPointTemporalSpatialOperations(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TBoolInst('True@2019-09-01')), + (tpds, TBoolSeq('{True@2019-09-01, False@2019-09-02}')), + (tps, TBoolSeqSet('{[True@2019-09-01], (False@2019-09-01, False@2019-09-02]}')), + (tpss, TBoolSeqSet('{[True@2019-09-01], (False@2019-09-01, False@2019-09-02],' + '[True@2019-09-03, True@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_intersects_disjoint_touches(self, temporal, expected): + assert temporal.intersects(Point(1,1)) == expected + assert temporal.disjoint(Point(1,1)) == ~expected + # assert temporal.touches(Point(1,1)) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, Point(1,1), TBoolInst('True@2019-09-01')), + (tpds, Point(1,1), TBoolSeq('{True@2019-09-01, True@2019-09-02}')), + (tps, Point(1,1), TBoolSeqSet('{[True@2019-09-01, True@2019-09-02]}')), + (tpss, Point(1,1), TBoolSeqSet('{[True@2019-09-01, True@2019-09-02],' + '[True@2019-09-03, True@2019-09-05]}')), + + (tpi, TGeomPointInst('Point(1 1)@2019-09-01'), TBoolInst('True@2019-09-01')), + (tpds, TGeomPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-02}'), + TBoolSeq('{True@2019-09-01, True@2019-09-02}')), + (tps, TGeomPointSeq('[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02]'), + TBoolSeqSet('{[True@2019-09-01, True@2019-09-02]}')), + (tpss, + TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02],' + '[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}'), + TBoolSeqSet('{[True@2019-09-01, True@2019-09-02],' + '[True@2019-09-03, True@2019-09-05]}')), + ], + ids=['Instant Geo', 'Discrete Sequence Geo', 'Sequence Geo', 'SequenceSet Geo', + 'Instant TPoint', 'Discrete Sequence TPoint', 'Sequence TPoint', 'SequenceSet TPoint'] + ) + def test_temporal_withindist(self, temporal, argument, expected): + assert temporal.within_distance(argument, 2) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TBoolInst('True@2019-09-01')), + (tpds, TBoolSeq('{True@2019-09-01, True@2019-09-02}')), + (tps, TBoolSeqSet('{[True@2019-09-01, True@2019-09-02]}')), + (tpss, TBoolSeqSet('{[True@2019-09-01, True@2019-09-02],' + '[True@2019-09-03, True@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_contained(self, temporal, expected): + assert temporal.is_spatially_contained_in(Polygon([(0,0),(0,3),(3,3),(3,0),(0,0)])) == expected + assert temporal.is_spatially_contained_in(STBox('STBOX X((0,0),(3,3))')) == expected + + +class TestTGeomPointDistanceOperations(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, Point(1,1), TFloatInst('0@2019-09-01')), + (tpds, Point(1,1), TFloatSeq('{0@2019-09-01, 1.414@2019-09-02}')), + (tps, Point(1,1), TFloatSeq('[0@2019-09-01, 1.414@2019-09-02]')), + (tpss, Point(1,1), TFloatSeqSet('{[0@2019-09-01, 1.414@2019-09-02],' + '[0@2019-09-03, 0@2019-09-05]}')), + + (tpi, STBox('STBOX X((1,1),(1,1))'), TFloatInst('0@2019-09-01')), + (tpds, STBox('STBOX X((1,1),(1,1))'), TFloatSeq('{0@2019-09-01, 1.414@2019-09-02}')), + (tps, STBox('STBOX X((1,1),(1,1))'), TFloatSeq('[0@2019-09-01, 1.414@2019-09-02]')), + (tpss, STBox('STBOX X((1,1),(1,1))'), TFloatSeqSet('{[0@2019-09-01, 1.414@2019-09-02],' + '[0@2019-09-03, 0@2019-09-05]}')), + + (tpi, TGeomPointInst('Point(1 1)@2019-09-01'), TFloatInst('0@2019-09-01')), + (tpds, TGeomPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-02}'), + TFloatSeq('{0@2019-09-01, 1.414@2019-09-02}')), + (tps, TGeomPointSeq('[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02]'), + TFloatSeq('[0@2019-09-01, 1.414@2019-09-02]')), + (tpss, + TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02],' + '[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}'), + TFloatSeqSet('{[0@2019-09-01, 1.414@2019-09-02],' + '[0@2019-09-03, 0@2019-09-05]}')), + ], + ids=['Instant Geo', 'Discrete Sequence Geo', 'Sequence Geo', 'SequenceSet Geo', + 'Instant STBox', 'Discrete Sequence STBox', 'Sequence STBox', 'SequenceSet STBox', + 'Instant TPoint', 'Discrete Sequence TPoint', 'Sequence TPoint', 'SequenceSet TPoint'] + ) + def test_distance(self, temporal, argument, expected): + assert temporal.distance(argument).round(3) == expected + assert temporal.nearest_approach_distance(argument) == 0.0 + + @pytest.mark.parametrize( + 'temporal, argument', + [ + (tpi, Point(1,1)), + (tpds, Point(1,1)), + (tps, Point(1,1)), + (tpss, Point(1,1)), + + (tpi, TGeomPointInst('Point(1 1)@2019-09-01')), + (tpds, TGeomPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-02}')), + (tps, TGeomPointSeq('[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02]')), + (tpss, + TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02],' + '[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + ], + ids=['Instant Geo', 'Discrete Sequence Geo', 'Sequence Geo', 'SequenceSet Geo', + 'Instant TPoint', 'Discrete Sequence TPoint', 'Sequence TPoint', 'SequenceSet TPoint'] + ) + def test_nearest_approach_instant(self, temporal, argument): + assert temporal.nearest_approach_instant(argument) == TGeomPointInst('Point(1 1)@2019-09-01') + + @pytest.mark.parametrize( + 'temporal, argument', + [ + (tpi, Point(1,1)), + (tpds, Point(1,1)), + (tps, Point(1,1)), + (tpss, Point(1,1)), + + (tpi, TGeomPointInst('Point(1 1)@2019-09-01')), + (tpds, TGeomPointSeq('{Point(1 1)@2019-09-01, Point(1 1)@2019-09-02}')), + (tps, TGeomPointSeq('[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02]')), + (tpss, + TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(1 1)@2019-09-02],' + '[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}')), + ], + ids=['Instant Geo', 'Discrete Sequence Geo', 'Sequence Geo', 'SequenceSet Geo', + 'Instant TPoint', 'Discrete Sequence TPoint', 'Sequence TPoint', 'SequenceSet TPoint'] + ) + def test_shortest_line(self, temporal, argument): + assert temporal.shortest_line(argument) == LineString([(1,1), (1,1)]) + + +class TestTGeomPointSplitOperations(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + # The PyMEOS function uses as default origin the initial timestamp of the + # temporal value while in MEOS the default origin is Monday Janury 3, 2000 + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, [TGeomPointInst('Point(1 1)@2019-09-01')]), + (tpds, [TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}')]), + (tps, [TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]')]), + (tpss, [TGeomPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-02]'), + TGeomPointSeq('[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_time_split(self, temporal, expected): + assert temporal.time_split(timedelta(days=2)) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, [TGeomPointInst('Point(1 1)@2019-09-01')]), + (tpds, [TGeomPointSeq('{Point(1 1)@2019-09-01}'), + TGeomPointSeq('{Point(2 2)@2019-09-02}')]), + (tps, [TGeomPointSeq('[Point(1 1)@2019-09-01, Point(1.5 1.5)@2019-09-01 12:00:00+00)'), + TGeomPointSeq('[Point(1.5 1.5)@2019-09-01 12:00:00+00, Point(2 2)@2019-09-02]')]), + (tpss, [TGeomPointSeq('[Point(1 1)@2019-09-01,Point(2 2)@2019-09-02]'), + TGeomPointSeq('[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_time_split_n(self, temporal, expected): + assert temporal.time_split_n(2) == expected + + +class TestTGeomPointComparisons(TestTGeomPoint): + tp = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + other = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + def test_eq(self): + _ = self.tp == self.other + + def test_ne(self): + _ = self.tp != self.other + + def test_lt(self): + _ = self.tp < self.other + + def test_le(self): + _ = self.tp <= self.other + + def test_gt(self): + _ = self.tp > self.other + + def test_ge(self): + _ = self.tp >= self.other + + +class TestTGeomPointEverAlwaysComparisons(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02], [Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, Point(1,1), True), + (tpi, Point(2,2), False), + (tpds, Point(1,1), False), + (tpds, Point(2,2), False), + (tps, Point(1,1), False), + (tps, Point(2,2), False), + (tpss, Point(1,1), False), + (tpss, Point(2,2), False), + ], + ids=['Instant Point(1,1)', 'Instant Point(2,2)', 'Discrete Sequence Point(1,1)', 'Discrete Sequence Point(2,2)', + 'Sequence Point(1,1)', 'Sequence Point(2,2)', 'SequenceSet Point(1,1)', 'SequenceSet Point(2,2)'] + ) + def test_always_equal_ever_not_equal(self, temporal, argument, expected): + assert temporal.always_equal(argument) == expected + assert temporal.never_not_equal(argument) == expected + assert temporal.ever_not_equal(argument) == not_(expected) + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, Point(1,1), True), + (tpi, Point(2,2), False), + (tpds, Point(1,1), True), + (tpds, Point(2,2), True), + (tps, Point(1,1), True), + (tps, Point(2,2), True), + (tpss, Point(1,1), True), + (tpss, Point(2,2), True), + ], + ids=['Instant Point(1,1)', 'Instant Point(2,2)', 'Discrete Sequence Point(1,1)', 'Discrete Sequence Point(2,2)', + 'Sequence Point(1,1)', 'Sequence Point(2,2)', 'SequenceSet Point(1,1)', 'SequenceSet Point(2,2)'] + ) + def test_ever_equal_always_not_equal(self, temporal, argument, expected): + assert temporal.ever_equal(argument) == expected + assert temporal.always_not_equal(argument) == not_(expected) + assert temporal.never_equal(argument) == not_(expected) + + +class TestTGeomPointTemporalComparisons(TestTGeomPoint): + tpi = TGeomPointInst('Point(1 1)@2019-09-01') + tpds = TGeomPointSeq('{Point(1 1)@2019-09-01, Point(2 2)@2019-09-02}') + tps = TGeomPointSeq('[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02]') + tpss = TGeomPointSeqSet('{[Point(1 1)@2019-09-01, Point(2 2)@2019-09-02],[Point(1 1)@2019-09-03, Point(1 1)@2019-09-05]}') + argument = TGeomPointSeq('[Point(2 2)@2019-09-01, Point(1 1)@2019-09-02, Point(1 1)@2019-09-03]') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tpi, TBoolInst('False@2019-09-01')), + (tpds, TBoolSeq('{False@2019-09-01, False@2019-09-02}')), + (tps, TBoolSeqSet('{[False@2019-09-01, True@2019-09-01 12:00:00+00],' + '(False@2019-09-01 12:00:00+00, False@2019-09-02]}')), + (tpss, TBoolSeqSet('{[False@2019-09-01, True@2019-09-01 12:00:00+00],' + '(False@2019-09-01 12:00:00+00, False@2019-09-02],[True@2019-09-03]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_equal_temporal(self, temporal, expected): + assert temporal.temporal_equal(self.argument) == expected + assert temporal.temporal_not_equal(self.argument) == expected.temporal_not() + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tpi, Point(1,1), TBoolInst('True@2019-09-01')), + (tpds, Point(1,1), TBoolSeq('{True@2019-09-01, False@2019-09-02}')), + (tps, Point(1,1), TBoolSeqSet('{[True@2019-09-01], (False@2019-09-01, False@2019-09-02]}')), + (tpss, Point(1,1), TBoolSeqSet('{[True@2019-09-01], (False@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}')), + + (tpi, Point(2,2), TBoolInst('False@2019-09-01')), + (tpds, Point(2,2), TBoolSeq('{False@2019-09-01, True@2019-09-02}')), + (tps, Point(2,2), TBoolSeq('[False@2019-09-01, True@2019-09-02]')), + (tpss, Point(2,2), TBoolSeqSet('{[False@2019-09-01, True@2019-09-02],[False@2019-09-03, False@2019-09-05]}')), + ], + ids=['Instant Point(1,1)', 'Discrete Sequence Point(1,1)', 'Sequence Point(1,1)', 'SequenceSet Point(1,1)', + 'Instant Point(2,2)', 'Discrete Sequence Point(2,2)', 'Sequence Point(2,2)', 'SequenceSet Point(2,2)'] + ) + def test_temporal_equal_point(self, temporal, argument, expected): + assert temporal.temporal_equal(argument) == expected + assert temporal.temporal_not_equal(argument) == expected.temporal_not() + + diff --git a/pymeos/tests/main/tint_test.py b/pymeos/tests/main/tint_test.py new file mode 100644 index 00000000..28a29281 --- /dev/null +++ b/pymeos/tests/main/tint_test.py @@ -0,0 +1,1903 @@ +from copy import copy +from operator import not_ +from datetime import datetime, timezone, timedelta +from spans.types import intrange, floatrange + +import pytest + +from pymeos import TBool, TBoolInst, TBoolSeq, TBoolSeqSet, TFloat, TFloatInst, TFloatSeq, TFloatSeqSet, TInt, \ + TIntInst, TIntSeq, TIntSeqSet, TInterpolation, TBox, TimestampSet, Period, PeriodSet +from tests.conftest import TestPyMEOS + + +class TestTInt(TestPyMEOS): + pass + + +class TestTIntConstructors(TestTInt): + tii = TIntInst('1@2019-09-01') + tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') + tis = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + tiss = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}') + + @pytest.mark.parametrize( + 'source, type, interpolation', + [ + (TFloatInst('1.5@2019-09-01'), TIntInst, TInterpolation.NONE), + (TFloatSeq('{1.5@2019-09-01, 0.5@2019-09-02}'), TIntSeq, TInterpolation.DISCRETE), + (TFloatSeq('[1.5@2019-09-01, 0.5@2019-09-02]'), TIntSeq, TInterpolation.STEPWISE), + (TFloatSeqSet('{[1.5@2019-09-01, 0.5@2019-09-02],[1.5@2019-09-03, 1.5@2019-09-05]}'), + TIntSeqSet, TInterpolation.STEPWISE) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_from_base_constructor(self, source, type, interpolation): + ti = TInt.from_base_temporal(1, source) + assert isinstance(ti, type) + assert ti.interpolation() == interpolation + + @pytest.mark.parametrize( + 'source, type, interpolation', + [ + (datetime(2000, 1, 1), TIntInst, TInterpolation.NONE), + (TimestampSet('{2019-09-01, 2019-09-02}'), TIntSeq, TInterpolation.DISCRETE), + (Period('[2019-09-01, 2019-09-02]'), TIntSeq, TInterpolation.STEPWISE), + (PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}'), TIntSeqSet, TInterpolation.STEPWISE) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_from_base_time_constructor(self, source, type, interpolation): + ti = TInt.from_base_time(1, source) + assert isinstance(ti, type) + assert ti.interpolation() == interpolation + + @pytest.mark.parametrize( + 'source, type, interpolation, expected', + [ + ('1@2019-09-01', TIntInst, TInterpolation.NONE, '1@2019-09-01 00:00:00+00'), + ('{1@2019-09-01, 2@2019-09-02}', TIntSeq, TInterpolation.DISCRETE, + '{1@2019-09-01 00:00:00+00, 2@2019-09-02 00:00:00+00}'), + ('[1@2019-09-01, 2@2019-09-02]', TIntSeq, TInterpolation.STEPWISE, + '[1@2019-09-01 00:00:00+00, 2@2019-09-02 00:00:00+00]'), + ('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}', TIntSeqSet, + TInterpolation.STEPWISE, '{[1@2019-09-01 00:00:00+00, 2@2019-09-02 00:00:00+00], ' + '[1@2019-09-03 00:00:00+00, 1@2019-09-05 00:00:00+00]}'), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_string_constructor(self, source, type, interpolation, expected): + ti = type(source) + assert isinstance(ti, type) + assert ti.interpolation() == interpolation + assert str(ti) == expected + + @pytest.mark.parametrize( + 'source, type, expected', + [ + ('[1@2019-09-01, 1@2019-09-02, 1@2019-09-03, 2@2019-09-05]', TIntSeq, + '[1@2019-09-01 00:00:00+00, 2@2019-09-05 00:00:00+00]'), + ('{[1@2019-09-01, 1@2019-09-02, 1@2019-09-03, 2@2019-09-05],' + '[1@2019-09-07, 1@2019-09-08, 1@2019-09-09]}', TIntSeqSet, + '{[1@2019-09-01 00:00:00+00, 2@2019-09-05 00:00:00+00], ' + '[1@2019-09-07 00:00:00+00, 1@2019-09-09 00:00:00+00]}'), + ], + ids=['Sequence', 'SequenceSet'] + ) + def test_string_constructor_normalization(self, source, type, expected): + ti = type(source, normalize=1) + assert isinstance(ti, type) + assert str(ti) == expected + + @pytest.mark.parametrize( + 'value, timestamp', + [ + (1, datetime(2019, 9, 1, tzinfo=timezone.utc)), + ('1', datetime(2019, 9, 1, tzinfo=timezone.utc)), + (1, '2019-09-01'), + ('1', '2019-09-01'), + ], + ids=['int-datetime', 'string-datetime', 'int-string', 'string-string'] + ) + def test_value_timestamp_instant_constructor(self, value, timestamp): + tii = TIntInst(value=value, timestamp=timestamp) + assert str(tii) == '1@2019-09-01 00:00:00+00' + + @pytest.mark.parametrize( + 'list, interpolation, normalize, expected', + [ + (['1@2019-09-01', '2@2019-09-03'], TInterpolation.DISCRETE, False, + '{1@2019-09-01 00:00:00+00, 2@2019-09-03 00:00:00+00}'), + (['1@2019-09-01', '2@2019-09-03'], TInterpolation.STEPWISE, False, + '[1@2019-09-01 00:00:00+00, 2@2019-09-03 00:00:00+00]'), + ([TIntInst('1@2019-09-01'), TIntInst('2@2019-09-03')], TInterpolation.DISCRETE, False, + '{1@2019-09-01 00:00:00+00, 2@2019-09-03 00:00:00+00}'), + ([TIntInst('1@2019-09-01'), TIntInst('2@2019-09-03')], TInterpolation.STEPWISE, False, + '[1@2019-09-01 00:00:00+00, 2@2019-09-03 00:00:00+00]'), + (['1@2019-09-01', TIntInst('2@2019-09-03')], TInterpolation.DISCRETE, False, + '{1@2019-09-01 00:00:00+00, 2@2019-09-03 00:00:00+00}'), + (['1@2019-09-01', TIntInst('2@2019-09-03')], TInterpolation.STEPWISE, False, + '[1@2019-09-01 00:00:00+00, 2@2019-09-03 00:00:00+00]'), + + (['1@2019-09-01', '1@2019-09-02', '2@2019-09-03'], TInterpolation.STEPWISE, True, + '[1@2019-09-01 00:00:00+00, 2@2019-09-03 00:00:00+00]'), + ([TIntInst('1@2019-09-01'), TIntInst('1@2019-09-02'), TIntInst('2@2019-09-03')], + TInterpolation.STEPWISE, True, + '[1@2019-09-01 00:00:00+00, 2@2019-09-03 00:00:00+00]'), + (['1@2019-09-01', '1@2019-09-02', TIntInst('2@2019-09-03')], TInterpolation.STEPWISE, True, + '[1@2019-09-01 00:00:00+00, 2@2019-09-03 00:00:00+00]'), + ], + ids=['String Discrete', 'String Stepwise', 'TIntInst Discrete', 'TIntInst Stepwise', 'Mixed Discrete', + 'Mixed Stepwise', 'String Stepwise Normalized', 'TIntInst Stepwise Normalized', + 'Mixed Stepwise Normalized'] + ) + def test_instant_list_sequence_constructor(self, list, interpolation, normalize, expected): + tis = TIntSeq(instant_list=list, interpolation=interpolation, normalize=normalize, upper_inc=True) + assert str(tis) == expected + assert tis.interpolation() == interpolation + + tis2 = TIntSeq.from_instants(list, interpolation=interpolation, normalize=normalize, upper_inc=True) + assert str(tis2) == expected + assert tis2.interpolation() == interpolation + + @pytest.mark.parametrize( + 'temporal', + [tii, tids, tis, tiss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_from_as_constructor(self, temporal): + assert temporal == temporal.from_wkb(temporal.as_wkb()) + assert temporal == temporal.from_hexwkb(temporal.as_hexwkb()) + assert temporal == temporal.from_mfjson(temporal.as_mfjson()) + + @pytest.mark.parametrize( + 'temporal', + [tii, tids, tis, tiss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_copy_constructor(self, temporal): + other = copy(temporal) + assert temporal == other + assert temporal is not other + + +class TestTIntOutputs(TestTInt): + tii = TIntInst('1@2019-09-01') + tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') + tis = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + tiss = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, '1@2019-09-01 00:00:00+00'), + (tids, '{1@2019-09-01 00:00:00+00, 2@2019-09-02 00:00:00+00}'), + (tis, '[1@2019-09-01 00:00:00+00, 2@2019-09-02 00:00:00+00]'), + (tiss, '{[1@2019-09-01 00:00:00+00, 2@2019-09-02 00:00:00+00], ' + '[1@2019-09-03 00:00:00+00, 1@2019-09-05 00:00:00+00]}') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_str(self, temporal, expected): + assert str(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, 'TIntInst(1@2019-09-01 00:00:00+00)'), + (tids, 'TIntSeq({1@2019-09-01 00:00:00+00, 2@2019-09-02 00:00:00+00})'), + (tis, 'TIntSeq([1@2019-09-01 00:00:00+00, 2@2019-09-02 00:00:00+00])'), + (tiss, 'TIntSeqSet({[1@2019-09-01 00:00:00+00, 2@2019-09-02 00:00:00+00], ' + '[1@2019-09-03 00:00:00+00, 1@2019-09-05 00:00:00+00]})') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_repr(self, temporal, expected): + assert repr(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, '1@2019-09-01 00:00:00+00'), + (tids, '{1@2019-09-01 00:00:00+00, 2@2019-09-02 00:00:00+00}'), + (tis, '[1@2019-09-01 00:00:00+00, 2@2019-09-02 00:00:00+00]'), + (tiss, '{[1@2019-09-01 00:00:00+00, 2@2019-09-02 00:00:00+00], ' + '[1@2019-09-03 00:00:00+00, 1@2019-09-05 00:00:00+00]}') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_wkt(self, temporal, expected): + assert temporal.as_wkt() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, '011D00010100000000A01E4E71340200'), + (tids, '011D000602000000030100000000A01E4E71340200020000000000F66B85340200'), + (tis, '011D000A02000000030100000000A01E4E71340200020000000000F66B85340200'), + (tiss, '011D000B0200000002000000030100000000A01E4E71340200020000000000F66B85340200' + '0200000003010000000060CD89993402000100000000207CC5C1340200') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_hexwkb(self, temporal, expected): + assert temporal.as_hexwkb() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, '{\n' + ' "type": "MovingInteger",\n' + ' "bbox": [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-01T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "values": [\n' + ' 1\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00"\n' + ' ],\n' + ' "interpolation": "None"\n' + ' }'), + (tids, '{\n' + ' "type": "MovingInteger",\n' + ' "bbox": [\n' + ' 1,\n' + ' 2\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-02T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "values": [\n' + ' 1,\n' + ' 2\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true,\n' + ' "interpolation": "Discrete"\n' + ' }'), + (tis, '{\n' + ' "type": "MovingInteger",\n' + ' "bbox": [\n' + ' 1,\n' + ' 2\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-02T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "values": [\n' + ' 1,\n' + ' 2\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true,\n' + ' "interpolation": "Step"\n' + ' }'), + (tiss, '{\n' + ' "type": "MovingInteger",\n' + ' "bbox": [\n' + ' 1,\n' + ' 2\n' + ' ],\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-05T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "sequences": [\n' + ' {\n' + ' "values": [\n' + ' 1,\n' + ' 2\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' {\n' + ' "values": [\n' + ' 1,\n' + ' 1\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-03T00:00:00+00",\n' + ' "2019-09-05T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' }\n' + ' ],\n' + ' "interpolation": "Step"\n' + ' }') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_mfjson(self, temporal, expected): + assert temporal.as_mfjson() == expected + + +class TestTIntAccessors(TestTInt): + tii = TIntInst('1@2019-09-01') + tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') + tis = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + tiss = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, TInterpolation.NONE), + (tids, TInterpolation.DISCRETE), + (tis, TInterpolation.STEPWISE), + (tiss, TInterpolation.STEPWISE) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_interpolation(self, temporal, expected): + assert temporal.interpolation() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, {1}), + (tids, {1, 2}), + (tis, {1, 2}), + (tiss, {1, 2}) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_set(self, temporal, expected): + assert temporal.value_set() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, [1]), + (tids, [1, 2]), + (tis, [1, 2]), + (tiss, [1, 2, 1, 1]) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_values(self, temporal, expected): + assert temporal.values() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, 1), + (tids, 1), + (tis, 1), + (tiss, 1) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_value(self, temporal, expected): + assert temporal.start_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, 1), + (tids, 2), + (tis, 2), + (tiss, 1) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_value(self, temporal, expected): + assert temporal.end_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, 1), + (tids, 1), + (tis, 1), + (tiss, 1) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_min_value(self, temporal, expected): + assert temporal.min_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, 1), + (tids, 2), + (tis, 2), + (tiss, 2) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_max_value(self, temporal, expected): + assert temporal.max_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, 1), + (tids, 1), + (tis, 1), + (tiss, 1) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_at_timestamp(self, temporal, expected): + assert temporal.value_at_timestamp(datetime(2019, 9, 1)) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, PeriodSet('{[2019-09-01, 2019-09-01]}')), + (tids, PeriodSet('{[2019-09-01, 2019-09-01], [2019-09-02, 2019-09-02]}')), + (tis, PeriodSet('{[2019-09-01, 2019-09-02]}')), + (tiss, PeriodSet('{[2019-09-01, 2019-09-02], [2019-09-03, 2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_time(self, temporal, expected): + assert temporal.time() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, timedelta()), + (tids, timedelta()), + (tis, timedelta(days=1)), + (tiss, timedelta(days=3)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_duration(self, temporal, expected): + assert temporal.duration() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, timedelta()), + (tids, timedelta(days=1)), + (tis, timedelta(days=1)), + (tiss, timedelta(days=4)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_duration_ignoring_gaps(self, temporal, expected): + assert temporal.duration(ignore_gaps=True) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, Period('[2019-09-01, 2019-09-01]')), + (tids, Period('[2019-09-01, 2019-09-02]')), + (tis, Period('[2019-09-01, 2019-09-02]')), + (tiss, Period('[2019-09-01, 2019-09-05]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_period(self, temporal, expected): + assert temporal.period() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, Period('[2019-09-01, 2019-09-01]')), + (tids, Period('[2019-09-01, 2019-09-02]')), + (tis, Period('[2019-09-01, 2019-09-02]')), + (tiss, Period('[2019-09-01, 2019-09-05]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timespan(self, temporal, expected): + assert temporal.timespan() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, 1), + (tids, 2), + (tis, 2), + (tiss, 4), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_num_instants(self, temporal, expected): + assert temporal.num_instants() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, tii), + (tids, tii), + (tis, tii), + (tiss, tii), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_instant(self, temporal, expected): + assert temporal.start_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, tii), + (tids, TIntInst('2@2019-09-02')), + (tis, TIntInst('2@2019-09-02')), + (tiss, TIntInst('1@2019-09-05')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_instant(self, temporal, expected): + assert temporal.end_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, tii), + (tids, TIntInst('2@2019-09-02')), + (tis, TIntInst('2@2019-09-02')), + (tiss, TIntInst('2@2019-09-02')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_max_instant(self, temporal, expected): + assert temporal.max_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, tii), + (tids, tii), + (tis, tii), + (tiss, tii), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_min_instant(self, temporal, expected): + assert temporal.min_instant() == expected + + @pytest.mark.parametrize( + 'temporal, n, expected', + [ + (tii, 0, tii), + (tids, 1, TIntInst('2@2019-09-02')), + (tis, 1, TIntInst('2@2019-09-02')), + (tiss, 2, TIntInst('1@2019-09-03')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_instant_n(self, temporal, n, expected): + assert temporal.instant_n(n) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, [tii]), + (tids, [tii, TIntInst('2@2019-09-02')]), + (tis, [tii, TIntInst('2@2019-09-02')]), + (tiss, [tii, TIntInst('2@2019-09-02'), TIntInst('1@2019-09-03'), TIntInst('1@2019-09-05')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_instants(self, temporal, expected): + assert temporal.instants() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, 1), + (tids, 2), + (tis, 2), + (tiss, 4), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_num_timestamps(self, temporal, expected): + assert temporal.num_timestamps() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tids, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tis, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tiss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_timestamp(self, temporal, expected): + assert temporal.start_timestamp() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tids, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tis, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tiss, datetime(year=2019, month=9, day=5, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_timestamp(self, temporal, expected): + assert temporal.end_timestamp() == expected + + @pytest.mark.parametrize( + 'temporal, n, expected', + [ + (tii, 0, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tids, 1, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tis, 1, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tiss, 2, datetime(year=2019, month=9, day=3, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timestamp_n(self, temporal, n, expected): + assert temporal.timestamp_n(n) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)]), + (tids, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)]), + (tis, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)]), + (tiss, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=3, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=5, tzinfo=timezone.utc)]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timestamps(self, temporal, expected): + assert temporal.timestamps() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tids, [TIntSeq('[1@2019-09-01]'), TIntSeq('[2@2019-09-02]')]), + (tis, [TIntSeq('[1@2019-09-01, 1@2019-09-02)'), + TIntSeq('[2@2019-09-02]')]), + (tiss, + [TIntSeq('[1@2019-09-01, 1@2019-09-02)'), + TIntSeq('[2@2019-09-02]'), + TIntSeq('[1@2019-09-03, 1@2019-09-05]')]), + ], + ids=['Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_segments(self, temporal, expected): + assert temporal.segments() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tids, True), + (tis, True), + ], + ids=['Discrete Sequence', 'Sequence'] + ) + def test_lower_upper_inc(self, temporal, expected): + assert temporal.lower_inc() == expected + assert temporal.upper_inc() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, 440045287), + (tids, 3589664982), + (tis, 3589664982), + (tiss, 205124107) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_hash(self, temporal, expected): + assert hash(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, TBox('TBOX XT([1,1],[2019-09-01, 2019-09-01])')), + (tids, TBox('TBOX XT([1,2],[2019-09-01, 2019-09-02])')), + (tis, TBox('TBOX XT([1,2],[2019-09-01, 2019-09-02])')), + (tiss, TBox('TBOX XT([1,2],[2019-09-01, 2019-09-05])')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_bounding_box(self, temporal, expected): + assert temporal.bounding_box() == expected + + +class TestTIntTransformations(TestTInt): + tii = TIntInst('1@2019-09-01') + tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') + tis = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + tiss = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TIntInst('1@2019-09-01'), tii), + (TIntSeq('{1@2019-09-01}'), tii), + (TIntSeq('[1@2019-09-01]'), tii), + (TIntSeqSet('{[1@2019-09-01]}'), tii), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_instant(self, temporal, expected): + temp = temporal.to_instant() + assert isinstance(temp, TIntInst) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TIntInst('1@2019-09-01'), + TIntSeq('[1@2019-09-01]')), + (TIntSeq('{1@2019-09-01, 2@2019-09-02}'), + TIntSeq('{1@2019-09-01, 2@2019-09-02}')), + (TIntSeq('[1@2019-09-01, 2@2019-09-02]'), + TIntSeq('[1@2019-09-01, 2@2019-09-02]')), + (TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}'), + TIntSeq('[1@2019-09-01, 2@2019-09-02]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_sequence(self, temporal, expected): + temp = temporal.to_sequence() + assert isinstance(temp, TIntSeq) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TIntInst('1@2019-09-01'), + TIntSeqSet('{[1@2019-09-01]}')), + (TIntSeq('{1@2019-09-01, 2@2019-09-02}'), + TIntSeqSet('{[1@2019-09-01], [2@2019-09-02]}')), + (TIntSeq('[1@2019-09-01, 2@2019-09-02]'), + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}')), + (TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}'), + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_sequenceset(self, temporal, expected): + temp = temporal.to_sequenceset() + assert isinstance(temp, TIntSeqSet) + assert temp == expected + + @pytest.mark.parametrize( + 'tint, delta, expected', + [(tii, timedelta(days=4), TIntInst('1@2019-09-05')), + (tii, timedelta(days=-4), TIntInst('1@2019-08-28')), + (tii, timedelta(hours=2), TIntInst('1@2019-09-01 02:00:00')), + (tii, timedelta(hours=-2), TIntInst('1@2019-08-31 22:00:00')), + (tids, timedelta(days=4), TIntSeq('{1@2019-09-05, 2@2019-09-06}')), + (tids, timedelta(days=-4), TIntSeq('{1@2019-08-28, 2@2019-08-29}')), + (tids, timedelta(hours=2), TIntSeq('{1@2019-09-01 02:00:00, 2@2019-09-02 02:00:00}')), + (tids, timedelta(hours=-2), TIntSeq('{1@2019-08-31 22:00:00, 2@2019-09-01 22:00:00}')), + (tis, timedelta(days=4), TIntSeq('[1@2019-09-05, 2@2019-09-06]')), + (tis, timedelta(days=-4), TIntSeq('[1@2019-08-28, 2@2019-08-29]')), + (tis, timedelta(hours=2), TIntSeq('[1@2019-09-01 02:00:00, 2@2019-09-02 02:00:00]')), + (tis, timedelta(hours=-2), TIntSeq('[1@2019-08-31 22:00:00, 2@2019-09-01 22:00:00]')), + (tiss, timedelta(days=4), + TIntSeqSet('{[1@2019-09-05, 2@2019-09-06],[1@2019-09-07, 1@2019-09-09]}')), + (tiss, timedelta(days=-4), + TIntSeqSet('{[1@2019-08-28, 2@2019-08-29],[1@2019-08-30, 1@2019-09-01]}')), + (tiss, timedelta(hours=2), + TIntSeqSet('{[1@2019-09-01 02:00:00, 2@2019-09-02 02:00:00],' + '[1@2019-09-03 02:00:00, 1@2019-09-05 02:00:00]}')), + (tiss, timedelta(hours=-2), + TIntSeqSet('{[1@2019-08-31 22:00:00, 2@2019-09-01 22:00:00],' + '[1@2019-09-02 22:00:00, 1@2019-09-04 22:00:00]}')), + ], + ids=['Instant positive days', 'Instant negative days', + 'Instant positive hours', 'Instant negative hours', + 'Discrete Sequence positive days', 'Discrete Sequence negative days', + 'Discrete Sequence positive hours', 'Discrete Sequence negative hours', + 'Sequence positive days', 'Sequence negative days', + 'Sequence positive hours', 'Sequence negative hours', + 'Sequence Set positive days', 'Sequence Set negative days', + 'Sequence Set positive hours', 'Sequence Set negative hours'] + ) + def test_shift(self, tint, delta, expected): + assert tint.shift(delta) == expected + + @pytest.mark.parametrize( + 'tint, delta, expected', + [(tii, timedelta(days=4), TIntInst('1@2019-09-01')), + (tii, timedelta(hours=2), TIntInst('1@2019-09-01')), + (tids, timedelta(days=4), TIntSeq('{1@2019-09-01, 2@2019-09-05}')), + (tids, timedelta(hours=2), TIntSeq('{1@2019-09-01 00:00:00, 2@2019-09-01 02:00:00}')), + (tis, timedelta(days=4), TIntSeq('[1@2019-09-01, 2@2019-09-05]')), + (tis, timedelta(hours=2), TIntSeq('[1@2019-09-01 00:00:00, 2@2019-09-01 02:00:00]')), + (tiss, timedelta(days=4), + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), + (tiss, timedelta(hours=2), + TIntSeqSet('{[1@2019-09-01 00:00:00, 2@2019-09-01 00:30:00],' + '[1@2019-09-01 01:00:00, 1@2019-09-01 02:00:00]}')), + ], + ids=['Instant positive days', 'Instant positive hours', + 'Discrete Sequence positive days', 'Discrete Sequence positive hours', + 'Sequence positive days', 'Sequence positive hours', + 'Sequence Set positive days', 'Sequence Set positive hours'] + ) + def test_scale(self, tint, delta, expected): + assert tint.tscale(delta) == expected + + def test_shift_tscale(self): + assert self.tiss.shift_tscale(timedelta(days=4), timedelta(hours=2)) == \ + TIntSeqSet('{[1@2019-09-05 00:00:00, 2@2019-09-05 00:30:00],' + '[1@2019-09-05 01:00:00, 1@2019-09-05 02:00:00]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, TFloatInst('1@2019-09-01')), + (tids, TFloatSeq('{1@2019-09-01, 2@2019-09-02}')), + (tis, TFloatSeq('Interp=Step;[1@2019-09-01, 2@2019-09-02]')), + # (tiss, TFloatSeqSet('Interp=Step;{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence'] #, 'SequenceSet'] + ) + def test_to_tfloat(self, temporal, expected): + assert temporal.to_tfloat() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, intrange(1, 1, True, True)), + (tids, intrange(1, 2, True, True)), + (tis, intrange(1, 2, True, True)), + (tiss, intrange(1, 2, True, True)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_intrange(self, temporal, expected): + assert temporal.to_intrange() == expected + + +class TestTIntModifications(TestTInt): + tii = TIntInst('1@2019-09-01') + tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') + tis = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + tiss = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, sequence, expected', + [ + (tii, TIntSeq('{1@2019-09-03}'), TIntSeq('{1@2019-09-01, 1@2019-09-03}')), + (tids, TIntSeq('{1@2019-09-03}'), TIntSeq('{1@2019-09-01, 2@2019-09-02, 1@2019-09-03}')), + (tis, TIntSeq('[1@2019-09-03]'), TIntSeqSet('{[1@2019-09-01, 2@2019-09-02, 1@2019-09-03]}')), + (tiss, TIntSeq('[1@2019-09-06]'), + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05],[1@2019-09-06]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_insert(self, temporal, sequence, expected): + assert temporal.insert(sequence) == expected + + @pytest.mark.parametrize( + 'temporal, instant, expected', + [ + (tii, TIntInst('2@2019-09-01'), TIntInst('2@2019-09-01')), + (tids, TIntInst('2@2019-09-01'), TIntSeq('{2@2019-09-01, 2@2019-09-02}')), + (tis, TIntInst('2@2019-09-01'), + TIntSeqSet('{[2@2019-09-01], (1@2019-09-01, 2@2019-09-02]}')), + (tiss, TIntInst('2@2019-09-01'), + TIntSeqSet('{[2@2019-09-01], (1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_update(self, temporal, instant, expected): + assert temporal.update(instant) == expected + + @pytest.mark.parametrize( + 'temporal, time, expected', + [ + (tii, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), None), + (tii, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), tii), + (tids, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), TIntSeq('{2@2019-09-02}')), + (tis, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + TIntSeqSet('{(1@2019-09-01, 2@2019-09-02]}')), + (tiss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + TIntSeqSet('{(1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), + ], + ids=['Instant intersection', 'Instant disjoint', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_delete(self, temporal, time, expected): + assert temporal.delete(time) == expected + + @pytest.mark.parametrize( + 'temporal, instant, expected', + [ + (tii, TIntInst('1@2019-09-02'), TIntSeq('{1@2019-09-01, 1@2019-09-02}')), + (tids, TIntInst('1@2019-09-03'), TIntSeq('{1@2019-09-01, 2@2019-09-02, 1@2019-09-03}')), + (tis, TIntInst('1@2019-09-03'), TIntSeq('[1@2019-09-01, 2@2019-09-02, 1@2019-09-03]')), + (tiss, TIntInst('1@2019-09-06'), + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-06]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_append_instant(self, temporal, instant, expected): + assert temporal.append_instant(instant) == expected + + @pytest.mark.parametrize( + 'temporal, sequence, expected', + [ + (tids, TIntSeq('{1@2019-09-03}'), TIntSeq('{1@2019-09-01, 2@2019-09-02, 1@2019-09-03}')), + (tis, TIntSeq('[1@2019-09-03]'), TIntSeqSet('{[1@2019-09-01, 2@2019-09-02], [1@2019-09-03]}')), + (tiss, TIntSeq('[1@2019-09-06]'), + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05],[1@2019-09-06]}')), + ], + ids=['Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_append_sequence(self, temporal, sequence, expected): + assert temporal.append_sequence(sequence) == expected + + +class TestTIntMathematicalOperations(TestTInt): + tii = TIntInst('1@2019-09-01') + tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') + tis = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + tiss = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}') + tintarg = TIntSeq('[2@2019-09-01, 1@2019-09-02, 1@2019-09-03]') + tfloatarg = TFloatSeq('[2@2019-09-01, 1@2019-09-02, 1@2019-09-03]') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, tintarg, TIntInst('3@2019-09-01')), + (tids, tintarg, TIntSeq('{3@2019-09-01, 3@2019-09-02}')), + (tis, tintarg, TIntSeq('[3@2019-09-01, 3@2019-09-02]')), + (tiss, tintarg, TIntSeqSet('{[3@2019-09-01, 3@2019-09-02],[2@2019-09-03]}')), + (tii, tfloatarg, TFloatInst('3@2019-09-01')), + (tids, tfloatarg, TFloatSeq('{3@2019-09-01, 3@2019-09-02}')), + (tis, tfloatarg, TFloatSeqSet('{[3@2019-09-01, 2@2019-09-02),[3@2019-09-02]}')), + (tiss, tfloatarg, TFloatSeqSet('{[3@2019-09-01, 2@2019-09-02),[3@2019-09-02],[2@2019-09-03]}')), + ], + ids=['Instant TInt', 'Discrete Sequence TInt', 'Sequence TInt', 'SequenceSet TInt', + 'Instant TFloat', 'Discrete Sequence TFloat', 'Sequence TFloat', 'SequenceSet TFloat'] + ) + def test_temporal_add_temporal(self, temporal, argument, expected): + assert temporal.add(argument) == expected + assert temporal + argument == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, 1, TIntInst('2@2019-09-01')), + (tids, 1, TIntSeq('{2@2019-09-01, 3@2019-09-02}')), + (tis, 1, TIntSeq('[2@2019-09-01, 3@2019-09-02]')), + (tiss, 1, TIntSeqSet('{[2@2019-09-01, 3@2019-09-02],[2@2019-09-03, 2@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_add_int_float(self, temporal, argument, expected): + assert temporal.add(argument) == expected + assert temporal.add(float(argument)) == expected.to_tfloat() + assert temporal.radd(argument) == expected + assert temporal.radd(float(argument)) == expected.to_tfloat() + assert (temporal + argument) == expected + assert (temporal + float(argument)) == expected.to_tfloat() + assert (argument + temporal) == expected + assert (float(argument) + temporal) == expected.to_tfloat() + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, tintarg, TIntInst('-1@2019-09-01')), + (tids, tintarg, TIntSeq('{-1@2019-09-01, 1@2019-09-02}')), + (tis, tintarg, TIntSeq('[-1@2019-09-01, 1@2019-09-02]')), + (tiss, tintarg, TIntSeqSet('{[-1@2019-09-01, 1@2019-09-02],[0@2019-09-03]}')), + (tii, tfloatarg, TFloatInst('-1@2019-09-01')), + (tids, tfloatarg, TFloatSeq('{-1@2019-09-01, 1@2019-09-02}')), + (tis, tfloatarg, TFloatSeqSet('{[-1@2019-09-01, 0@2019-09-02),[1@2019-09-02]}')), + (tiss, tfloatarg, TFloatSeqSet('{[-1@2019-09-01, 0@2019-09-02),[1@2019-09-02],[0@2019-09-03]}')) + ], + ids=['Instant TInt', 'Discrete Sequence TInt', 'Sequence TInt', 'SequenceSet TInt', + 'Instant TFloat', 'Discrete Sequence TFloat', 'Sequence TFloat', 'SequenceSet TFloat'] + ) + def test_temporal_sub_temporal(self, temporal, argument, expected): + assert temporal.sub(argument) == expected + assert temporal - argument == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, 1, TIntInst('0@2019-09-01')), + (tids, 1, TIntSeq('{0@2019-09-01, 1@2019-09-02}')), + (tis, 1, TIntSeq('[0@2019-09-01, 1@2019-09-02]')), + (tiss, 1, TIntSeqSet('{[0@2019-09-01, 1@2019-09-02],[0@2019-09-03, 0@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_sub_int_float(self, temporal, argument, expected): + assert temporal.sub(argument) == expected + assert temporal.sub(float(argument)) == expected.to_tfloat() + assert temporal.rsub(argument) == -1 * expected + assert temporal.rsub(float(argument)) == (-1 * expected).to_tfloat() + assert (temporal - argument) == expected + assert (temporal - float(argument)) == expected.to_tfloat() + assert (argument - temporal) == - 1 * expected + assert (float(argument) - temporal) == (- 1 * expected).to_tfloat() + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, tintarg, TIntInst('2@2019-09-01')), + (tids, tintarg, TIntSeq('{2@2019-09-01, 2@2019-09-02}')), + (tis, tintarg, TIntSeq('[2@2019-09-01, 2@2019-09-02]')), + (tiss, tintarg, TIntSeqSet('{[2@2019-09-01, 2@2019-09-02],[1@2019-09-03]}')), + (tii, tfloatarg, TFloatInst('2@2019-09-01')), + (tids, tfloatarg, TFloatSeq('{2@2019-09-01, 2@2019-09-02}')), + (tis, tfloatarg, TFloatSeqSet('{[2@2019-09-01, 1@2019-09-02), [2@2019-09-02]}')), + (tiss, tfloatarg, TFloatSeqSet('{[2@2019-09-01, 1@2019-09-02), [2@2019-09-02],[1@2019-09-03]}')), + ], + ids=['Instant TInt', 'Discrete Sequence TInt', 'Sequence TInt', 'SequenceSet TInt', + 'Instant TFloat', 'Discrete Sequence TFloat', 'Sequence TFloat', 'SequenceSet TFloat'] + ) + def test_temporal_mul_temporal(self, temporal, argument, expected): + assert temporal.mul(argument) == expected + assert temporal * argument == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, 0, TInt.from_base_temporal(0, tii)), + (tids, 0, TInt.from_base_temporal(0, tids)), + (tis, 0, TInt.from_base_temporal(0, tis)), + (tiss, 0, TInt.from_base_temporal(0, tiss)), + + (tii, 1, tii), + (tids, 1, tids), + (tis, 1, tis), + (tiss, 1, tiss), + + (tii, 2, TIntInst('2@2019-09-01')), + (tids, 2, TIntSeq('{2@2019-09-01, 4@2019-09-02}')), + (tis, 2, TIntSeq('[2@2019-09-01, 4@2019-09-02]')), + (tiss, 2, TIntSeqSet('{[2@2019-09-01, 4@2019-09-02],[2@2019-09-03, 2@2019-09-05]}')), + ], + ids=['Instant 0', 'Discrete Sequence 0', 'Sequence 0', 'SequenceSet 0', + 'Instant 1', 'Discrete Sequence 1', 'Sequence 1', 'SequenceSet 1', + 'Instant 2', 'Discrete Sequence 2', 'Sequence 2', 'SequenceSet 2'] + ) + def test_temporal_mul_int_float(self, temporal, argument, expected): + assert temporal.mul(argument) == expected + assert temporal.mul(float(argument)) == expected.to_tfloat() + assert temporal.rmul(argument) == expected + assert temporal.rmul(float(argument)) == expected.to_tfloat() + assert (temporal * argument) == expected + assert (temporal * float(argument)) == expected.to_tfloat() + assert (argument * temporal) == expected + assert (float(argument) * temporal) == expected.to_tfloat() + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, tintarg, TIntInst('0@2019-09-01')), + (tids, tintarg, TIntSeq('{0@2019-09-01, 2@2019-09-02}')), + (tis, tintarg, TIntSeq('[0@2019-09-01, 2@2019-09-02]')), + (tiss, tintarg, TIntSeqSet('{[0@2019-09-01, 2@2019-09-02],[1@2019-09-03]}')), + (tii, tfloatarg, TFloatInst('0.5@2019-09-01')), + (tids, tfloatarg, TFloatSeq('{0.5@2019-09-01, 2@2019-09-02}')), + (tis, tfloatarg, TFloatSeqSet('{[0.5@2019-09-01, 1@2019-09-02), [2@2019-09-02]}')), + (tiss, tfloatarg, TFloatSeqSet('{[0.5@2019-09-01, 1@2019-09-02), [2@2019-09-02],[1@2019-09-03]}')) + ], + ids=['Instant TInt', 'Discrete Sequence TInt', 'Sequence TInt', 'SequenceSet TInt', + 'Instant TFloat', 'Discrete Sequence TFloat', 'Sequence TFloat', 'SequenceSet TFloat'] + ) + def test_temporal_div_temporal(self, temporal, argument, expected): + assert temporal.div(argument) == expected + assert temporal / argument == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, 1, tii), + (tids, 1, tids), + (tis, 1, tis), + (tiss, 1, tiss), + (tii, 1.0, TFloatInst('1@2019-09-01')), + (tids, 1.0, TFloatSeq('{1@2019-09-01, 2@2019-09-02}')), + # (tis, 1.0, TFloatSeq('[1@2019-09-01, 2@2019-09-02]')), + # (tiss, 1.0, TFloatSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), + (tii, 2, TIntInst('0@2019-09-01')), + (tids, 2, TIntSeq('{0@2019-09-01, 1@2019-09-02}')), + (tis, 2, TIntSeq('[0@2019-09-01, 1@2019-09-02]')), + (tiss, 2, TIntSeqSet('{[0@2019-09-01, 1@2019-09-02],[0@2019-09-03, 0@2019-09-05]}')), + (tii, 2.0, TFloatInst('0.5@2019-09-01')), + (tids, 2.0, TFloatSeq('{0.5@2019-09-01, 1@2019-09-02}')), + (tis, 2.0, TFloatSeq('Interp=Step;[0.5@2019-09-01, 1@2019-09-02]')), + (tiss, 2.0, TFloatSeqSet('Interp=Step;{[0.5@2019-09-01, 1@2019-09-02],[0.5@2019-09-03, 0.5@2019-09-05]}')), + ], + ids=['Instant 1', 'Discrete Sequence 1', 'Sequence 1', 'SequenceSet 1', + 'Instant 1.0', 'Discrete Sequence 1.0', # 'Sequence 1.0', 'SequenceSet 1.0', + 'Instant 2', 'Discrete Sequence 2', 'Sequence 2', 'SequenceSet 2', + 'Instant 2.0', 'Discrete Sequence 2.0', 'Sequence 2.0', 'SequenceSet 2.0'] + ) + def test_temporal_div_int_float (self, temporal, argument, expected): + assert temporal.div(argument) == expected + assert (temporal / argument) == expected + + @pytest.mark.parametrize( + 'temporal', + [tii, tids, tis, tiss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_abs(self, temporal): + assert temporal.abs() == temporal + assert (-1 * temporal).abs() == temporal + + @pytest.mark.parametrize( + 'temporal, expected', + [ + # (tii, TIntInst('1@2019-09-01')), + (tids, TIntSeq('{1@2019-09-01, 1@2019-09-02}')), + (tis, TIntSeq('[1@2019-09-01, 1@2019-09-02)')), + (tiss, TIntSeqSet('{[1@2019-09-01, 1@2019-09-02),[0@2019-09-03, 0@2019-09-05)}')), + ], + ids=[# 'Instant', + 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_delta_value(self, temporal, expected): + assert temporal.delta_value() == expected + + +class TestTIntRestrictors(TestTInt): + tii = TIntInst('1@2019-09-01') + tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') + tis = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + tiss = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}') + + timestamp = datetime(2019, 9, 1) + timestamp_set = TimestampSet('{2019-09-01, 2019-09-03}') + period = Period('[2019-09-01, 2019-09-02]') + period_set = PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, restrictor, expected', + [ + (tii, timestamp, TIntInst('1@2019-09-01')), + (tii, timestamp_set, TIntInst('1@2019-09-01')), + (tii, period, TIntInst('1@2019-09-01')), + (tii, period_set, TIntInst('1@2019-09-01')), + (tii, 1, TIntInst('1@2019-09-01')), + (tii, 2, None), + # (tii, [1,2], TIntInst('1@2019-09-01')), + + (tids, timestamp, TIntSeq('{1@2019-09-01}')), + (tids, timestamp_set, TIntSeq('{1@2019-09-01}')), + (tids, period, TIntSeq('{1@2019-09-01, 2@2019-09-02}')), + (tids, period_set, TIntSeq('{1@2019-09-01, 2@2019-09-02}')), + (tids, 1, TIntSeq('{1@2019-09-01}')), + (tids, 2, TIntSeq('{2@2019-09-02}')), + # (tids, [1,2], TIntSeq('{2@2019-09-02}')), + + (tis, timestamp, TIntSeq('[1@2019-09-01]')), + (tis, timestamp_set, TIntSeq('{1@2019-09-01}')), + (tis, period, TIntSeq('[1@2019-09-01, 2@2019-09-02]')), + (tis, period_set, TIntSeq('[1@2019-09-01, 2@2019-09-02]')), + (tis, 1, TIntSeq('[1@2019-09-01, 1@2019-09-02)')), + (tis, 2, TIntSeq('[2@2019-09-02]')), + # (tis, [1,2], TIntSeq('[2@2019-09-02]')), + + (tiss, timestamp, TIntSeqSet('[1@2019-09-01]')), + (tiss, timestamp_set, TIntSeq('{1@2019-09-01, 1@2019-09-03}')), + (tiss, period, TIntSeqSet('{[1@2019-09-01, 2@2019-09-02]}')), + (tiss, period_set, + TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), + (tiss, 1, TIntSeqSet('{[1@2019-09-01, 1@2019-09-02),[1@2019-09-03, 1@2019-09-05]}')), + (tiss, 2, TIntSeqSet('{[2@2019-09-02]}')), + # (tiss, [1,2], TIntSeqSet('{[2@2019-09-02]}')) + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', + 'Instant-PeriodSet', 'Instant-1', 'Instant-2', # 'Instant-[1,2]', + 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', + 'Discrete Sequence-1', 'Discrete Sequence-2', # 'Discrete Sequence-[1,2]', + 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-1', 'Sequence-2', # 'Sequence-[1,2]', + 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', + 'SequenceSet-PeriodSet', 'SequenceSet-1', 'SequenceSet-2', # 'SequenceSet-[1,2]' + ] + ) + def test_at(self, temporal, restrictor, expected): + assert temporal.at(restrictor) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, TIntInst('1@2019-09-01')), + (tids, TIntSeq('{1@2019-09-01}')), + (tis, TIntSeq('{[1@2019-09-01, 1@2019-09-02)}')), + (tiss, TIntSeqSet('{[1@2019-09-01, 1@2019-09-02),[1@2019-09-03, 1@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_min(self, temporal, expected): + assert temporal.at_min() == expected + + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, TIntInst('1@2019-09-01')), + (tids, TIntSeq('{2@2019-09-02}')), + (tis, TIntSeq('{[2@2019-09-02]}')), + (tiss, TIntSeqSet('{[2@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_max(self, temporal, expected): + assert temporal.at_max() == expected + + @pytest.mark.parametrize( + 'temporal, restrictor, expected', + [ + (tii, timestamp, None), + (tii, timestamp_set, None), + (tii, period, None), + (tii, period_set, None), + (tii, 1, None), + (tii, 2, TIntInst('1@2019-09-01')), + # (tii, [1,2], None), + + (tids, timestamp, TIntSeq('{2@2019-09-02}')), + (tids, timestamp_set, TIntSeq('{2@2019-09-02}')), + (tids, period, None), + (tids, period_set, None), + (tids, 1, TIntSeq('{2@2019-09-02}')), + (tids, 2, TIntSeq('{1@2019-09-01}')), + # (tids, [1,2], tids), + + (tis, timestamp, TIntSeqSet('{(1@2019-09-01, 2@2019-09-02]}')), + (tis, timestamp_set, TIntSeqSet('{(1@2019-09-01, 2@2019-09-02]}')), + (tis, period, None), + (tis, period_set, None), + (tis, 1, TIntSeqSet('{[2@2019-09-02]}')), + (tis, 2, TIntSeqSet('{[1@2019-09-01, 1@2019-09-02)}')), + # (tis, [1,2], tis), + + (tiss, timestamp, + TIntSeqSet('{(1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}')), + (tiss, timestamp_set, + TIntSeqSet('{(1@2019-09-01, 2@2019-09-02],(1@2019-09-03, 1@2019-09-05]}')), + (tiss, period, TIntSeqSet('{[1@2019-09-03, 1@2019-09-05]}')), + (tiss, period_set, None), + (tiss, 1, TIntSeqSet('{[2@2019-09-02]}')), + (tiss, 2, TIntSeqSet('{[1@2019-09-01, 1@2019-09-02),[1@2019-09-03, 1@2019-09-05]}')), + # (tiss, [1,2], tiss) + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', + 'Instant-PeriodSet', 'Instant-1', 'Instant-2', # 'Instant-[1,2]', + 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', + 'Discrete Sequence-1', 'Discrete Sequence-2', # 'Discrete Sequence-[1,2]', + 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-1', 'Sequence-2', # 'Sequence-[1,2]', + 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', + 'SequenceSet-PeriodSet', 'SequenceSet-1', 'SequenceSet-2', # 'SequenceSet-[1,2]' + ] + ) + def test_minus(self, temporal, restrictor, expected): + assert temporal.minus(restrictor) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, None), + (tids, TIntSeq('{1@2019-09-01}')), + (tis, TIntSeq('[1@2019-09-01, 1@2019-09-02)')), + (tiss, TIntSeqSet('{[1@2019-09-01, 1@2019-09-02),[1@2019-09-03, 1@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_minus_max(self, temporal, expected): + assert temporal.minus_max() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, None), + (tids, TIntSeq('{2@2019-09-02}')), + (tis, TIntSeq('[2@2019-09-02]')), + (tiss, TIntSeqSet('{[2@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_minus_min(self, temporal, expected): + assert temporal.minus_min() == expected + + @pytest.mark.parametrize( + 'temporal, restrictor', + [ + (tii, timestamp), + (tii, timestamp_set), + (tii, period), + (tii, period_set), + (tii, 1), + (tii, 2), + + (tids, timestamp), + (tids, timestamp_set), + (tids, period), + (tids, period_set), + (tids, 1), + (tids, 2), + + (tis, timestamp), + (tis, timestamp_set), + (tis, period), + (tis, period_set), + (tis, 1), + (tis, 2), + + (tiss, timestamp), + (tiss, timestamp_set), + (tiss, period), + (tiss, period_set), + (tiss, 1), + (tiss, 2), + + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', 'Instant-PeriodSet', 'Instant-1', + 'Instant-2', 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', 'Discrete Sequence-1', + 'Discrete Sequence-2', 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-1', 'Sequence-2', 'SequenceSet-Timestamp', + 'SequenceSet-TimestampSet', 'SequenceSet-Period', 'SequenceSet-PeriodSet', 'SequenceSet-1', + 'SequenceSet-2'] + ) + def test_at_minus(self, temporal, restrictor): + assert TInt.merge(temporal.at(restrictor), temporal.minus(restrictor)) == temporal + + @pytest.mark.parametrize( + 'temporal', + [tii, tids, tis, tiss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_minus_min_max(self, temporal): + assert TInt.merge(temporal.at_min(), temporal.minus_min()) == temporal + assert TInt.merge(temporal.at_max(), temporal.minus_max()) == temporal + + +class TestTIntTopologicalFunctions(TestTInt): + tii = TIntInst('1@2019-09-01') + tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') + tis = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + tiss = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02], [1@2019-09-03, 1@2019-09-05]}') + + # Problem in MEOS: the definition of TBox for tint should have an intspan ! + # @pytest.mark.parametrize( + # 'temporal, argument, expected', + # [ + # (tii, TIntInst('1@2019-09-02'), False), + # (tii, TIntSeq('(1@2019-09-01, 2@2019-09-02]'), True), + # (tids, TIntInst('1@2019-09-03'), False), + # (tids, TIntInst('2@2019-09-01'), True), + # (tis, TIntInst('1@2019-09-03'), False), + # (tis, TIntSeq('(2@2019-09-01, 3@2019-09-02]'), True), + # (tiss, TIntInst('1@2019-09-08'), False), + # (tiss, TIntSeq('(2@2019-09-01, 3@2019-09-02]'), True), + # ], + # ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + # 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + # ) + # def test_is_adjacent(self, temporal, argument, expected): + # assert temporal.is_adjacent(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, TIntInst('1@2019-09-02'), False), + (tii, TIntSeq('(1@2019-09-01, 2@2019-09-02]'), True), + (tids, TIntInst('1@2019-09-03'), False), + (tids, TIntSeq('(1@2019-09-02, 2@2019-09-03]'), True), + (tis, TIntInst('1@2019-09-03'), False), + (tis, TIntSeq('(1@2019-09-02, 2@2019-09-03]'), True), + (tiss, TIntInst('1@2019-09-08'), False), + (tiss, TIntSeq('(1@2019-09-05, 2@2019-09-06]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_temporally_adjacent(self, temporal, argument, expected): + assert temporal.is_temporally_adjacent(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, TIntInst('1@2019-09-02'), False), + (tii, TIntSeq('[1@2019-09-01,2@2019-09-02]'), True), + (tids, TIntInst('1@2019-09-02'), False), + (tids, TIntSeq('[1@2019-09-01,2@2019-09-02]'), True), + (tis, TIntInst('1@2019-09-02'), False), + (tis, TIntSeq('[1@2019-09-01,2@2019-09-05]'), True), + (tiss, TIntInst('1@2019-09-02'), False), + (tiss, TIntSeq('[1@2019-09-01,2@2019-09-05]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_contained_in_contains(self, temporal, argument, expected): + assert temporal.is_contained_in(argument) == expected + assert argument.contains(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, TIntInst('1@2019-09-02'), False), + (tii, TIntSeq('[1@2019-09-01,2@2019-09-02]'), True), + (tids, TIntInst('1@2019-09-02'), False), + (tids, TIntSeq('[1@2019-09-01,2@2019-09-02]'), True), + (tis, TIntInst('1@2019-09-02'), False), + (tis, TIntSeq('[1@2019-09-01,2@2019-09-05]'), True), + (tiss, TIntInst('1@2019-09-02'), False), + (tiss, TIntSeq('[1@2019-09-01,2@2019-09-05]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_temporally_contained_in_contains(self, temporal, argument, expected): + assert temporal.is_temporally_contained_in(argument) == expected + assert argument.temporally_contains(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, TIntInst('1@2019-09-02'), False), + (tii, TIntSeq('[1@2019-09-01]'), True), + (tids, TIntInst('3@2019-09-02'), False), + (tids, TIntSeq('[1@2019-09-01,2@2019-09-02]'), True), + (tis, TIntInst('3@2019-09-02'), False), + (tis, TIntSeq('[1@2019-09-01,2@2019-09-02]'), True), + (tiss, TIntInst('3@2019-09-02'), False), + (tiss, TIntSeq('[1@2019-09-01,2@2019-09-05]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_overlaps_is_same(self, temporal, argument, expected): + assert temporal.overlaps(argument) == expected + assert temporal.is_same(argument) == expected + + +class TestTIntPositionFunctions(TestTInt): + tii = TIntInst('1@2019-09-01') + tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') + tis = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + tiss = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02], [1@2019-09-03, 1@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, TIntInst('1@2019-09-01'), False), + (tii, TIntInst('1@2019-10-01'), True), + (tids, TIntInst('1@2019-09-01'), False), + (tids, TIntInst('1@2019-10-01'), True), + (tis, TIntInst('1@2019-09-01'), False), + (tis, TIntInst('1@2019-10-01'), True), + (tiss, TIntInst('1@2019-09-01'), False), + (tiss, TIntInst('1@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_before_after(self, temporal, argument, expected): + assert temporal.is_before(argument) == expected + assert argument.is_after(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, TIntInst('1@2019-08-01'), False), + (tii, TIntInst('1@2019-10-01'), True), + (tids, TIntInst('1@2019-08-01'), False), + (tids, TIntInst('1@2019-10-01'), True), + (tis, TIntInst('1@2019-08-01'), False), + (tis, TIntInst('1@2019-10-01'), True), + (tiss, TIntInst('1@2019-08-01'), False), + (tiss, TIntInst('1@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_before(self, temporal, argument, expected): + assert temporal.is_over_or_before(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, TIntInst('1@2019-10-01'), False), + (tii, TIntInst('1@2019-09-01'), True), + (tids, TIntInst('1@2019-10-01'), False), + (tids, TIntInst('1@2019-09-01'), True), + (tis, TIntInst('1@2019-10-01'), False), + (tis, TIntInst('1@2019-09-01'), True), + (tiss, TIntInst('1@2019-10-01'), False), + (tiss, TIntInst('1@2019-09-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_after(self, temporal, argument, expected): + assert temporal.is_over_or_after(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, TIntInst('1@2019-09-01'), False), + (tii, TIntInst('3@2019-10-01'), True), + (tids, TIntInst('1@2019-09-01'), False), + (tids, TIntInst('3@2019-10-01'), True), + (tis, TIntInst('1@2019-09-01'), False), + (tis, TIntInst('3@2019-10-01'), True), + (tiss, TIntInst('1@2019-09-01'), False), + (tiss, TIntInst('3@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_left_right(self, temporal, argument, expected): + assert temporal.is_left(argument) == expected + assert argument.is_right(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, TIntInst('0@2019-09-01'), False), + (tii, TIntInst('3@2019-10-01'), True), + (tids, TIntInst('1@2019-09-01'), False), + (tids, TIntInst('3@2019-10-01'), True), + (tis, TIntInst('1@2019-09-01'), False), + (tis, TIntInst('3@2019-10-01'), True), + (tiss, TIntInst('1@2019-09-01'), False), + (tiss, TIntInst('3@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_left(self, temporal, argument, expected): + assert temporal.is_over_or_left(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, TIntInst('0@2019-09-01'), False), + (tii, TIntInst('3@2019-10-01'), True), + (tids, TIntInst('0@2019-09-01'), False), + (tids, TIntInst('3@2019-10-01'), True), + (tis, TIntInst('0@2019-09-01'), False), + (tis, TIntInst('3@2019-10-01'), True), + (tiss, TIntInst('0@2019-09-01'), False), + (tiss, TIntInst('3@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_right(self, temporal, argument, expected): + assert argument.is_over_or_right(temporal) == expected + + +class TestTIntSimilarityFunctions(TestTInt): + tii = TIntInst('1@2019-09-01') + tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') + tis = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + tiss = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02], [1@2019-09-03, 1@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, TIntInst('3@2019-09-02'), 2.0), + (tids, TIntInst('3@2019-09-03'), 2.0), + (tis, TIntInst('3@2019-09-03'), 2.0), + (tiss, TIntInst('3@2019-09-08'), 2.0), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_frechet_distance(self, temporal, argument, expected): + assert temporal.frechet_distance(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, TIntInst('3@2019-09-02'), 2.0), + (tids, TIntInst('3@2019-09-03'), 3.0), + (tis, TIntInst('3@2019-09-03'), 3.0), + (tiss, TIntInst('3@2019-09-08'), 7.0), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_dyntimewarp_distance(self, temporal, argument, expected): + assert temporal.dyntimewarp_distance(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, TIntInst('3@2019-09-02'), 2.0), + (tids, TIntInst('3@2019-09-03'), 2.0), + (tis, TIntInst('3@2019-09-03'), 2.0), + (tiss, TIntInst('3@2019-09-08'), 2.0), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'Sequence Set'] + ) + def test_hausdorff_distance(self, temporal, argument, expected): + assert temporal.hausdorff_distance(argument) == expected + + +class TestTIntSplitOperations(TestTInt): + tii = TIntInst('1@2019-09-01') + tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') + tis = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + tiss = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, [TIntInst('1@2019-09-01')]), + (tids, [TIntSeq('{1@2019-09-01}'),TIntSeq('{2@2019-09-02}')]), + (tis, [TIntSeq('[1@2019-09-01, 1@2019-09-02)'),TIntSeq('[2@2019-09-02]')]), + (tiss, [TIntSeqSet('{[1@2019-09-01, 1@2019-09-02),[1@2019-09-03, 1@2019-09-05]}'),TIntSeq('[2@2019-09-02]')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_split(self, temporal, expected): + assert temporal.value_split(2) == expected + + # The PyMEOS function uses as default origin the initial timestamp of the + # temporal value while in MEOS the default origin is Monday Janury 3, 2000 + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, [TIntInst('1@2019-09-01')]), + (tids, [TIntSeq('{1@2019-09-01,2@2019-09-02}')]), + (tis, [TIntSeq('[1@2019-09-01, 2@2019-09-02]')]), + (tiss, [TIntSeq('[1@2019-09-01,2@2019-09-02]'), + TIntSeq('[1@2019-09-03, 1@2019-09-05]')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_time_split(self, temporal, expected): + assert temporal.time_split(timedelta(days=2)) == expected + + +class TestTIntComparisons(TestTInt): + ti = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + other = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}') + + def test_eq(self): + _ = self.ti == self.other + + def test_ne(self): + _ = self.ti != self.other + + def test_lt(self): + _ = self.ti < self.other + + def test_le(self): + _ = self.ti <= self.other + + def test_gt(self): + _ = self.ti > self.other + + def test_ge(self): + _ = self.ti >= self.other + + +class TestTIntEverAlwaysComparisons(TestTInt): + tii = TIntInst('1@2019-09-01') + tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') + tis = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + tiss = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, 1, True), + (tii, 2, False), + (tids, 1, False), + (tids, 2, False), + (tis, 1, False), + (tis, 2, False), + (tiss, 1, False), + (tiss, 2, False), + ], + ids=['Instant 1', 'Instant 2', 'Discrete Sequence 1', 'Discrete Sequence 2', + 'Sequence 1', 'Sequence 2', 'SequenceSet 1', 'SequenceSet 2'] + ) + def test_always_equal_ever_not_equal(self, temporal, argument, expected): + assert temporal.always_equal(argument) == expected + assert temporal.never_not_equal(argument) == expected + assert temporal.ever_not_equal(argument) == not_(expected) + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, 1, True), + (tii, 2, False), + (tids, 1, True), + (tids, 2, True), + (tis, 1, True), + (tis, 2, True), + (tiss, 1, True), + (tiss, 2, True) + ], + ids=['Instant 1', 'Instant 2', 'Discrete Sequence 1', 'Discrete Sequence 2', + 'Sequence 1', 'Sequence 2', 'SequenceSet 1', 'SequenceSet 2'] + ) + def test_ever_equal_always_not_equal(self, temporal, argument, expected): + assert temporal.ever_equal(argument) == expected + assert temporal.always_not_equal(argument) == not_(expected) + assert temporal.never_equal(argument) == not_(expected) + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, 1, False), + (tii, 2, True), + (tids, 1, False), + (tids, 2, False), + (tis, 1, False), + (tis, 2, False), + (tiss, 1, False), + (tiss, 2, False), + ], + ids=['Instant 1', 'Instant 2', 'Discrete Sequence 1', 'Discrete Sequence 2', + 'Sequence 1', 'Sequence 2', 'SequenceSet 1', 'SequenceSet 2'] + ) + def test_always_less_ever_greater_or_equal(self, temporal, argument, expected): + assert temporal.always_less(argument) == expected + # assert temporal.never_greater_or_equal(argument) == expected + assert temporal.ever_greater_or_equal(argument) == not_(expected) + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, 1, False), + (tii, 2, True), + (tids, 1, False), + (tids, 2, True), + (tis, 1, False), + (tis, 2, True), + (tiss, 1, False), + (tiss, 2, True), + ], + ids=['Instant 1', 'Instant 2', 'Discrete Sequence 1', 'Discrete Sequence 2', + 'Sequence 1', 'Sequence 2', 'SequenceSet 1', 'SequenceSet 2'] + ) + def test_ever_less_always_greater_or_equal(self, temporal, argument, expected): + assert temporal.ever_less(argument) == expected + assert temporal.always_greater_or_equal(argument) == not_(expected) + assert temporal.never_less(argument) == not_(expected) + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, 1, True), + (tii, 2, True), + (tids, 1, False), + (tids, 2, True), + (tis, 1, False), + (tis, 2, True), + (tiss, 1, False), + (tiss, 2, True), + ], + ids=['Instant 1', 'Instant 2', 'Discrete Sequence 1', 'Discrete Sequence 2', + 'Sequence 1', 'Sequence 2', 'SequenceSet 1', 'SequenceSet 2'] + ) + def test_always_less_or_equal_ever_greater(self, temporal, argument, expected): + assert temporal.always_less_or_equal(argument) == expected + assert temporal.never_greater(argument) == expected + assert temporal.ever_greater(argument) == not_(expected) + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tii, 1, True), + (tii, 2, True), + (tids, 1, True), + (tids, 2, True), + (tis, 1, True), + (tis, 2, True), + (tiss, 1, True), + (tiss, 2, True), + ], + ids=['Instant 1', 'Instant 2', 'Discrete Sequence 1', 'Discrete Sequence 2', + 'Sequence 1', 'Sequence 2', 'SequenceSet 1', 'SequenceSet 2'] + ) + def test_ever_less_or_equal_always_greater(self, temporal, argument, expected): + assert temporal.ever_less_or_equal(argument) == expected + assert temporal.always_greater(argument) == not_(expected) + assert temporal.never_less_or_equal(argument) == not_(expected) + + +class TestTIntTemporalComparisons(TestTInt): + tii = TIntInst('1@2019-09-01') + tids = TIntSeq('{1@2019-09-01, 2@2019-09-02}') + tis = TIntSeq('[1@2019-09-01, 2@2019-09-02]') + tiss = TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}') + argument = TIntSeq('[2@2019-09-01, 1@2019-09-02, 1@2019-09-03]') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, TBoolInst('False@2019-09-01')), + (tids, TBoolSeq('{False@2019-09-01, False@2019-09-02}')), + (tis, TBoolSeq('[False@2019-09-01, False@2019-09-02]')), + (tiss, TBoolSeqSet('{[False@2019-09-01, False@2019-09-02],[True@2019-09-03]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_equal_temporal(self, temporal, expected): + assert temporal.temporal_equal(self.argument) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, TBoolInst('True@2019-09-01')), + (tids, TBoolSeq('{True@2019-09-01, False@2019-09-02}')), + (tis, TBoolSeq('[True@2019-09-01, False@2019-09-02]')), + (tiss, TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_equal_int(self, temporal, expected): + assert temporal.temporal_equal(1) == expected + + assert temporal.temporal_equal(2) == ~expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, TBoolInst('True@2019-09-01')), + (tids, TBoolSeq('{True@2019-09-01, True@2019-09-02}')), + (tis, TBoolSeq('[True@2019-09-01, True@2019-09-02]')), + (tiss, TBoolSeqSet('{[True@2019-09-01, True@2019-09-02],[False@2019-09-03]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_not_equal_temporal(self, temporal, expected): + assert temporal.temporal_not_equal(self.argument) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tii, TBoolInst('False@2019-09-01')), + (tids, TBoolSeq('{False@2019-09-01, True@2019-09-02}')), + (tis, TBoolSeq('[False@2019-09-01, True@2019-09-02]')), + (tiss, TBoolSeqSet('{[False@2019-09-01, True@2019-09-02],[False@2019-09-03, False@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_not_equal_int(self, temporal, expected): + assert temporal.temporal_not_equal(1) == expected + + assert temporal.temporal_not_equal(2) == ~expected + + diff --git a/pymeos/tests/main/ttext_test.py b/pymeos/tests/main/ttext_test.py new file mode 100644 index 00000000..22da37c7 --- /dev/null +++ b/pymeos/tests/main/ttext_test.py @@ -0,0 +1,1498 @@ +from copy import copy +from datetime import datetime, timezone, timedelta + +import pytest + +from pymeos import TBool, TBoolInst, TBoolSeq, TBoolSeqSet, TText, TTextInst, TTextSeq, TTextSeqSet, \ + TInt, TIntInst, TIntSeq, TIntSeqSet, TInterpolation, TimestampSet, Period, PeriodSet +from tests.conftest import TestPyMEOS + + +class TestTText(TestPyMEOS): + pass + + +class TestTTextConstructors(TestTText): + tti = TTextInst('AAA@2019-09-01') + ttds = TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}') + tts = TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]') + ttss = TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}') + + @pytest.mark.parametrize( + 'source, type, interpolation', + [ + (TIntInst('1@2019-09-01'), TTextInst, TInterpolation.NONE), + (TIntSeq('{1@2019-09-01, 2@2019-09-02}'), TTextSeq, TInterpolation.DISCRETE), + (TIntSeq('[1@2019-09-01, 2@2019-09-02]'), TTextSeq, TInterpolation.STEPWISE), + (TIntSeqSet('{[1@2019-09-01, 2@2019-09-02],[1@2019-09-03, 1@2019-09-05]}'), + TTextSeqSet, TInterpolation.STEPWISE) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_from_base_temporal_constructor(self, source, type, interpolation): + tt = TText.from_base_temporal('AAA', source) + assert isinstance(tt, type) + assert tt.interpolation() == interpolation + + @pytest.mark.parametrize( + 'source, type, interpolation', + [ + (datetime(2000, 1, 1), TTextInst, TInterpolation.NONE), + (TimestampSet('{2019-09-01, 2019-09-02}'), TTextSeq, TInterpolation.DISCRETE), + (Period('[2019-09-01, 2019-09-02]'), TTextSeq, TInterpolation.STEPWISE), + (PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}'), TTextSeqSet, TInterpolation.STEPWISE) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_from_base_time_constructor(self, source, type, interpolation): + tt = TText.from_base_time('AAA', source) + assert isinstance(tt, type) + assert tt.interpolation() == interpolation + + @pytest.mark.parametrize( + 'source, type, interpolation, expected', + [ + ('AAA@2019-09-01', TTextInst, TInterpolation.NONE, '"AAA"@2019-09-01 00:00:00+00'), + ('{AAA@2019-09-01, BBB@2019-09-02}', TTextSeq, TInterpolation.DISCRETE, + '{"AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-02 00:00:00+00}'), + ('[AAA@2019-09-01, BBB@2019-09-02]', TTextSeq, TInterpolation.STEPWISE, + '["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-02 00:00:00+00]'), + ('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}', TTextSeqSet, + TInterpolation.STEPWISE, '{["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-02 00:00:00+00], ' + '["AAA"@2019-09-03 00:00:00+00, "AAA"@2019-09-05 00:00:00+00]}'), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_string_constructor(self, source, type, interpolation, expected): + tt = type(source) + assert isinstance(tt, type) + assert tt.interpolation() == interpolation + assert str(tt) == expected + + @pytest.mark.parametrize( + 'source, type, expected', + [ + ('[AAA@2019-09-01, AAA@2019-09-02, AAA@2019-09-03, BBB@2019-09-05]', TTextSeq, + '["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-05 00:00:00+00]'), + ('{[AAA@2019-09-01, AAA@2019-09-02, AAA@2019-09-03, BBB@2019-09-05],' + '[AAA@2019-09-07, AAA@2019-09-08, AAA@2019-09-09]}', TTextSeqSet, + '{["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-05 00:00:00+00], ' + '["AAA"@2019-09-07 00:00:00+00, "AAA"@2019-09-09 00:00:00+00]}'), + ], + ids=['Sequence', 'SequenceSet'] + ) + def test_string_constructor_normalization(self, source, type, expected): + tt = type(source, normalize=1) + assert isinstance(tt, type) + assert str(tt) == expected + + @pytest.mark.parametrize( + 'value, timestamp', + [ + ('AAA', datetime(2019, 9, 1, tzinfo=timezone.utc)), + ('AAA', '2019-09-01'), + ], + ids=['string', 'datetime'] + ) + def test_value_timestamp_instant_constructor(self, value, timestamp): + tti = TTextInst(value=value, timestamp=timestamp) + assert str(tti) == '"AAA"@2019-09-01 00:00:00+00' + + @pytest.mark.parametrize( + 'list, interpolation, normalize, expected', + [ + (['AAA@2019-09-01', 'BBB@2019-09-03'], TInterpolation.DISCRETE, False, + '{"AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-03 00:00:00+00}'), + (['AAA@2019-09-01', 'BBB@2019-09-03'], TInterpolation.STEPWISE, False, + '["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-03 00:00:00+00]'), + ([TTextInst('AAA@2019-09-01'), TTextInst('BBB@2019-09-03')], TInterpolation.DISCRETE, False, + '{"AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-03 00:00:00+00}'), + ([TTextInst('AAA@2019-09-01'), TTextInst('BBB@2019-09-03')], TInterpolation.STEPWISE, False, + '["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-03 00:00:00+00]'), + (['AAA@2019-09-01', TTextInst('BBB@2019-09-03')], TInterpolation.DISCRETE, False, + '{"AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-03 00:00:00+00}'), + (['AAA@2019-09-01', TTextInst('BBB@2019-09-03')], TInterpolation.STEPWISE, False, + '["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-03 00:00:00+00]'), + + (['AAA@2019-09-01', 'AAA@2019-09-02', 'BBB@2019-09-03'], TInterpolation.STEPWISE, True, + '["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-03 00:00:00+00]'), + ([TTextInst('AAA@2019-09-01'), TTextInst('AAA@2019-09-02'), TTextInst('BBB@2019-09-03')], + TInterpolation.STEPWISE, True, + '["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-03 00:00:00+00]'), + (['AAA@2019-09-01', 'AAA@2019-09-02', TTextInst('BBB@2019-09-03')], TInterpolation.STEPWISE, True, + '["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-03 00:00:00+00]'), + ], + ids=['String Discrete', 'String Stepwise', 'TTextInst Discrete', 'TTextInst Stepwise', 'Mixed Discrete', + 'Mixed Stepwise', 'String Stepwise Normalized', 'TTextInst Stepwise Normalized', + 'Mixed Stepwise Normalized'] + ) + def test_instant_list_sequence_constructor(self, list, interpolation, normalize, expected): + tts = TTextSeq(instant_list=list, interpolation=interpolation, normalize=normalize, upper_inc=True) + assert str(tts) == expected + assert tts.interpolation() == interpolation + + tts2 = TTextSeq.from_instants(list, interpolation=interpolation, normalize=normalize, upper_inc=True) + assert str(tts2) == expected + assert tts2.interpolation() == interpolation + + @pytest.mark.parametrize( + 'temporal', + [tti, ttds, tts, ttss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_from_as_constructor(self, temporal): + assert temporal == temporal.from_wkb(temporal.as_wkb()) + assert temporal == temporal.from_hexwkb(temporal.as_hexwkb()) + assert temporal == temporal.from_mfjson(temporal.as_mfjson()) + + @pytest.mark.parametrize( + 'temporal', + [tti, ttds, tts, ttss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_copy_constructor(self, temporal): + other = copy(temporal) + assert temporal == other + assert temporal is not other + + +class TestTTextOutputs(TestTText): + tti = TTextInst('AAA@2019-09-01') + ttds = TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}') + tts = TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]') + ttss = TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, '"AAA"@2019-09-01 00:00:00+00'), + (ttds, '{"AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-02 00:00:00+00}'), + (tts, '["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-02 00:00:00+00]'), + (ttss, '{["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-02 00:00:00+00], ' + '["AAA"@2019-09-03 00:00:00+00, "AAA"@2019-09-05 00:00:00+00]}') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_str(self, temporal, expected): + assert str(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, 'TTextInst("AAA"@2019-09-01 00:00:00+00)'), + (ttds, 'TTextSeq({"AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-02 00:00:00+00})'), + (tts, 'TTextSeq(["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-02 00:00:00+00])'), + (ttss, 'TTextSeqSet({["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-02 00:00:00+00], ' + '["AAA"@2019-09-03 00:00:00+00, "AAA"@2019-09-05 00:00:00+00]})') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_repr(self, temporal, expected): + assert repr(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, '"AAA"@2019-09-01 00:00:00+00'), + (ttds, '{"AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-02 00:00:00+00}'), + (tts, '["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-02 00:00:00+00]'), + (ttss, '{["AAA"@2019-09-01 00:00:00+00, "BBB"@2019-09-02 00:00:00+00], ' + '["AAA"@2019-09-03 00:00:00+00, "AAA"@2019-09-05 00:00:00+00]}') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_wkt(self, temporal, expected): + assert temporal.as_wkt() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, '0123000104000000000000004141410000A01E4E71340200'), + (ttds, '01230006020000000304000000000000004141410000A01E4E713402000400000000000000424242000000F66B85340200'), + (tts, '0123000A020000000304000000000000004141410000A01E4E713402000400000000000000424242000000F66B85340200'), + (ttss, '0123000B02000000020000000304000000000000004141410000A01E4E713402000400000000000000424242000000F66B85340200' '02000000030400000000000000414141000060CD899934020004000000000000004141410000207CC5C1340200') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_hexwkb(self, temporal, expected): + assert temporal.as_hexwkb() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, '{\n' + ' "type": "MovingText",\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-01T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "values": [\n' + ' "AAA"\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00"\n' + ' ],\n' + ' "interpolation": "None"\n' + ' }'), + (ttds, '{\n' + ' "type": "MovingText",\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-02T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "values": [\n' + ' "AAA",\n' + ' "BBB"\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true,\n' + ' "interpolation": "Discrete"\n' + ' }'), + (tts, '{\n' + ' "type": "MovingText",\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-02T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "values": [\n' + ' "AAA",\n' + ' "BBB"\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true,\n' + ' "interpolation": "Step"\n' + ' }'), + (ttss, '{\n' + ' "type": "MovingText",\n' + ' "period": {\n' + ' "begin": "2019-09-01T00:00:00+00",\n' + ' "end": "2019-09-05T00:00:00+00",\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' "sequences": [\n' + ' {\n' + ' "values": [\n' + ' "AAA",\n' + ' "BBB"\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-01T00:00:00+00",\n' + ' "2019-09-02T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' },\n' + ' {\n' + ' "values": [\n' + ' "AAA",\n' + ' "AAA"\n' + ' ],\n' + ' "datetimes": [\n' + ' "2019-09-03T00:00:00+00",\n' + ' "2019-09-05T00:00:00+00"\n' + ' ],\n' + ' "lower_inc": true,\n' + ' "upper_inc": true\n' + ' }\n' + ' ],\n' + ' "interpolation": "Step"\n' + ' }') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_as_mfjson(self, temporal, expected): + assert temporal.as_mfjson() == expected + + +class TestTTextAccessors(TestTText): + tti = TTextInst('AAA@2019-09-01') + ttds = TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}') + tts = TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]') + ttss = TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, TInterpolation.NONE), + (ttds, TInterpolation.DISCRETE), + (tts, TInterpolation.STEPWISE), + (ttss, TInterpolation.STEPWISE) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_interpolation(self, temporal, expected): + assert temporal.interpolation() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, {'AAA'}), + (ttds, {'AAA', 'BBB'}), + (tts, {'AAA', 'BBB'}), + (ttss, {'AAA', 'BBB'}) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_value_set(self, temporal, expected): + assert temporal.value_set() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, ['AAA']), + (ttds, ['AAA', 'BBB']), + (tts, ['AAA', 'BBB']), + (ttss, ['AAA', 'BBB', 'AAA', 'AAA']) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_values(self, temporal, expected): + assert temporal.values() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, 'AAA'), + (ttds, 'AAA'), + (tts, 'AAA'), + (ttss, 'AAA') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_value(self, temporal, expected): + assert temporal.start_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, 'AAA'), + (ttds, 'BBB'), + (tts, 'BBB'), + (ttss, 'AAA') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_value(self, temporal, expected): + assert temporal.end_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, 'AAA'), + (ttds, 'AAA'), + (tts, 'AAA'), + (ttss, 'AAA') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_min_value(self, temporal, expected): + assert temporal.min_value() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, 'AAA'), + (ttds, 'BBB'), + (tts, 'BBB'), + (ttss, 'BBB') + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_max_value(self, temporal, expected): + assert temporal.max_value() == expected + + # @pytest.mark.parametrize( + # 'temporal, expected', + # [ + # (tti, 'AAA'), + # (ttds, 'AAA'), + # (tts, 'AAA'), + # (ttss, 'AAA') + # ], + # ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + # ) + # def test_value_at_timestamp(self, temporal, expected): + # assert temporal.value_at_timestamp(datetime(2019, 9, 1)) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, PeriodSet('{[2019-09-01, 2019-09-01]}')), + (ttds, PeriodSet('{[2019-09-01, 2019-09-01], [2019-09-02, 2019-09-02]}')), + (tts, PeriodSet('{[2019-09-01, 2019-09-02]}')), + (ttss, PeriodSet('{[2019-09-01, 2019-09-02], [2019-09-03, 2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_time(self, temporal, expected): + assert temporal.time() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, timedelta()), + (ttds, timedelta()), + (tts, timedelta(days=1)), + (ttss, timedelta(days=3)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_duration(self, temporal, expected): + assert temporal.duration() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, timedelta()), + (ttds, timedelta(days=1)), + (tts, timedelta(days=1)), + (ttss, timedelta(days=4)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_duration_ignoring_gaps(self, temporal, expected): + assert temporal.duration(ignore_gaps=True) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, Period('[2019-09-01, 2019-09-01]')), + (ttds, Period('[2019-09-01, 2019-09-02]')), + (tts, Period('[2019-09-01, 2019-09-02]')), + (ttss, Period('[2019-09-01, 2019-09-05]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_period(self, temporal, expected): + assert temporal.period() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, Period('[2019-09-01, 2019-09-01]')), + (ttds, Period('[2019-09-01, 2019-09-02]')), + (tts, Period('[2019-09-01, 2019-09-02]')), + (ttss, Period('[2019-09-01, 2019-09-05]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timespan(self, temporal, expected): + assert temporal.timespan() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, 1), + (ttds, 2), + (tts, 2), + (ttss, 4), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_num_instants(self, temporal, expected): + assert temporal.num_instants() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, tti), + (ttds, tti), + (tts, tti), + (ttss, tti), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_instant(self, temporal, expected): + assert temporal.start_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, tti), + (ttds, TTextInst('BBB@2019-09-02')), + (tts, TTextInst('BBB@2019-09-02')), + (ttss, TTextInst('AAA@2019-09-05')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_instant(self, temporal, expected): + assert temporal.end_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, tti), + (ttds, TTextInst('BBB@2019-09-02')), + (tts, TTextInst('BBB@2019-09-02')), + (ttss, TTextInst('BBB@2019-09-02')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_max_instant(self, temporal, expected): + assert temporal.max_instant() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, tti), + (ttds, tti), + (tts, tti), + (ttss, tti), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_min_instant(self, temporal, expected): + assert temporal.min_instant() == expected + + @pytest.mark.parametrize( + 'temporal, n, expected', + [ + (tti, 0, tti), + (ttds, 1, TTextInst('BBB@2019-09-02')), + (tts, 1, TTextInst('BBB@2019-09-02')), + (ttss, 2, TTextInst('AAA@2019-09-03')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_instant_n(self, temporal, n, expected): + assert temporal.instant_n(n) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, [tti]), + (ttds, [tti, TTextInst('BBB@2019-09-02')]), + (tts, [tti, TTextInst('BBB@2019-09-02')]), + (ttss, [tti, TTextInst('BBB@2019-09-02'), TTextInst('AAA@2019-09-03'), TTextInst('AAA@2019-09-05')]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_instants(self, temporal, expected): + assert temporal.instants() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, 1), + (ttds, 2), + (tts, 2), + (ttss, 4), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_num_timestamps(self, temporal, expected): + assert temporal.num_timestamps() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (ttds, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (tts, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (ttss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_start_timestamp(self, temporal, expected): + assert temporal.start_timestamp() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (ttds, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tts, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (ttss, datetime(year=2019, month=9, day=5, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_end_timestamp(self, temporal, expected): + assert temporal.end_timestamp() == expected + + @pytest.mark.parametrize( + 'temporal, n, expected', + [ + (tti, 0, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)), + (ttds, 1, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (tts, 1, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)), + (ttss, 2, datetime(year=2019, month=9, day=3, tzinfo=timezone.utc)), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timestamp_n(self, temporal, n, expected): + assert temporal.timestamp_n(n) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc)]), + (ttds, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)]), + (tts, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc)]), + (ttss, [datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=3, tzinfo=timezone.utc), + datetime(year=2019, month=9, day=5, tzinfo=timezone.utc)]), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_timestamps(self, temporal, expected): + assert temporal.timestamps() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (ttds, [TTextSeq('[AAA@2019-09-01]'), TTextSeq('[BBB@2019-09-02]')]), + (tts, [TTextSeq('[AAA@2019-09-01, AAA@2019-09-02)'), + TTextSeq('[BBB@2019-09-02]')]), + (ttss, + [TTextSeq('[AAA@2019-09-01, AAA@2019-09-02)'), + TTextSeq('[BBB@2019-09-02]'), + TTextSeq('[AAA@2019-09-03, AAA@2019-09-05]')]), + ], + ids=['Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_segments(self, temporal, expected): + assert temporal.segments() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (ttds, True), + (tts, True), + ], + ids=['Discrete Sequence', 'Sequence'] + ) + def test_lower_upper_inc(self, temporal, expected): + assert temporal.lower_inc() == expected + assert temporal.upper_inc() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, 1893808825), + (ttds, 1223816819), + (tts, 1223816819), + (ttss, 2199213310) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_hash(self, temporal, expected): + assert hash(temporal) == expected + + +class TestTTextTransformations(TestTText): + tti = TTextInst('AAA@2019-09-01') + ttds = TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}') + tts = TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]') + ttss = TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TTextInst('AAA@2019-09-01'), tti), + (TTextSeq('{AAA@2019-09-01}'), tti), + (TTextSeq('[AAA@2019-09-01]'), tti), + (TTextSeqSet('{[AAA@2019-09-01]}'), tti), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_instant(self, temporal, expected): + temp = temporal.to_instant() + assert isinstance(temp, TTextInst) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TTextInst('AAA@2019-09-01'), + TTextSeq('[AAA@2019-09-01]')), + (TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}'), + TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}')), + (TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]'), + TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]')), + (TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02]}'), + TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_sequence(self, temporal, expected): + temp = temporal.to_sequence() + assert isinstance(temp, TTextSeq) + assert temp == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (TTextInst('AAA@2019-09-01'), + TTextSeqSet('{[AAA@2019-09-01]}')), + (TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}'), + TTextSeqSet('{[AAA@2019-09-01], [BBB@2019-09-02]}')), + (TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]'), + TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02]}')), + (TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02]}'), + TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_to_sequenceset(self, temporal, expected): + temp = temporal.to_sequenceset() + assert isinstance(temp, TTextSeqSet) + assert temp == expected + + @pytest.mark.parametrize( + 'ttext, delta, expected', + [(tti, timedelta(days=4), TTextInst('AAA@2019-09-05')), + (tti, timedelta(days=-4), TTextInst('AAA@2019-08-28')), + (tti, timedelta(hours=2), TTextInst('AAA@2019-09-01 02:00:00')), + (tti, timedelta(hours=-2), TTextInst('AAA@2019-08-31 22:00:00')), + (ttds, timedelta(days=4), TTextSeq('{AAA@2019-09-05, BBB@2019-09-06}')), + (ttds, timedelta(days=-4), TTextSeq('{AAA@2019-08-28, BBB@2019-08-29}')), + (ttds, timedelta(hours=2), TTextSeq('{AAA@2019-09-01 02:00:00, BBB@2019-09-02 02:00:00}')), + (ttds, timedelta(hours=-2), TTextSeq('{AAA@2019-08-31 22:00:00, BBB@2019-09-01 22:00:00}')), + (tts, timedelta(days=4), TTextSeq('[AAA@2019-09-05, BBB@2019-09-06]')), + (tts, timedelta(days=-4), TTextSeq('[AAA@2019-08-28, BBB@2019-08-29]')), + (tts, timedelta(hours=2), TTextSeq('[AAA@2019-09-01 02:00:00, BBB@2019-09-02 02:00:00]')), + (tts, timedelta(hours=-2), TTextSeq('[AAA@2019-08-31 22:00:00, BBB@2019-09-01 22:00:00]')), + (ttss, timedelta(days=4), + TTextSeqSet('{[AAA@2019-09-05, BBB@2019-09-06],[AAA@2019-09-07, AAA@2019-09-09]}')), + (ttss, timedelta(days=-4), + TTextSeqSet('{[AAA@2019-08-28, BBB@2019-08-29],[AAA@2019-08-30, AAA@2019-09-01]}')), + (ttss, timedelta(hours=2), + TTextSeqSet('{[AAA@2019-09-01 02:00:00, BBB@2019-09-02 02:00:00],' + '[AAA@2019-09-03 02:00:00, AAA@2019-09-05 02:00:00]}')), + (ttss, timedelta(hours=-2), + TTextSeqSet('{[AAA@2019-08-31 22:00:00, BBB@2019-09-01 22:00:00],' + '[AAA@2019-09-02 22:00:00, AAA@2019-09-04 22:00:00]}')), + ], + ids=['Instant positive days', 'Instant negative days', + 'Instant positive hours', 'Instant negative hours', + 'Discrete Sequence positive days', 'Discrete Sequence negative days', + 'Discrete Sequence positive hours', 'Discrete Sequence negative hours', + 'Sequence positive days', 'Sequence negative days', + 'Sequence positive hours', 'Sequence negative hours', + 'Sequence Set positive days', 'Sequence Set negative days', + 'Sequence Set positive hours', 'Sequence Set negative hours'] + ) + def test_shift(self, ttext, delta, expected): + assert ttext.shift(delta) == expected + + @pytest.mark.parametrize( + 'ttext, delta, expected', + [(tti, timedelta(days=4), TTextInst('AAA@2019-09-01')), + (tti, timedelta(hours=2), TTextInst('AAA@2019-09-01')), + (ttds, timedelta(days=4), TTextSeq('{AAA@2019-09-01, BBB@2019-09-05}')), + (ttds, timedelta(hours=2), TTextSeq('{AAA@2019-09-01 00:00:00, BBB@2019-09-01 02:00:00}')), + (tts, timedelta(days=4), TTextSeq('[AAA@2019-09-01, BBB@2019-09-05]')), + (tts, timedelta(hours=2), TTextSeq('[AAA@2019-09-01 00:00:00, BBB@2019-09-01 02:00:00]')), + (ttss, timedelta(days=4), + TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}')), + (ttss, timedelta(hours=2), + TTextSeqSet('{[AAA@2019-09-01 00:00:00, BBB@2019-09-01 00:30:00],' + '[AAA@2019-09-01 01:00:00, AAA@2019-09-01 02:00:00]}')), + ], + ids=['Instant positive days', 'Instant positive hours', + 'Discrete Sequence positive days', 'Discrete Sequence positive hours', + 'Sequence positive days', 'Sequence positive hours', + 'Sequence Set positive days', 'Sequence Set positive hours'] + ) + def test_scale(self, ttext, delta, expected): + assert ttext.tscale(delta) == expected + + def test_shift_tscale(self): + assert self.ttss.shift_tscale(timedelta(days=4), timedelta(hours=2)) == \ + TTextSeqSet('{[AAA@2019-09-05 00:00:00, BBB@2019-09-05 00:30:00],' + '[AAA@2019-09-05 01:00:00, AAA@2019-09-05 02:00:00]}') + + +class TestTTextModifications(TestTText): + tti = TTextInst('AAA@2019-09-01') + ttds = TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}') + tts = TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]') + ttss = TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, sequence, expected', + [ + (tti, TTextSeq('{AAA@2019-09-03}'), TTextSeq('{AAA@2019-09-01, AAA@2019-09-03}')), + (ttds, TTextSeq('{AAA@2019-09-03}'), TTextSeq('{AAA@2019-09-01, BBB@2019-09-02, AAA@2019-09-03}')), + (tts, TTextSeq('[AAA@2019-09-03]'), TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02, AAA@2019-09-03]}')), + (ttss, TTextSeq('[AAA@2019-09-06]'), + TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05],[AAA@2019-09-06]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_insert(self, temporal, sequence, expected): + assert temporal.insert(sequence) == expected + + @pytest.mark.parametrize( + 'temporal, instant, expected', + [ + (tti, TTextInst('BBB@2019-09-01'), TTextInst('BBB@2019-09-01')), + (ttds, TTextInst('BBB@2019-09-01'), TTextSeq('{BBB@2019-09-01, BBB@2019-09-02}')), + (tts, TTextInst('BBB@2019-09-01'), + TTextSeqSet('{[BBB@2019-09-01], (AAA@2019-09-01, BBB@2019-09-02]}')), + (ttss, TTextInst('BBB@2019-09-01'), + TTextSeqSet('{[BBB@2019-09-01], (AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_update(self, temporal, instant, expected): + assert temporal.update(instant) == expected + + @pytest.mark.parametrize( + 'temporal, time, expected', + [ + (tti, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), None), + (tti, datetime(year=2019, month=9, day=2, tzinfo=timezone.utc), tti), + (ttds, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), TTextSeq('{BBB@2019-09-02}')), + (tts, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + TTextSeqSet('{(AAA@2019-09-01, BBB@2019-09-02]}')), + (ttss, datetime(year=2019, month=9, day=1, tzinfo=timezone.utc), + TTextSeqSet('{(AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}')), + ], + ids=['Instant intersection', 'Instant disjoint', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_delete(self, temporal, time, expected): + assert temporal.delete(time) == expected + + @pytest.mark.parametrize( + 'temporal, instant, expected', + [ + (tti, TTextInst('AAA@2019-09-02'), TTextSeq('{AAA@2019-09-01, AAA@2019-09-02}')), + (ttds, TTextInst('AAA@2019-09-03'), TTextSeq('{AAA@2019-09-01, BBB@2019-09-02, AAA@2019-09-03}')), + (tts, TTextInst('AAA@2019-09-03'), TTextSeq('[AAA@2019-09-01, BBB@2019-09-02, AAA@2019-09-03]')), + (ttss, TTextInst('AAA@2019-09-06'), + TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-06]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_append_instant(self, temporal, instant, expected): + assert temporal.append_instant(instant) == expected + + @pytest.mark.parametrize( + 'temporal, sequence, expected', + [ + (ttds, TTextSeq('{AAA@2019-09-03}'), TTextSeq('{AAA@2019-09-01, BBB@2019-09-02, AAA@2019-09-03}')), + (tts, TTextSeq('[AAA@2019-09-03]'), TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02], [AAA@2019-09-03]}')), + (ttss, TTextSeq('[AAA@2019-09-06]'), + TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05],[AAA@2019-09-06]}')), + ], + ids=['Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_append_sequence(self, temporal, sequence, expected): + assert temporal.append_sequence(sequence) == expected + + +class TestTTextTextOperations(TestTText): + tti = TTextInst('AAA@2019-09-01') + ttds = TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}') + tts = TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]') + ttss = TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}') + argument = TTextSeq('[BBB@2019-09-01, AAA@2019-09-02, AAA@2019-09-03]') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, TTextInst('AAABBB@2019-09-01')), + (ttds, TTextSeq('{AAABBB@2019-09-01, BBBAAA@2019-09-02}')), + (tts, TTextSeq('[AAABBB@2019-09-01, BBBAAA@2019-09-02]')), + (ttss, TTextSeqSet('{[AAABBB@2019-09-01, BBBAAA@2019-09-02],[AAAAAA@2019-09-03]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_concat_temporal(self, temporal, expected): + assert temporal.concatenate(self.argument) == expected + assert temporal + self.argument == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, TTextInst('AAABBB@2019-09-01')), + (ttds, TTextSeq('{AAABBB@2019-09-01, BBBBBB@2019-09-02}')), + (tts, TTextSeq('[AAABBB@2019-09-01, BBBBBB@2019-09-02]')), + (ttss, TTextSeqSet('{[AAABBB@2019-09-01, BBBBBB@2019-09-02],[AAABBB@2019-09-03, AAABBB@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_concat_text(self, temporal, expected): + assert temporal.concatenate('BBB') == expected + assert (temporal + 'BBB') == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, TTextInst('BBBAAA@2019-09-01')), + (ttds, TTextSeq('{BBBAAA@2019-09-01, BBBBBB@2019-09-02}')), + (tts, TTextSeq('[BBBAAA@2019-09-01, BBBBBB@2019-09-02]')), + (ttss, TTextSeqSet('{[BBBAAA@2019-09-01, BBBBBB@2019-09-02],[BBBAAA@2019-09-03, BBBAAA@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_radd_text(self, temporal, expected): + assert ('BBB' + temporal) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, TTextInst('aaa@2019-09-01')), + (ttds, TTextSeq('{aaa@2019-09-01, bbb@2019-09-02}')), + (tts, TTextSeq('[aaa@2019-09-01, bbb@2019-09-02]')), + (ttss, TTextSeqSet('{[aaa@2019-09-01, bbb@2019-09-02],[aaa@2019-09-03, aaa@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_lower(self, temporal, expected): + assert temporal.lower() == expected + + @pytest.mark.parametrize( + 'temporal', + [tti, ttds, tts, ttss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_upper(self, temporal): + assert temporal.upper() == temporal + + +class TestTTextRestrictors(TestTText): + tti = TTextInst('AAA@2019-09-01') + ttds = TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}') + tts = TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]') + ttss = TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}') + + timestamp = datetime(2019, 9, 1) + timestamp_set = TimestampSet('{2019-09-01, 2019-09-03}') + period = Period('[2019-09-01, 2019-09-02]') + period_set = PeriodSet('{[2019-09-01, 2019-09-02],[2019-09-03, 2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, restrictor, expected', + [ + (tti, timestamp, TTextInst('AAA@2019-09-01')), + (tti, timestamp_set, TTextInst('AAA@2019-09-01')), + (tti, period, TTextInst('AAA@2019-09-01')), + (tti, period_set, TTextInst('AAA@2019-09-01')), + (tti, 'AAA', TTextInst('AAA@2019-09-01')), + (tti, 'BBB', None), + # (tti, [AAA,BBB], None), + + (ttds, timestamp, TTextSeq('{AAA@2019-09-01}')), + (ttds, timestamp_set, TTextSeq('{AAA@2019-09-01}')), + (ttds, period, TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}')), + (ttds, period_set, TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}')), + (ttds, 'AAA', TTextSeq('{AAA@2019-09-01}')), + (ttds, 'BBB', TTextSeq('{BBB@2019-09-02}')), + # (ttds, [AAA,BBB], TTextSeq('{BBB@2019-09-02}')), + + (tts, timestamp, TTextSeq('[AAA@2019-09-01]')), + (tts, timestamp_set, TTextSeq('{AAA@2019-09-01}')), + (tts, period, TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]')), + (tts, period_set, TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]')), + (tts, 'AAA', TTextSeq('[AAA@2019-09-01, AAA@2019-09-02)')), + (tts, 'BBB', TTextSeq('[BBB@2019-09-02]')), + # (tts, [AAA,BBB], TTextSeq('[BBB@2019-09-02]')), + + (ttss, timestamp, TTextSeqSet('[AAA@2019-09-01]')), + (ttss, timestamp_set, TTextSeq('{AAA@2019-09-01, AAA@2019-09-03}')), + (ttss, period, TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02]}')), + (ttss, period_set, + TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}')), + (ttss, 'AAA', TTextSeqSet('{[AAA@2019-09-01, AAA@2019-09-02),[AAA@2019-09-03, AAA@2019-09-05]}')), + (ttss, 'BBB', TTextSeqSet('{[BBB@2019-09-02]}')), + # (ttss, [AAA,BBB], TTextSeqSet('{[BBB@2019-09-02]}')) + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', + 'Instant-PeriodSet', 'Instant-AAA', 'Instant-BBB', # 'Instant-[AAA,BBB]', + 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', + 'Discrete Sequence-AAA', 'Discrete Sequence-BBB', # 'Discrete Sequence-[AAA,BBB]', + 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-AAA', 'Sequence-BBB', # 'Sequence-[AAA,BBB]', + 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', + 'SequenceSet-PeriodSet', 'SequenceSet-AAA', 'SequenceSet-BBB', + # 'SequenceSet-[AAA,BBB]' + ] + ) + def test_at(self, temporal, restrictor, expected): + assert temporal.at(restrictor) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, TTextInst('AAA@2019-09-01')), + (ttds, TTextSeq('{BBB@2019-09-02}')), + (tts, TTextSeq('{[BBB@2019-09-02]}')), + (ttss, TTextSeqSet('{[BBB@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_max(self, temporal, expected): + assert temporal.at_max() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, TTextInst('AAA@2019-09-01')), + (ttds, TTextSeq('{AAA@2019-09-01}')), + (tts, TTextSeq('{[AAA@2019-09-01, AAA@2019-09-02)}')), + (ttss, TTextSeqSet('{[AAA@2019-09-01, AAA@2019-09-02), [AAA@2019-09-03, AAA@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_min(self, temporal, expected): + assert temporal.at_min() == expected + + + @pytest.mark.parametrize( + 'temporal, restrictor, expected', + [ + (tti, timestamp, None), + (tti, timestamp_set, None), + (tti, period, None), + (tti, period_set, None), + (tti, 'AAA', None), + (tti, 'BBB', TTextInst('AAA@2019-09-01')), + # (tti, [AAA,BBB], None), + + (ttds, timestamp, TTextSeq('{BBB@2019-09-02}')), + (ttds, timestamp_set, TTextSeq('{BBB@2019-09-02}')), + (ttds, period, None), + (ttds, period_set, None), + (ttds, 'AAA', TTextSeq('{BBB@2019-09-02}')), + (ttds, 'BBB', TTextSeq('{AAA@2019-09-01}')), + # (ttds, [AAA,BBB], TTextSeq('{BBB@2019-09-02}')), + + (tts, timestamp, TTextSeqSet('{(AAA@2019-09-01, BBB@2019-09-02]}')), + (tts, timestamp_set, TTextSeqSet('{(AAA@2019-09-01, BBB@2019-09-02]}')), + (tts, period, None), + (tts, period_set, None), + (tts, 'AAA', TTextSeqSet('{[BBB@2019-09-02]}')), + (tts, 'BBB', TTextSeqSet('{[AAA@2019-09-01, AAA@2019-09-02)}')), + # (tts, [AAA,BBB], TTextSeq('[BBB@2019-09-02]')), + + (ttss, timestamp, + TTextSeqSet('{(AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}')), + (ttss, timestamp_set, + TTextSeqSet('{(AAA@2019-09-01, BBB@2019-09-02],(AAA@2019-09-03, AAA@2019-09-05]}')), + (ttss, period, TTextSeqSet('{[AAA@2019-09-03, AAA@2019-09-05]}')), + (ttss, period_set, None), + (ttss, 'AAA', TTextSeqSet('{[BBB@2019-09-02]}')), + (ttss, 'BBB', TTextSeqSet('{[AAA@2019-09-01, AAA@2019-09-02),[AAA@2019-09-03, AAA@2019-09-05]}')) + # (ttss, [AAA,BBB], TTextSeqSet('{[BBB@2019-09-02]}')) + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', + 'Instant-PeriodSet', 'Instant-AAA', 'Instant-BBB', # 'Instant-[AAA,BBB]', + 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', + 'Discrete Sequence-AAA', 'Discrete Sequence-BBB', # 'Discrete Sequence-[AAA,BBB]', + 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-AAA', 'Sequence-BBB', # 'Sequence-[AAA,BBB]', + 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', + 'SequenceSet-PeriodSet', 'SequenceSet-AAA', 'SequenceSet-BBB', + # 'SequenceSet-[AAA,BBB]' + ] + ) + def test_minus(self, temporal, restrictor, expected): + assert temporal.minus(restrictor) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, None), + (ttds, TTextSeq('{BBB@2019-09-02}')), + (tts, TTextSeq('{[BBB@2019-09-02]}')), + (ttss, TTextSeqSet('{[BBB@2019-09-02]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_minus_min(self, temporal, expected): + assert temporal.minus_min() == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, None), + (ttds, TTextSeq('{AAA@2019-09-01}')), + (tts, TTextSeq('{[AAA@2019-09-01, AAA@2019-09-02)}')), + (ttss, TTextSeqSet('{[AAA@2019-09-01, AAA@2019-09-02), [AAA@2019-09-03, AAA@2019-09-05]}')), + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_minus_max(self, temporal, expected): + assert temporal.minus_max() == expected + + @pytest.mark.parametrize( + 'temporal, restrictor', + [ + (tti, timestamp), + (tti, timestamp_set), + (tti, period), + (tti, period_set), + (tti, 'AAA'), + (tti, 'BBB'), + # (tti, ['AAA','BBB']), + + (ttds, timestamp), + (ttds, timestamp_set), + (ttds, period), + (ttds, period_set), + (ttds, 'AAA'), + (ttds, 'BBB'), + # (ttds, ['AAA','BBB']), + + (tts, timestamp), + (tts, timestamp_set), + (tts, period), + (tts, period_set), + (tts, 'AAA'), + (tts, 'BBB'), + # (tts, ['AAA','BBB']), + + (ttss, timestamp), + (ttss, timestamp_set), + (ttss, period), + (ttss, period_set), + (ttss, 'AAA'), + (ttss, 'BBB'), + # (ttss, ['AAA','BBB']), + ], + ids=['Instant-Timestamp', 'Instant-TimestampSet', 'Instant-Period', + 'Instant-PeriodSet', 'Instant-AAA', 'Instant-BBB', # 'Instant-[AAA,BBB]', + 'Discrete Sequence-Timestamp', 'Discrete Sequence-TimestampSet', + 'Discrete Sequence-Period', 'Discrete Sequence-PeriodSet', + 'Discrete Sequence-AAA', 'Discrete Sequence-BBB', # 'Discrete Sequence-[AAA,BBB]', + 'Sequence-Timestamp', 'Sequence-TimestampSet', 'Sequence-Period', + 'Sequence-PeriodSet', 'Sequence-AAA', 'Sequence-BBB', # 'Sequence-[AAA,BBB]', + 'SequenceSet-Timestamp', 'SequenceSet-TimestampSet', 'SequenceSet-Period', + 'SequenceSet-PeriodSet', 'SequenceSet-AAA', 'SequenceSet-BBB', + # 'SequenceSet-[AAA,BBB]' + ] + ) + def test_at_minus(self, temporal, restrictor): + assert TText.merge(temporal.at(restrictor), temporal.minus(restrictor)) == temporal + + @pytest.mark.parametrize( + 'temporal', + [tti, ttds, tts, ttss], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_at_minus_min_max(self, temporal): + assert TText.merge(temporal.at_min(), temporal.minus_min()) == temporal + assert TText.merge(temporal.at_max(), temporal.minus_max()) == temporal + + +class TestTTextTopologicalFunctions(TestTText): + tti = TTextInst('AAA@2019-09-01') + ttds = TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}') + tts = TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]') + ttss = TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02], [AAA@2019-09-03, AAA@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tti, TTextInst('AAA@2019-09-02'), False), + (tti, TTextSeq('(AAA@2019-09-01, BBB@2019-09-02]'), True), + (ttds, TTextInst('AAA@2019-09-03'), False), + (ttds, TTextSeq('(AAA@2019-09-02, BBB@2019-09-03]'), True), + (tts, TTextInst('AAA@2019-09-03'), False), + (tts, TTextSeq('(AAA@2019-09-02, BBB@2019-09-03]'), True), + (ttss, TTextInst('AAA@2019-09-08'), False), + (ttss, TTextSeq('(AAA@2019-09-05, BBB@2019-09-06]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_temporally_adjacent(self, temporal, argument, expected): + assert temporal.is_adjacent(argument) == expected + assert temporal.is_temporally_adjacent(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tti, TTextInst('AAA@2019-09-02'), False), + (tti, TTextSeq('[AAA@2019-09-01,BBB@2019-09-02]'), True), + (ttds, TTextInst('AAA@2019-09-02'), False), + (ttds, TTextSeq('[AAA@2019-09-01,BBB@2019-09-02]'), True), + (tts, TTextInst('AAA@2019-09-02'), False), + (tts, TTextSeq('[AAA@2019-09-01,BBB@2019-09-05]'), True), + (ttss, TTextInst('AAA@2019-09-02'), False), + (ttss, TTextSeq('[AAA@2019-09-01,BBB@2019-09-05]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_temporally_contained_in_contains(self, temporal, argument, expected): + assert temporal.is_contained_in(argument) == expected + assert argument.contains(temporal) == expected + assert temporal.is_temporally_contained_in(argument) == expected + assert argument.temporally_contains(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tti, TTextInst('AAA@2019-09-02'), False), + (tti, TTextSeq('[AAA@2019-09-01]'), True), + (ttds, TTextInst('CCC@2019-09-03'), False), + (ttds, TTextSeq('[AAA@2019-09-01,BBB@2019-09-02]'), True), + (tts, TTextInst('CCC@2019-09-03'), False), + (tts, TTextSeq('[AAA@2019-09-01,BBB@2019-09-02]'), True), + (ttss, TTextInst('CCC@2019-09-06'), False), + (ttss, TTextSeq('[AAA@2019-09-01,BBB@2019-09-05]'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_overlaps_is_same(self, temporal, argument, expected): + assert temporal.overlaps(argument) == expected + assert temporal.is_same(argument) == expected + + +class TestTTextPositionFunctions(TestTText): + tti = TTextInst('AAA@2019-09-01') + ttds = TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}') + tts = TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]') + ttss = TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02], [AAA@2019-09-03, AAA@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tti, TTextInst('AAA@2019-09-01'), False), + (tti, TTextInst('AAA@2019-10-01'), True), + (ttds, TTextInst('AAA@2019-09-01'), False), + (ttds, TTextInst('AAA@2019-10-01'), True), + (tts, TTextInst('AAA@2019-09-01'), False), + (tts, TTextInst('AAA@2019-10-01'), True), + (ttss, TTextInst('AAA@2019-09-01'), False), + (ttss, TTextInst('AAA@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_before_after(self, temporal, argument, expected): + assert temporal.is_before(argument) == expected + assert argument.is_after(temporal) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tti, TTextInst('AAA@2019-08-01'), False), + (tti, TTextInst('AAA@2019-10-01'), True), + (ttds, TTextInst('AAA@2019-08-01'), False), + (ttds, TTextInst('AAA@2019-10-01'), True), + (tts, TTextInst('AAA@2019-08-01'), False), + (tts, TTextInst('AAA@2019-10-01'), True), + (ttss, TTextInst('AAA@2019-08-01'), False), + (ttss, TTextInst('AAA@2019-10-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_before(self, temporal, argument, expected): + assert temporal.is_over_or_before(argument) == expected + + @pytest.mark.parametrize( + 'temporal, argument, expected', + [ + (tti, TTextInst('AAA@2019-10-01'), False), + (tti, TTextInst('AAA@2019-09-01'), True), + (ttds, TTextInst('AAA@2019-10-01'), False), + (ttds, TTextInst('AAA@2019-09-01'), True), + (tts, TTextInst('AAA@2019-10-01'), False), + (tts, TTextInst('AAA@2019-09-01'), True), + (ttss, TTextInst('AAA@2019-10-01'), False), + (ttss, TTextInst('AAA@2019-09-01'), True), + ], + ids=['Instant False', 'Instant True', 'Discrete Sequence False', 'Discrete Sequence True', + 'Sequence False', 'Sequence True', 'Sequence Set False', 'Sequence Set True'] + ) + def test_is_over_or_after(self, temporal, argument, expected): + assert temporal.is_over_or_after(argument) == expected + + +class TestTTextComparisons(TestTText): + tt = TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]') + other = TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}') + + def test_eq(self): + _ = self.tt == self.other + + def test_ne(self): + _ = self.tt != self.other + + def test_lt(self): + _ = self.tt < self.other + + def test_le(self): + _ = self.tt <= self.other + + def test_gt(self): + _ = self.tt > self.other + + def test_ge(self): + _ = self.tt >= self.other + + +class TestTTextEverAlwaysComparisons(TestTText): + tti = TTextInst('AAA@2019-09-01') + ttds = TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}') + tts = TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]') + ttss = TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, True), + (ttds, False), + (tts, False), + (ttss, False) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_always_AAA(self, temporal, expected): + assert temporal.always_equal('AAA') == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, False), + (ttds, False), + (tts, False), + (ttss, False) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_always_BBB(self, temporal, expected): + assert temporal.always_equal('BBB') == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, True), + (ttds, True), + (tts, True), + (ttss, True) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_ever_AAA(self, temporal, expected): + assert temporal.ever_equal('AAA') == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, False), + (ttds, True), + (tts, True), + (ttss, True) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_ever_BBB(self, temporal, expected): + assert temporal.ever_equal('BBB') == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, False), + (ttds, False), + (tts, False), + (ttss, False) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_never_AAA(self, temporal, expected): + assert temporal.never_equal('AAA') == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, True), + (ttds, False), + (tts, False), + (ttss, False) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_never_BBB(self, temporal, expected): + assert temporal.never_equal('BBB') == expected + + +class TestTTextTemporalComparisons(TestTText): + tti = TTextInst('AAA@2019-09-01') + ttds = TTextSeq('{AAA@2019-09-01, BBB@2019-09-02}') + tts = TTextSeq('[AAA@2019-09-01, BBB@2019-09-02]') + ttss = TTextSeqSet('{[AAA@2019-09-01, BBB@2019-09-02],[AAA@2019-09-03, AAA@2019-09-05]}') + argument = TTextSeq('[BBB@2019-09-01, AAA@2019-09-02, AAA@2019-09-03]') + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, TBoolInst('False@2019-09-01')), + (ttds, TBoolSeq('{False@2019-09-01, False@2019-09-02}')), + (tts, TBoolSeq('[False@2019-09-01, False@2019-09-02]')), + (ttss, TBoolSeqSet('{[False@2019-09-01, False@2019-09-02],[True@2019-09-03]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_equal_temporal(self, temporal, expected): + assert temporal.temporal_equal(self.argument) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, TBoolInst('True@2019-09-01')), + (ttds, TBoolSeq('{True@2019-09-01, False@2019-09-02}')), + (tts, TBoolSeq('[True@2019-09-01, False@2019-09-02]')), + (ttss, TBoolSeqSet('{[True@2019-09-01, False@2019-09-02],[True@2019-09-03, True@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_equal_int(self, temporal, expected): + assert temporal.temporal_equal('AAA') == expected + + assert temporal.temporal_equal('BBB') == ~expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, TBoolInst('True@2019-09-01')), + (ttds, TBoolSeq('{True@2019-09-01, True@2019-09-02}')), + (tts, TBoolSeq('[True@2019-09-01, True@2019-09-02]')), + (ttss, TBoolSeqSet('{[True@2019-09-01, True@2019-09-02],[False@2019-09-03]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_not_equal_temporal(self, temporal, expected): + assert temporal.temporal_not_equal(self.argument) == expected + + @pytest.mark.parametrize( + 'temporal, expected', + [ + (tti, TBoolInst('False@2019-09-01')), + (ttds, TBoolSeq('{False@2019-09-01, True@2019-09-02}')), + (tts, TBoolSeq('[False@2019-09-01, True@2019-09-02]')), + (ttss, TBoolSeqSet('{[False@2019-09-01, True@2019-09-02],[False@2019-09-03, False@2019-09-05]}')) + ], + ids=['Instant', 'Discrete Sequence', 'Sequence', 'SequenceSet'] + ) + def test_temporal_not_equal_int(self, temporal, expected): + assert temporal.temporal_not_equal('AAA') == expected + assert temporal.temporal_not_equal('BBB') == ~expected + + diff --git a/pymeos/tests/temporal/__init__.py b/pymeos/tests/temporal/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymeos/tests/temporal/interpolation_test.py b/pymeos/tests/temporal/interpolation_test.py new file mode 100644 index 00000000..685c528c --- /dev/null +++ b/pymeos/tests/temporal/interpolation_test.py @@ -0,0 +1,27 @@ +from pymeos import TInterpolation +from tests.conftest import TestPyMEOS +import pytest + + +class TestTInterpolation(TestPyMEOS): + + @pytest.mark.parametrize( + 'source, result', + [ + ('discrete', TInterpolation.DISCRETE), + ('linear', TInterpolation.LINEAR), + ('stepwise', TInterpolation.STEPWISE), + ('step', TInterpolation.STEPWISE), + ('none', TInterpolation.NONE), + ], + ids=['discrete', 'linear', 'stepwise', 'step', 'none'] + ) + def test_from_string(self, source, result): + assert TInterpolation.from_string(source) == result + + def test_from_string_invalid(self): + assert TInterpolation.from_string('invalid') == TInterpolation.NONE + + def test_from_string_invalid_none(self): + with pytest.raises(ValueError): + TInterpolation.from_string('invalid', none=False) diff --git a/pymeos/tests/time/__init__.py b/pymeos/tests/time/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pymeos/tests/time/period_test.py b/pymeos/tests/time/period_test.py new file mode 100644 index 00000000..cd8a3506 --- /dev/null +++ b/pymeos/tests/time/period_test.py @@ -0,0 +1,363 @@ +from copy import copy +from datetime import datetime, timezone, timedelta + +import pytest + +from pymeos import Period, PeriodSet, TimestampSet, TBox, STBox, TFloatInst, TFloatSeq, TFloatSeqSet + +from tests.conftest import TestPyMEOS + + +class TestPeriod(TestPyMEOS): + period = Period('(2019-09-08 00:00:00+0, 2019-09-10 00:00:00+0)') + + @staticmethod + def assert_period_equality(period: Period, + lower: datetime = None, + upper: datetime = None, + lower_inc: bool = None, + upper_inc: bool = None): + if lower is not None: + assert period.lower() == lower + if upper is not None: + assert period.upper() == upper + if lower_inc is not None: + assert period.lower_inc() == lower_inc + if upper_inc is not None: + assert period.upper_inc() == upper_inc + + +class TestPeriodConstructors(TestPeriod): + + @pytest.mark.parametrize( + 'source, params', + [ + ('(2019-09-08 00:00:00+0, 2019-09-10 00:00:00+0)', + (datetime(2019, 9, 8, tzinfo=timezone.utc), datetime(2019, 9, 10, tzinfo=timezone.utc), False, False)), + ('[2019-09-08 00:00:00+0, 2019-09-10 00:00:00+0]', + (datetime(2019, 9, 8, tzinfo=timezone.utc), datetime(2019, 9, 10, tzinfo=timezone.utc), True, True)), + ] + ) + def test_string_constructor(self, source, params): + period = Period(source) + self.assert_period_equality(period, *params) + + @pytest.mark.parametrize( + 'input_lower,input_upper,lower,upper', + [ + ('2019-09-08 00:00:00+0', '2019-09-10 00:00:00+0', + datetime(2019, 9, 8, tzinfo=timezone.utc), datetime(2019, 9, 10, tzinfo=timezone.utc)), + (datetime(2019, 9, 8, tzinfo=timezone.utc), datetime(2019, 9, 10, tzinfo=timezone.utc), + datetime(2019, 9, 8, tzinfo=timezone.utc), datetime(2019, 9, 10, tzinfo=timezone.utc)), + (datetime(2019, 9, 8, tzinfo=timezone.utc), '2019-09-10 00:00:00+0', + datetime(2019, 9, 8, tzinfo=timezone.utc), datetime(2019, 9, 10, tzinfo=timezone.utc)), + ], + ids=['string', 'datetime', 'mixed'] + ) + def test_constructor_bounds(self, input_lower, input_upper, lower, upper): + period = Period(lower=lower, upper=upper) + self.assert_period_equality(period, lower, upper) + + def test_constructor_bound_inclusivity_defaults(self): + period = Period(lower='2019-09-08 00:00:00+0', upper='2019-09-10 00:00:00+0') + self.assert_period_equality(period, lower_inc=True, upper_inc=False) + + @pytest.mark.parametrize( + 'lower,upper', + [ + (True, True), + (True, False), + (False, True), + (False, False), + ] + ) + def test_constructor_bound_inclusivity(self, lower, upper): + period = Period(lower='2019-09-08 00:00:00+0', upper='2019-09-10 00:00:00+0', lower_inc=lower, upper_inc=upper) + self.assert_period_equality(period, lower_inc=lower, upper_inc=upper) + + def test_hexwkb_constructor(self): + source = '012100000040021FFE3402000000B15A26350200' + period = Period.from_hexwkb(source) + self.assert_period_equality(period, datetime(2019, 9, 8, tzinfo=timezone.utc), + datetime(2019, 9, 10, tzinfo=timezone.utc), False, False) + + def test_from_as_constructor(self): + assert self.period == Period.from_wkb(self.period.as_wkb()) + assert self.period == Period.from_hexwkb(self.period.as_hexwkb()) + + def test_copy_constructor(self): + other = copy(self.period) + assert self.period == other + assert self.period is not other + + +class TestPeriodOutputs(TestPeriod): + + def test_str(self): + assert str(self.period) == '(2019-09-08 00:00:00+00, 2019-09-10 00:00:00+00)' + + def test_repr(self): + assert repr(self.period) == 'Period((2019-09-08 00:00:00+00, 2019-09-10 00:00:00+00))' + + def test_hexwkb(self): + assert self.period.as_hexwkb() == '012100000040021FFE3402000000B15A26350200' + + +class TestPeriodConversions(TestPeriod): + + def test_to_periodset(self): + periodset = self.period.to_periodset() + assert isinstance(periodset, PeriodSet) + assert periodset.num_periods() == 1 + assert periodset.start_period() == self.period + + +class TestPeriodAccessors(TestPeriod): + period2 = Period('[2019-09-08 02:03:00+0, 2019-09-10 02:03:00+0]') + + def test_lower(self): + assert self.period.lower() == datetime(2019, 9, 8, tzinfo=timezone.utc) + assert self.period2.lower() == datetime(2019, 9, 8, 2, 3, tzinfo=timezone.utc) + + def test_upper(self): + assert self.period.upper() == datetime(2019, 9, 10, tzinfo=timezone.utc) + assert self.period2.upper() == datetime(2019, 9, 10, 2, 3, tzinfo=timezone.utc) + + def test_lower_inc(self): + assert not self.period.lower_inc() + assert self.period2.lower_inc() + + def test_upper_inc(self): + assert not self.period.upper_inc() + assert self.period2.upper_inc() + + def test_duration(self): + assert self.period.duration() == timedelta(days=2) + assert self.period2.duration() == timedelta(days=2) + + def test_duration_in_seconds(self): + assert self.period.duration_in_seconds() == 172800 + assert self.period2.duration_in_seconds() == 172800 + + def test_hash(self): + assert hash(self.period) == 1164402929 + + +class TestPeriodTransformationFunctions(TestPeriod): + + @pytest.mark.parametrize( + 'delta,result', + [(timedelta(days=4), + (datetime(2019, 9, 12, tzinfo=timezone.utc), datetime(2019, 9, 14, tzinfo=timezone.utc), False, False)), + (timedelta(days=-4), + (datetime(2019, 9, 4, tzinfo=timezone.utc), datetime(2019, 9, 6, tzinfo=timezone.utc), False, False)), + (timedelta(hours=2), + (datetime(2019, 9, 8, 2, tzinfo=timezone.utc), datetime(2019, 9, 10, 2, tzinfo=timezone.utc), False, False)), + (timedelta(hours=-2), + (datetime(2019, 9, 7, 22, tzinfo=timezone.utc), datetime(2019, 9, 9, 22, tzinfo=timezone.utc), False, + False)), + ], + ids=['positive days', 'negative days', 'positive hours', 'negative hours'] + ) + def test_shift(self, delta, result): + shifted = self.period.shift(delta) + self.assert_period_equality(shifted, *result) + + @pytest.mark.parametrize( + 'delta,result', + [(timedelta(days=4), + (datetime(2019, 9, 8, tzinfo=timezone.utc), datetime(2019, 9, 12, tzinfo=timezone.utc), False, False)), + (timedelta(hours=2), + (datetime(2019, 9, 8, tzinfo=timezone.utc), datetime(2019, 9, 8, 2, tzinfo=timezone.utc), False, False)), + ], + ids=['days', 'hours'] + ) + def test_tscale(self, delta, result): + scaled = self.period.tscale(delta) + self.assert_period_equality(scaled, *result) + + def test_shift_tscale(self): + shifted_scaled = self.period.shift_tscale(timedelta(days=4), timedelta(hours=4)) + self.assert_period_equality(shifted_scaled, datetime(2019, 9, 12, 0, tzinfo=timezone.utc), + datetime(2019, 9, 12, 4, tzinfo=timezone.utc), False, False) + + def test_expand(self): + expanded = self.period.expand(Period('(2021-01-01 00:00:00+0, 2021-02-01 00:00:00+0)')) + self.assert_period_equality(expanded, datetime(2019, 9, 8, tzinfo=timezone.utc), + datetime(2021, 2, 1, tzinfo=timezone.utc), False, False) + + +class TestPeriodTopologicalPositionFunctions(TestPeriod): + period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') + periodset = PeriodSet( + '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') + timestamp = datetime(year=2020, month=1, day=1) + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') + instant = TFloatInst('1.0@2020-01-01') + discrete_sequence = TFloatSeq('{1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31}') + stepwise_sequence = TFloatSeq('Interp=Step;(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31]') + continuous_sequence = TFloatSeq('(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31]') + sequence_set = TFloatSeqSet('{(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31], ' + '(1.0@2021-01-01, 3.0@2021-01-10, 10.0@2021-01-20, 0.0@2021-01-31]}') + tbox = TBox('TBox XT([0, 10),[2020-01-01, 2020-01-31])') + stbox = STBox('STBOX ZT(((1.0,2.0,3.0),(4.0,5.0,6.0)),[2001-01-01, 2001-01-02])') + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_adjacent(self, other): + self.period.is_adjacent(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, instant, discrete_sequence, stepwise_sequence, continuous_sequence, sequence_set, tbox, + stbox], + ids=['period', 'periodset', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', + 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_contained_in(self, other): + self.period.is_contained_in(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_contains(self, other): + self.period.contains(other) + _ = other in self.period + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_overlaps(self, other): + self.period.overlaps(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_same(self, other): + self.period.is_same(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_before(self, other): + self.period.is_before(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_over_or_before(self, other): + self.period.is_over_or_before(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_after(self, other): + self.period.is_after(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_over_or_after(self, other): + self.period.is_over_or_after(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_distance(self, other): + self.period.distance(other) + + +class TestPeriodSetFunctions(TestPeriod): + period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') + periodset = PeriodSet( + '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') + timestamp = datetime(year=2020, month=1, day=1) + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset], + ids=['period', 'periodset', 'timestamp', 'timestampset'] + ) + def test_intersection(self, other): + self.period.intersection(other) + self.period * other + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset], + ids=['period', 'periodset', 'timestamp', 'timestampset'] + ) + def test_union(self, other): + self.period.union(other) + self.period + other + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset], + ids=['period', 'periodset', 'timestamp', 'timestampset'] + ) + def test_minus(self, other): + self.period.minus(other) + self.period - other + + +class TestPeriodComparisons(TestPeriod): + period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') + other = Period('(2020-01-02 00:00:00+0, 2020-03-31 00:00:00+0)') + + def test_eq(self): + _ = self.period == self.other + + def test_ne(self): + _ = self.period != self.other + + def test_lt(self): + _ = self.period < self.other + + def test_le(self): + _ = self.period <= self.other + + def test_gt(self): + _ = self.period > self.other + + def test_ge(self): + _ = self.period >= self.other + diff --git a/pymeos/tests/time/periodset_test.py b/pymeos/tests/time/periodset_test.py new file mode 100644 index 00000000..e85fc4b2 --- /dev/null +++ b/pymeos/tests/time/periodset_test.py @@ -0,0 +1,342 @@ +from copy import copy +from datetime import datetime, timezone, timedelta +from typing import List + +import pytest + +from pymeos import Period, PeriodSet, TimestampSet, TFloatInst, TFloatSeq, STBox, TFloatSeqSet, TBox +from tests.conftest import TestPyMEOS + + +class TestPeriodSet(TestPyMEOS): + periodset = PeriodSet('{[2019-09-01, 2019-09-02], [2019-09-03, 2019-09-04]}') + + @staticmethod + def assert_periodset_equality(periodset: PeriodSet, periods: List[Period]): + assert periodset.num_periods() == len(periods) + assert periodset.periods() == periods + + +class TestPeriodSetConstructors(TestPeriodSet): + + def test_string_constructor(self): + self.assert_periodset_equality(self.periodset, [ + Period('[2019-09-01, 2019-09-02]'), + Period('[2019-09-03, 2019-09-04]') + ]) + + def test_period_list_constructor(self): + periodset = PeriodSet(period_list=[ + Period('[2019-09-01, 2019-09-02]'), + Period('[2019-09-03, 2019-09-04]') + ]) + self.assert_periodset_equality(periodset, [ + Period('[2019-09-01, 2019-09-02]'), + Period('[2019-09-03, 2019-09-04]') + ]) + + def test_from_as_constructor(self): + assert self.periodset == PeriodSet.from_wkb(self.periodset.as_wkb()) + assert self.periodset == PeriodSet.from_hexwkb(self.periodset.as_hexwkb()) + + def test_copy_constructor(self): + copied = copy(self.periodset) + assert self.periodset == copied + assert self.periodset is not copied + + +class TestPeriodSetOutputs(TestPeriodSet): + + def test_str(self): + assert str(self.periodset) == '{[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00], ' \ + '[2019-09-03 00:00:00+00, 2019-09-04 00:00:00+00]}' + + def test_repr(self): + assert repr(self.periodset) == 'PeriodSet({[2019-09-01 00:00:00+00, 2019-09-02 00:00:00+00], ' \ + '[2019-09-03 00:00:00+00, 2019-09-04 00:00:00+00]})' + + def test_hexwkb(self): + assert self.periodset.as_hexwkb() == '012200020000000300A01E4E713402000000F66B85340200030060CD899934020000C0A4A7AD340200' + +class TestPeriodSetConversions(TestPeriodSet): + + def test_to_period(self): + assert self.periodset.to_period() == Period('[2019-09-01, 2019-09-04]') + + +class TestPeriodSetAccessors(TestPeriodSet): + periodset2 = PeriodSet('{[2019-09-01, 2019-09-02), (2019-09-02, 2019-09-04]}') + + def test_duration(self): + assert self.periodset.duration() == timedelta(days=2) + assert self.periodset.duration(True) == timedelta(days=3) + + def test_num_timestamps(self): + assert self.periodset.num_timestamps() == 4 + assert self.periodset2.num_timestamps() == 3 + + def test_start_timestamp(self): + assert self.periodset.start_timestamp() == datetime(2019, 9, 1, tzinfo=timezone.utc) + + def test_end_timestamp(self): + assert self.periodset.end_timestamp() == datetime(2019, 9, 4, tzinfo=timezone.utc) + + def test_timestamp_n(self): + assert self.periodset.timestamp_n(0) == datetime(2019, 9, 1, tzinfo=timezone.utc) + assert self.periodset.timestamp_n(1) == datetime(2019, 9, 2, tzinfo=timezone.utc) + assert self.periodset.timestamp_n(2) == datetime(2019, 9, 3, tzinfo=timezone.utc) + assert self.periodset.timestamp_n(3) == datetime(2019, 9, 4, tzinfo=timezone.utc) + + def test_timestamps(self): + assert self.periodset.timestamps() == [ + datetime(2019, 9, 1, tzinfo=timezone.utc), + datetime(2019, 9, 2, tzinfo=timezone.utc), + datetime(2019, 9, 3, tzinfo=timezone.utc), + datetime(2019, 9, 4, tzinfo=timezone.utc), + ] + + def test_num_periods(self): + assert self.periodset.num_periods() == 2 + + def test_start_period(self): + assert self.periodset.start_period() == Period('[2019-09-01, 2019-09-02]') + + def test_end_period(self): + assert self.periodset.end_period() == Period('[2019-09-03, 2019-09-04]') + + def test_period_n(self): + assert self.periodset.period_n(0) == Period('[2019-09-01, 2019-09-02]') + assert self.periodset.period_n(1) == Period('[2019-09-03, 2019-09-04]') + + def test_periods(self): + assert self.periodset.periods() == [ + Period('[2019-09-01, 2019-09-02]'), + Period('[2019-09-03, 2019-09-04]') + ] + + def test_hash(self): + assert hash(self.periodset) == 552347465 + + +class TestPeriodTransformationFunctions(TestPeriodSet): + + @pytest.mark.parametrize( + 'delta,result', + [(timedelta(days=4), + [Period('[2019-09-05 00:00:00+0, 2019-09-06 00:00:00+0]'), + Period('[2019-09-07 00:00:00+0, 2019-09-08 00:00:00+0]')]), + (timedelta(days=-4), + [Period('[2019-08-28 00:00:00+0, 2019-08-29 00:00:00+0]'), + Period('[2019-08-30 00:00:00+00, 2019-08-31 00:00:00+00]')]), + (timedelta(hours=2), + [Period('[2019-09-01 02:00:00+0, 2019-09-02 02:00:00+0]'), + Period('[2019-09-03 02:00:00+0, 2019-09-04 02:00:00+0]')]), + (timedelta(hours=-2), + [Period('[2019-08-31 22:00:00+0, 2019-09-01 22:00:00+0]'), + Period('[2019-09-02 22:00:00+0, 2019-09-03 22:00:00+0]')]), + ], + ids=['positive days', 'negative days', 'positive hours', 'negative hours'] + ) + def test_shift(self, delta, result): + shifted = self.periodset.shift(delta) + self.assert_periodset_equality(shifted, result) + + @pytest.mark.parametrize( + 'delta,result', + [(timedelta(days=3), + [Period('[2019-09-01 00:00:00+0, 2019-09-02 00:00:00+0]'), + Period('[2019-09-03 00:00:00+0, 2019-09-04 00:00:00+0]')]), + (timedelta(hours=6), + [Period('[2019-09-01 00:00:00+0, 2019-09-01 02:00:00+0]'), + Period('[2019-09-01 04:00:00+0, 2019-09-01 06:00:00+0]'), ]), + ], + ids=['days', 'hours'] + ) + def test_tscale(self, delta, result): + scaled = self.periodset.tscale(delta) + self.assert_periodset_equality(scaled, result) + + def test_shift_tscale(self): + shifted_scaled = self.periodset.shift_tscale(timedelta(days=4), timedelta(hours=6)) + self.assert_periodset_equality(shifted_scaled, [Period('[2019-09-05 00:00:00+0, 2019-09-05 02:00:00+0]'), + Period('[2019-09-05 04:00:00+0, 2019-09-05 06:00:00+0]')]) + + +class TestPeriodSetTopologicalPositionFunctions(TestPeriodSet): + period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') + periodset = PeriodSet( + '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') + timestamp = datetime(year=2020, month=1, day=1) + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') + instant = TFloatInst('1.0@2020-01-01') + discrete_sequence = TFloatSeq('{1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31}') + stepwise_sequence = TFloatSeq('Interp=Step;(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31]') + continuous_sequence = TFloatSeq('(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31]') + sequence_set = TFloatSeqSet('{(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31], ' + '(1.0@2021-01-01, 3.0@2021-01-10, 10.0@2021-01-20, 0.0@2021-01-31]}') + tbox = TBox('TBox XT([0, 10),[2020-01-01, 2020-01-31])') + stbox = STBox('STBOX ZT(((1.0,2.0,3.0),(4.0,5.0,6.0)),[2001-01-01, 2001-01-02])') + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_adjacent(self, other): + self.periodset.is_adjacent(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, instant, discrete_sequence, stepwise_sequence, continuous_sequence, sequence_set, tbox, + stbox], + ids=['period', 'periodset', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', + 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_contained_in(self, other): + self.periodset.is_contained_in(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_contains(self, other): + self.periodset.contains(other) + _ = other in self.periodset + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_overlaps(self, other): + self.periodset.overlaps(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_same(self, other): + self.periodset.is_same(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_before(self, other): + self.periodset.is_before(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_over_or_before(self, other): + self.periodset.is_over_or_before(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_after(self, other): + self.periodset.is_after(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_over_or_after(self, other): + self.periodset.is_over_or_after(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_distance(self, other): + self.periodset.distance(other) + + +class TestPeriodSetSetFunctions(TestPeriodSet): + period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') + periodset = PeriodSet( + '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') + timestamp = datetime(year=2020, month=1, day=1) + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset], + ids=['period', 'periodset', 'timestamp', 'timestampset'] + ) + def test_intersection(self, other): + self.periodset.intersection(other) + self.periodset * other + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset], + ids=['period', 'periodset', 'timestamp', 'timestampset'] + ) + def test_union(self, other): + self.periodset.union(other) + self.periodset + other + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset], + ids=['period', 'periodset', 'timestamp', 'timestampset'] + ) + def test_minus(self, other): + self.periodset.minus(other) + self.periodset - other + + +class TestPeriodComparisons(TestPeriodSet): + periodset = PeriodSet( + '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') + other = PeriodSet( + '{(2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0), (2022-01-01 00:00:00+0, 2022-01-31 00:00:00+0)}') + + def test_eq(self): + _ = self.periodset == self.other + + def test_ne(self): + _ = self.periodset != self.other + + def test_lt(self): + _ = self.periodset < self.other + + def test_le(self): + _ = self.periodset <= self.other + + def test_gt(self): + _ = self.periodset > self.other + + def test_ge(self): + _ = self.periodset >= self.other + + + diff --git a/pymeos/tests/time/timestampset_test.py b/pymeos/tests/time/timestampset_test.py new file mode 100644 index 00000000..e91e0abd --- /dev/null +++ b/pymeos/tests/time/timestampset_test.py @@ -0,0 +1,334 @@ +from copy import copy +from datetime import datetime, timezone, timedelta +from typing import List + +import pytest + +from pymeos import Period, PeriodSet, TimestampSet, TFloatInst, TFloatSeq, STBox, TFloatSeqSet, TBox +from tests.conftest import TestPyMEOS + + +class TestTimestampSet(TestPyMEOS): + ts_set = TimestampSet('{2019-09-01 00:00:00+0, 2019-09-02 00:00:00+0, 2019-09-03 00:00:00+0}') + + @staticmethod + def assert_timestampset_equality(ts_set: TimestampSet, + timestamps: List[datetime]): + assert ts_set.num_timestamps() == len(timestamps) + assert ts_set.timestamps() == timestamps + + +class TestTimestampSetConstructors(TestTimestampSet): + + def test_string_constructor(self): + self.assert_timestampset_equality(self.ts_set, [datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc)]) + + def test_list_constructor(self): + ts_set = TimestampSet(timestamp_list=[datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc)]) + self.assert_timestampset_equality(ts_set, [datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc)]) + + def test_hexwkb_constructor(self): + ts_set = TimestampSet.from_hexwkb('012000010300000000A01E4E713402000000F66B853402000060CD8999340200') + self.assert_timestampset_equality(ts_set, [datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc)]) + + def test_from_as_constructor(self): + assert self.ts_set == TimestampSet.from_wkb(self.ts_set.as_wkb()) + assert self.ts_set == TimestampSet.from_hexwkb(self.ts_set.as_hexwkb()) + + def test_copy_constructor(self): + ts_set_copy = copy(self.ts_set) + assert self.ts_set == ts_set_copy + assert self.ts_set is not ts_set_copy + + +class TestTimestampSetOutputs(TestTimestampSet): + + def test_str(self): + assert str(self.ts_set) == '{"2019-09-01 00:00:00+00", "2019-09-02 00:00:00+00", "2019-09-03 00:00:00+00"}' + + def test_repr(self): + assert repr(self.ts_set) == 'TimestampSet({"2019-09-01 00:00:00+00", "2019-09-02 00:00:00+00", "2019-09-03 00:00:00+00"})' + + def test_as_hexwkb(self): + assert self.ts_set.as_hexwkb() == '012000010300000000A01E4E713402000000F66B853402000060CD8999340200' + + +class TestTimestampConversions(TestTimestampSet): + + def test_to_periodset(self): + assert self.ts_set.to_periodset() == PeriodSet( + '{[2019-09-01 00:00:00+00, 2019-09-01 00:00:00+00], ' + '[2019-09-02 00:00:00+00, 2019-09-02 00:00:00+00], ' + '[2019-09-03 00:00:00+00, 2019-09-03 00:00:00+00]}') + + +class TestTimestampSetAccessors(TestTimestampSet): + + def test_duration(self): + assert self.ts_set.duration() == timedelta(days=2) + + def test_period(self): + assert self.ts_set.period() == Period('[2019-09-01 00:00:00+00, 2019-09-03 00:00:00+00]') + + def test_num_timestamps(self): + assert self.ts_set.num_timestamps() == 3 + + def test_start_timestamp(self): + assert self.ts_set.start_timestamp() == datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc) + + def test_end_timestamp(self): + assert self.ts_set.end_timestamp() == datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc) + + def test_timestamp_n(self): + assert self.ts_set.timestamp_n(1) == datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc) + + def test_timestamp_n_out_of_range(self): + with pytest.raises(IndexError): + self.ts_set.timestamp_n(3) + + def test_timestamps(self): + assert self.ts_set.timestamps() == [datetime(2019, 9, 1, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 2, 0, 0, 0, tzinfo=timezone.utc), + datetime(2019, 9, 3, 0, 0, 0, tzinfo=timezone.utc), + ] + + def test_hash(self): + assert hash(self.ts_set) == 527267058 + + +class TestTimestampSetPositionFunctions(TestTimestampSet): + period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') + periodset = PeriodSet( + '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') + timestamp = datetime(year=2020, month=1, day=1) + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') + instant = TFloatInst('1.0@2020-01-01') + discrete_sequence = TFloatSeq('{1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31}') + stepwise_sequence = TFloatSeq('Interp=Step;(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31]') + continuous_sequence = TFloatSeq('(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31]') + sequence_set = TFloatSeqSet('{(1.0@2020-01-01, 3.0@2020-01-10, 10.0@2020-01-20, 0.0@2020-01-31], ' + '(1.0@2021-01-01, 3.0@2021-01-10, 10.0@2021-01-20, 0.0@2021-01-31]}') + tbox = TBox('TBox XT([0, 10),[2020-01-01, 2020-01-31])') + stbox = STBox('STBOX ZT(((1.0,2.0,3.0),(4.0,5.0,6.0)),[2001-01-01, 2001-01-02])') + + @pytest.mark.parametrize( + 'other', + [period, periodset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_adjacent(self, other): + self.timestampset.is_adjacent(other) + + @pytest.mark.parametrize( + 'other', + [timestampset, period, periodset, instant, discrete_sequence, stepwise_sequence, continuous_sequence, sequence_set, tbox, + stbox], + ids=['timestampset', 'period', 'periodset', 'instant', 'discrete_sequence', 'stepwise_sequence', 'continuous_sequence', + 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_contained_in(self, other): + self.timestampset.is_contained_in(other) + + @pytest.mark.parametrize( + 'other', + [timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence], + ids=['timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set'] + ) + def test_contains(self, other): + self.timestampset.contains(other) + _ = other in self.timestampset + + # + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_overlaps(self, other): + self.timestampset.overlaps(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_same(self, other): + self.periodset.is_same(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_before(self, other): + self.timestampset.is_before(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_over_or_before(self, other): + self.timestampset.is_over_or_before(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_after(self, other): + self.timestampset.is_after(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_is_over_or_after(self, other): + self.timestampset.is_over_or_after(other) + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset, instant, discrete_sequence, stepwise_sequence, sequence_set, + continuous_sequence, tbox, stbox], + ids=['period', 'periodset', 'timestamp', 'timestampset', 'instant', 'discrete_sequence', 'stepwise_sequence', + 'continuous_sequence', 'sequence_set', 'tbox', 'stbox'] + ) + def test_distance(self, other): + self.timestampset.distance(other) + + +class TestTimestampSetSetFunctions(TestTimestampSet): + timestamp = datetime(year=2020, month=1, day=1) + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') + period = Period('(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0)') + periodset = PeriodSet( + '{(2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0), (2021-01-01 00:00:00+0, 2021-01-31 00:00:00+0)}') + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset], + ids=['period', 'periodset', 'timestamp', 'timestampset'] + ) + def test_intersection(self, other): + self.timestampset.intersection(other) + self.timestampset * other + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset], + ids=['period', 'periodset', 'timestamp', 'timestampset'] + ) + def test_union(self, other): + self.timestampset.union(other) + self.timestampset + other + + @pytest.mark.parametrize( + 'other', + [period, periodset, timestamp, timestampset], + ids=['period', 'periodset', 'timestamp', 'timestampset'] + ) + def test_minus(self, other): + self.timestampset.minus(other) + self.timestampset - other + + +class TestTimestampSetComparisons(TestTimestampSet): + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-31 00:00:00+0}') + other = TimestampSet('{2020-01-02 00:00:00+0, 2020-03-31 00:00:00+0}') + + def test_eq(self): + _ = self.timestampset == self.other + + def test_ne(self): + _ = self.timestampset != self.other + + def test_lt(self): + _ = self.timestampset < self.other + + def test_le(self): + _ = self.timestampset <= self.other + + def test_gt(self): + _ = self.timestampset > self.other + + def test_ge(self): + _ = self.timestampset >= self.other + + +class TestTimestampSetManipulationFunctions(TestTimestampSet): + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-02 00:00:00+0, 2020-01-04 00:00:00+0}') + + @pytest.mark.parametrize( + 'delta,result', + [(timedelta(days=4), + [datetime(2020, 1, 5, tzinfo=timezone.utc), datetime(2020, 1, 6, tzinfo=timezone.utc), + datetime(2020, 1, 8, tzinfo=timezone.utc)]), + (timedelta(days=-4), + [datetime(2019, 12, 28, tzinfo=timezone.utc), datetime(2019, 12, 29, tzinfo=timezone.utc), + datetime(2019, 12, 31, tzinfo=timezone.utc)]), + (timedelta(hours=2), + [datetime(2020, 1, 1, 2, tzinfo=timezone.utc), datetime(2020, 1, 2, 2, tzinfo=timezone.utc), + datetime(2020, 1, 4, 2, tzinfo=timezone.utc)]), + (timedelta(hours=-2), + [datetime(2019, 12, 31, 22, tzinfo=timezone.utc), datetime(2020, 1, 1, 22, tzinfo=timezone.utc), + datetime(2020, 1, 3, 22, tzinfo=timezone.utc)]), + ], + ids=['positive days', 'negative days', 'positive hours', 'negative hours'] + ) + def test_shift(self, delta, result): + shifted = self.timestampset.shift(delta) + self.assert_timestampset_equality(shifted, result) + + @pytest.mark.parametrize( + 'delta,result', + [(timedelta(days=6), + [datetime(2020, 1, 1, tzinfo=timezone.utc), datetime(2020, 1, 3, tzinfo=timezone.utc), + datetime(2020, 1, 7, tzinfo=timezone.utc)]), + (timedelta(hours=3), + [datetime(2020, 1, 1, tzinfo=timezone.utc), datetime(2020, 1, 1, 1, tzinfo=timezone.utc), + datetime(2020, 1, 1, 3, tzinfo=timezone.utc)]), + ], + ids=['days', 'hours'] + ) + def test_tscale(self, delta, result): + scaled = self.timestampset.tscale(delta) + self.assert_timestampset_equality(scaled, result) + + def test_shift_tscale(self): + shifted_scaled = self.timestampset.shift_tscale(timedelta(days=4), timedelta(hours=3)) + self.assert_timestampset_equality(shifted_scaled, + [datetime(2020, 1, 5, tzinfo=timezone.utc), + datetime(2020, 1, 5, 1, tzinfo=timezone.utc), + datetime(2020, 1, 5, 3, tzinfo=timezone.utc)]) + + +class TestTimestampSetMiscFunctions(TestTimestampSet): + timestampset = TimestampSet('{2020-01-01 00:00:00+0, 2020-01-02 00:00:00+0, 2020-01-04 00:00:00+0}') + + def test_hash(self): + hash(self.timestampset) diff --git a/pymeos_cffi/LICENSE b/pymeos_cffi/LICENSE index 4780db0b..853efa53 100644 --- a/pymeos_cffi/LICENSE +++ b/pymeos_cffi/LICENSE @@ -1,16 +1,21 @@ +PostgreSQL License + ------------------------------------------------------------------------------- -This PyMEOS code is provided under The PostgreSQL License. +This PyMEOS CFFI code is provided under The PostgreSQL License. + +Copyright (c) 2020, Université libre de Bruxelles and PyMEOS CFFI contributors -Copyright (c) 2020, Université libre de Bruxelles and MobilityDB contributors +PyMEOS CFFI includes MEOS, which includes portions of PostGIS version 3 source code released under the GNU General +Public License (GPLv2 or later). Copyright (c) 2001-2023, PostGIS contributors Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. -IN NO EVENT SHALL UNIVERSITE LIBRE DE BRUXELLES BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST -PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF UNIVERSITE LIBRE DE BRUXELLES HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +IN NO EVENT SHALL UNIVERSITÉ LIBRE DE BRUXELLES AND PYMEOS CFFI CONTRIBUTORS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST +PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF UNIVERSITÉ LIBRE DE BRUXELLES AND PYMEOS CFFI CONTRIBUTORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -UNIVERSITE LIBRE DE BRUXELLES SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND UNIVERSITE LIBRE DE BRUXELLES HAS NO OBLIGATIONS TO PROVIDE +UNIVERSITÉ LIBRE DE BRUXELLES AND PYMEOS CFFI CONTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND UNIVERSITÉ LIBRE DE BRUXELLES AND PYMEOS CFFI CONTRIBUTORS HAVE NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. ------------------------------------------------------------------------------- \ No newline at end of file diff --git a/pymeos_cffi/docker/Dockerfile b/pymeos_cffi/docker/Dockerfile index 10adf9b7..d6cd2a13 100644 --- a/pymeos_cffi/docker/Dockerfile +++ b/pymeos_cffi/docker/Dockerfile @@ -1,16 +1,13 @@ -FROM pymeos/mobilitydb:latest +FROM pymeos/meos:latest WORKDIR MobilityDB RUN git fetch -RUN git checkout b7ebd168 +RUN git checkout develop -RUN sed -i '730 c\ ' ./meos/include/meos.h -RUN sed -i '21 c\ tpoint_tempspatialrels_meos.c' ./meos/src/point/CMakeLists.txt - WORKDIR build -RUN cmake .. -DMEOS=on -DGEOS_INCLUDE_DIR=/usr/geos39/include/ -DGEOS_LIBRARY=/usr/geos39/lib64/libgeos_c.so +RUN cmake .. -DMEOS=on -DGEOS_INCLUDE_DIR=/usr/geos39/include/ -DGEOS_LIBRARY=/usr/geos39/lib64/libgeos_c.so -DGEOS_CONFIG=/usr/geos39/bin/geos-config RUN make -j RUN make install diff --git a/pymeos_cffi/docker/build_wheels.sh b/pymeos_cffi/docker/build_wheels.sh index d8b415b6..bd2aba6e 100644 --- a/pymeos_cffi/docker/build_wheels.sh +++ b/pymeos_cffi/docker/build_wheels.sh @@ -13,7 +13,7 @@ function repair_wheel { fi } -for PYBIN in /opt/python/*/bin; do +for PYBIN in /opt/python/cp*/bin; do echo "================START $PYBIN================" echo "================COMPILE================" "${PYBIN}/pip" install -r /PyMEOS/pymeos_cffi/dev-requirements.txt diff --git a/pymeos_cffi/pymeos_cffi/__init__.py b/pymeos_cffi/pymeos_cffi/__init__.py index 5b1e6adb..c306c439 100644 --- a/pymeos_cffi/pymeos_cffi/__init__.py +++ b/pymeos_cffi/pymeos_cffi/__init__.py @@ -31,6 +31,7 @@ 'meos_finalize', 'bool_in', 'bool_out', + 'cstring2text', 'pg_date_in', 'pg_date_out', 'pg_interval_cmp', @@ -48,6 +49,12 @@ 'pg_timestamp_pl_interval', 'pg_timestamptz_in', 'pg_timestamptz_out', + 'text2cstring', + 'pg_timestamp_to_char', + 'pg_timestamptz_to_char', + 'pg_interval_to_char', + 'pg_to_timestamp', + 'pg_to_date', 'gserialized_as_ewkb', 'gserialized_as_ewkt', 'gserialized_as_geojson', @@ -72,10 +79,12 @@ 'floatspan_out', 'floatspanset_in', 'floatspanset_out', + 'geogset_in', 'geogset_out', + 'geomset_in', 'geomset_out', - 'geoset_as_text', 'geoset_as_ewkt', + 'geoset_as_text', 'intset_in', 'intset_out', 'intspan_in', @@ -86,8 +95,8 @@ 'period_out', 'periodset_in', 'periodset_out', - 'set_as_wkb', 'set_as_hexwkb', + 'set_as_wkb', 'set_from_hexwkb', 'set_from_wkb', 'set_out', @@ -103,32 +112,36 @@ 'spanset_out', 'textset_in', 'textset_out', - 'tstzset_in', - 'tstzset_out', + 'timestampset_in', + 'timestampset_out', + 'bigintset_make', 'bigintspan_make', + 'floatset_make', 'floatspan_make', + 'geogset_make', + 'geomset_make', + 'intset_make', 'intspan_make', + 'period_make', 'set_copy', - 'tstzspan_make', 'span_copy', 'spanset_copy', 'spanset_make', 'spanset_make_exp', 'spanset_make_free', - 'tstzset_make', + 'textset_make', + 'timestampset_make', 'bigint_to_bigintset', 'bigint_to_bigintspan', - 'float_to_floaspan', + 'bigint_to_bigintspanset', 'float_to_floatset', + 'float_to_floatspan', + 'float_to_floatspanset', 'int_to_intset', 'int_to_intspan', - 'set_set_span', - 'set_to_span', + 'int_to_intspanset', 'set_to_spanset', 'span_to_spanset', - 'spanset_to_span', - 'spatialset_set_stbox', - 'spatialset_to_stbox', 'timestamp_to_period', 'timestamp_to_periodset', 'timestamp_to_tstzset', @@ -148,6 +161,7 @@ 'floatspan_upper', 'floatspanset_lower', 'floatspanset_upper', + 'geoset_srid', 'intset_end_value', 'intset_start_value', 'intset_value_n', @@ -156,14 +170,6 @@ 'intspan_upper', 'intspanset_lower', 'intspanset_upper', - 'set_end_value', - 'set_hash', - 'set_hash_extended', - 'set_mem_size', - 'set_num_values', - 'set_start_value', - 'set_value_n', - 'set_values', 'period_duration', 'period_lower', 'period_upper', @@ -175,6 +181,11 @@ 'periodset_timestamp_n', 'periodset_timestamps', 'periodset_upper', + 'set_hash', + 'set_hash_extended', + 'set_mem_size', + 'set_num_values', + 'set_span', 'span_hash', 'span_hash_extended', 'span_lower_inc', @@ -186,17 +197,22 @@ 'spanset_lower_inc', 'spanset_mem_size', 'spanset_num_spans', + 'spanset_span', 'spanset_span_n', 'spanset_spans', 'spanset_start_span', 'spanset_upper_inc', 'spanset_width', - 'tstzset_end_timestamp', - 'tstzset_start_timestamp', - 'tstzset_timestamp_n', - 'tstzset_values', - 'geoset_srid', + 'spatialset_stbox', + 'timestampset_end_timestamp', + 'timestampset_start_timestamp', + 'timestampset_timestamp_n', + 'timestampset_values', + 'floatset_round', + 'floatspan_round', + 'floatspanset_round', 'floatspan_set_intspan', + 'geoset_round', 'intspan_set_floatspan', 'numspan_set_floatspan', 'period_tprecision', @@ -206,7 +222,7 @@ 'set_shift', 'span_expand', 'timestamp_tprecision', - 'tstzset_shift_tscale', + 'timestampset_shift_tscale', 'adjacent_bigintspan_bigint', 'adjacent_bigintspanset_bigint', 'adjacent_floatspan_float', @@ -319,7 +335,6 @@ 'union_span_span', 'union_spanset_span', 'union_spanset_spanset', - 'union_timestamp_timestampset', 'union_timestampset_timestamp', 'distance_floatspan_float', 'distance_intspan_int', @@ -332,22 +347,25 @@ 'distance_timestampset_timestamp', 'bigint_extent_transfn', 'bigint_union_transfn', - 'int_extent_transfn', - 'int_union_transfn', 'float_extent_transfn', 'float_union_transfn', + 'int_extent_transfn', + 'int_union_transfn', 'period_tcount_transfn', 'periodset_tcount_transfn', + 'set_extent_transfn', 'set_union_finalfn', 'set_union_transfn', 'span_extent_transfn', + 'span_union_transfn', 'spanset_extent_transfn', + 'spanset_union_finalfn', + 'spanset_union_transfn', 'text_union_transfn', 'timestamp_extent_transfn', 'timestamp_tcount_transfn', 'timestamp_union_transfn', - 'set_extent_transfn', - 'tstzset_tcount_transfn', + 'timestampset_tcount_transfn', 'set_cmp', 'set_eq', 'set_ge', @@ -390,7 +408,7 @@ 'int_to_tbox', 'float_to_tbox', 'timestamp_to_tbox', - 'tstzset_to_tbox', + 'timestampset_to_tbox', 'period_to_tbox', 'periodset_to_tbox', 'int_timestamp_to_tbox', @@ -410,15 +428,19 @@ 'stbox_to_geo', 'tpoint_to_stbox', 'timestamp_to_stbox', - 'tstzset_to_stbox', + 'timestampset_to_stbox', 'period_to_stbox', 'periodset_to_stbox', 'tbox_hasx', 'tbox_hast', 'tbox_xmin', + 'tbox_xmin_inc', 'tbox_xmax', + 'tbox_xmax_inc', 'tbox_tmin', + 'tbox_tmin_inc', 'tbox_tmax', + 'tbox_tmax_inc', 'stbox_hasx', 'stbox_hasz', 'stbox_hast', @@ -430,16 +452,20 @@ 'stbox_zmin', 'stbox_zmax', 'stbox_tmin', + 'stbox_tmin_inc', 'stbox_tmax', + 'stbox_tmax_inc', 'stbox_srid', - 'tbox_expand', - 'tbox_expand_value', - 'tbox_expand_time', 'stbox_expand', - 'stbox_set_srid', - 'stbox_get_space', 'stbox_expand_space', 'stbox_expand_time', + 'stbox_get_space', + 'stbox_round', + 'stbox_set_srid', + 'tbox_expand', + 'tbox_expand_value', + 'tbox_expand_time', + 'tbox_round', 'contains_tbox_tbox', 'contained_tbox_tbox', 'overlaps_tbox_tbox', @@ -495,8 +521,6 @@ 'stbox_le', 'stbox_ge', 'stbox_gt', - 'cstring2text', - 'text2cstring', 'tbool_in', 'tbool_out', 'temporal_as_hexwkb', @@ -516,61 +540,46 @@ 'tpoint_out', 'ttext_in', 'ttext_out', - 'tbool_from_base', + 'tbool_from_base_temp', 'tboolinst_make', - 'tbooldiscseq_from_base_time', - 'tboolseq_from_base', - 'tboolseq_from_base_time', - 'tboolseqset_from_base', - 'tboolseqset_from_base_time', + 'tboolseq_from_base_period', + 'tboolseq_from_base_timestampset', + 'tboolseqset_from_base_periodset', 'temporal_copy', - 'tfloat_from_base', + 'tfloat_from_base_temp', 'tfloatinst_make', - 'tfloatdiscseq_from_base_time', - 'tfloatseq_from_base', - 'tfloatseq_from_base_time', - 'tfloatseqset_from_base', - 'tfloatseqset_from_base_time', - 'tgeogpoint_from_base', + 'tfloatseq_from_base_period', + 'tfloatseq_from_base_timestampset', + 'tfloatseqset_from_base_periodset', + 'tgeogpoint_from_base_temp', 'tgeogpointinst_make', - 'tgeogpointdiscseq_from_base_time', - 'tgeogpointseq_from_base', - 'tgeogpointseq_from_base_time', - 'tgeogpointseqset_from_base', - 'tgeogpointseqset_from_base_time', - 'tgeompoint_from_base', + 'tgeogpointseq_from_base_period', + 'tgeogpointseq_from_base_timestampset', + 'tgeogpointseqset_from_base_periodset', + 'tgeompoint_from_base_temp', 'tgeompointinst_make', - 'tgeompointdiscseq_from_base_time', - 'tgeompointseq_from_base', - 'tgeompointseq_from_base_time', - 'tgeompointseqset_from_base', - 'tgeompointseqset_from_base_time', - 'tint_from_base', + 'tgeompointseq_from_base_period', + 'tgeompointseq_from_base_timestampset', + 'tgeompointseqset_from_base_periodset', + 'tint_from_base_temp', 'tintinst_make', - 'tintdiscseq_from_base_time', - 'tintseq_from_base', - 'tintseq_from_base_time', - 'tintseqset_from_base', - 'tintseqset_from_base_time', + 'tintseq_from_base_period', + 'tintseq_from_base_timestampset', + 'tintseqset_from_base_periodset', 'tsequence_make', 'tsequence_make_exp', - 'tpointseq_make_coords', - 'tsequence_make_free', 'tsequenceset_make', 'tsequenceset_make_exp', - 'tsequenceset_make_free', 'tsequenceset_make_gaps', - 'ttext_from_base', + 'ttext_from_base_temp', 'ttextinst_make', - 'ttextdiscseq_from_base_time', - 'ttextseq_from_base', - 'ttextseq_from_base_time', - 'ttextseqset_from_base', - 'ttextseqset_from_base_time', + 'ttextseq_from_base_period', + 'ttextseq_from_base_timestampset', + 'ttextseqset_from_base_periodset', + 'temporal_to_period', 'tfloat_to_tint', 'tint_to_tfloat', 'tnumber_to_span', - 'temporal_to_period', 'tbool_end_value', 'tbool_start_value', 'tbool_values', @@ -581,7 +590,7 @@ 'temporal_hash', 'temporal_instant_n', 'temporal_instants', - 'temporal_interpolation', + 'temporal_interp', 'temporal_max_instant', 'temporal_min_instant', 'temporal_num_instants', @@ -593,10 +602,12 @@ 'temporal_start_instant', 'temporal_start_sequence', 'temporal_start_timestamp', + 'temporal_stops', 'temporal_subtype', 'temporal_time', 'temporal_timestamp_n', 'temporal_timestamps', + 'temporal_values', 'tfloat_end_value', 'tfloat_max_value', 'tfloat_min_value', @@ -607,7 +618,7 @@ 'tint_min_value', 'tint_start_value', 'tint_values', - 'tnumber_values', + 'tnumber_valuespans', 'tpoint_end_value', 'tpoint_start_value', 'tpoint_values', @@ -616,20 +627,15 @@ 'ttext_min_value', 'ttext_start_value', 'ttext_values', - 'temporal_append_tinstant', - 'temporal_append_tsequence', - 'temporal_merge', - 'temporal_merge_array', + 'temporal_set_interp', 'temporal_shift', 'temporal_shift_tscale', - 'temporal_step_to_linear', 'temporal_to_tinstant', - 'temporal_to_tdiscseq', - 'temporal_to_tcontseq', + 'temporal_to_tsequence', 'temporal_to_tsequenceset', - 'temporal_tscale', 'temporal_tprecision', 'temporal_tsample', + 'temporal_tscale', 'tbool_at_value', 'tbool_minus_value', 'tbool_value_at_timestamp', @@ -639,12 +645,14 @@ 'temporal_at_periodset', 'temporal_at_timestamp', 'temporal_at_timestampset', + 'temporal_at_values', 'temporal_minus_max', 'temporal_minus_min', 'temporal_minus_period', 'temporal_minus_periodset', 'temporal_minus_timestamp', 'temporal_minus_timestampset', + 'temporal_minus_values', 'tfloat_at_value', 'tfloat_minus_value', 'tfloat_value_at_timestamp', @@ -657,52 +665,62 @@ 'tnumber_minus_span', 'tnumber_minus_spanset', 'tnumber_minus_tbox', - 'tpoint_at_geometry', + 'tpoint_at_geom_time', 'tpoint_at_stbox', 'tpoint_at_value', - 'tpoint_minus_geometry', + 'tpoint_minus_geom_time', 'tpoint_minus_stbox', 'tpoint_minus_value', 'tpoint_value_at_timestamp', - 'tsequence_at_period', 'ttext_at_value', 'ttext_minus_value', 'ttext_value_at_timestamp', + 'temporal_append_tinstant', + 'temporal_append_tsequence', + 'temporal_delete_period', + 'temporal_delete_periodset', + 'temporal_delete_timestamp', + 'temporal_delete_timestampset', + 'temporal_insert', + 'temporal_merge', + 'temporal_merge_array', + 'temporal_update', 'tand_bool_tbool', 'tand_tbool_bool', 'tand_tbool_tbool', + 'tbool_when_true', 'tnot_tbool', 'tor_bool_tbool', 'tor_tbool_bool', 'tor_tbool_tbool', - 'tbool_when_true', - 'add_float_tfloat', - 'add_int_tint', - 'add_tfloat_float', - 'add_tint_int', + 'add_float_tnumber', + 'add_int_tnumber', + 'add_tnumber_float', + 'add_tnumber_int', 'add_tnumber_tnumber', - 'float_degrees', - 'div_float_tfloat', - 'div_int_tint', - 'div_tfloat_float', - 'div_tint_int', + 'div_float_tnumber', + 'div_int_tnumber', + 'div_tnumber_float', + 'div_tnumber_int', 'div_tnumber_tnumber', - 'mult_float_tfloat', - 'mult_int_tint', - 'mult_tfloat_float', - 'mult_tint_int', + 'float_degrees', + 'mult_float_tnumber', + 'mult_int_tnumber', + 'mult_tnumber_float', + 'mult_tnumber_int', 'mult_tnumber_tnumber', - 'sub_float_tfloat', - 'sub_int_tint', - 'sub_tfloat_float', - 'sub_tint_int', + 'sub_float_tnumber', + 'sub_int_tnumber', + 'sub_tnumber_float', + 'sub_tnumber_int', 'sub_tnumber_tnumber', + 'tfloat_round', 'tfloat_degrees', - 'tfloat_radians', 'tfloat_derivative', + 'tfloat_radians', 'tnumber_abs', - 'tnumber_delta_value', 'tnumber_angular_difference', + 'tnumber_delta_value', 'textcat_text_ttext', 'textcat_ttext_text', 'textcat_ttext_ttext', @@ -746,6 +764,8 @@ 'tint_ever_eq', 'tint_ever_le', 'tint_ever_lt', + 'tpoint_always_eq', + 'tpoint_ever_eq', 'ttext_always_eq', 'ttext_always_le', 'ttext_always_lt', @@ -835,6 +855,7 @@ 'geo_expand_space', 'tgeompoint_tgeogpoint', 'tpoint_expand_space', + 'tpoint_round', 'tpoint_make_simple', 'tpoint_set_srid', 'econtains_geo_tpoint', @@ -851,13 +872,6 @@ 'tdwithin_tpoint_tpoint', 'tintersects_tpoint_geo', 'ttouches_tpoint_geo', - 'temporal_insert', - 'temporal_update', - 'temporal_delete_timestamp', - 'temporal_delete_timestampset', - 'temporal_delete_period', - 'temporal_delete_periodset', - 'temporal_stops', 'tbool_tand_transfn', 'tbool_tor_transfn', 'temporal_extent_transfn', @@ -869,32 +883,34 @@ 'tint_tmax_transfn', 'tint_tmin_transfn', 'tint_tsum_transfn', - 'tnumber_integral', 'tnumber_extent_transfn', + 'tnumber_integral', 'tnumber_tavg_finalfn', 'tnumber_tavg_transfn', 'tnumber_twavg', 'tpoint_extent_transfn', + 'tpoint_tcentroid_finalfn', + 'tpoint_tcentroid_transfn', 'tpoint_twcentroid', 'ttext_tmax_transfn', 'ttext_tmin_transfn', - 'int_bucket', 'float_bucket', - 'timestamptz_bucket', - 'intspan_bucket_list', 'floatspan_bucket_list', + 'int_bucket', + 'intspan_bucket_list', 'period_bucket_list', + 'stbox_tile_list', 'tbox_tile_list', - 'tint_value_split', - 'tfloat_value_split', 'temporal_time_split', - 'tint_value_time_split', + 'tfloat_value_split', 'tfloat_value_time_split', - 'stbox_tile_list', - 'temporal_frechet_distance', + 'timestamptz_bucket', + 'tint_value_split', + 'tint_value_time_split', 'temporal_dyntimewarp_distance', - 'temporal_frechet_path', 'temporal_dyntimewarp_path', + 'temporal_frechet_distance', + 'temporal_frechet_path', 'temporal_hausdorff_distance', 'geo_to_tpoint', 'temporal_simplify_min_dist', @@ -902,5 +918,5 @@ 'temporal_simplify_dp', 'temporal_simplify_max_dist', 'tpoint_AsMVTGeom', - 'tpoint_to_geo_measure', + 'tpoint_to_geo_meas', ] diff --git a/pymeos_cffi/pymeos_cffi/builder/build_header.py b/pymeos_cffi/pymeos_cffi/builder/build_header.py index 1aa08530..31c03895 100644 --- a/pymeos_cffi/pymeos_cffi/builder/build_header.py +++ b/pymeos_cffi/pymeos_cffi/builder/build_header.py @@ -48,7 +48,8 @@ def main(header_path, so_path=None): if __name__ == '__main__': if len(sys.argv) > 1: - main(sys.argv[1]) + get_defined_functions(sys.argv[2]) + main(sys.argv[1], sys.argv[2]) else: get_defined_functions('/usr/local/lib/libmeos.so') main('/usr/local/include/meos.h', '/usr/local/lib/libmeos.so') diff --git a/pymeos_cffi/pymeos_cffi/builder/build_pymeos.py b/pymeos_cffi/pymeos_cffi/builder/build_pymeos.py index 7aa24c3a..4e91579f 100644 --- a/pymeos_cffi/pymeos_cffi/builder/build_pymeos.py +++ b/pymeos_cffi/pymeos_cffi/builder/build_pymeos.py @@ -9,7 +9,7 @@ ffibuilder.set_source('_meos_cffi', '#include "meos.h" // the C header of the library', - libraries=['meos'], ) # library name, for the linker + libraries=['meos'], library_dirs=['/usr/local/lib']) # library name, for the linker if __name__ == "__main__": # not when running with setuptools ffibuilder.compile(verbose=True) diff --git a/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions.py b/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions.py index 666778a8..28e9464f 100644 --- a/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions.py +++ b/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions.py @@ -45,7 +45,7 @@ def __init__(self, ctype: str, ptype: str, conversion: Optional[str]) -> None: import postgis as pg import shapely.geometry as spg from dateutil.parser import parse -from shapely import wkt, wkb +from shapely import wkt, wkb, get_srid from shapely.geometry.base import BaseGeometry from spans.types import floatrange, intrange @@ -78,14 +78,23 @@ def interval_to_timedelta(interval: Any) -> timedelta: return timedelta(days=interval.day, microseconds=interval.time) -def geometry_to_gserialized(geom: Union[pg.Geometry, BaseGeometry]) -> 'GSERIALIZED *': +def geometry_to_gserialized(geom: Union[pg.Geometry, BaseGeometry], geodetic: Optional[bool] = None) -> 'GSERIALIZED *': if isinstance(geom, pg.Geometry): text = geom.to_ewkb() + # if geom.has_srid(): + # text = f'SRID={geom.srid};{text}' elif isinstance(geom, BaseGeometry): text = wkb.dumps(geom, hex=True) + if get_srid(geom) > 0: + text = f'SRID={get_srid(geom)};{text}' else: raise TypeError('Parameter geom must be either a PostGIS Geometry or a Shapely BaseGeometry') - return gserialized_in(text, -1) + gs = gserialized_in(text, -1) + if geodetic is not None: + # GFlags is an 8-bit integer, where the 4th bit is the geodetic flag (0x80) + # If geodetic is True, then set the 4th bit to 1, otherwise set it to 0 + gs.gflags = (gs.gflags | 0x08) if geodetic else (gs.gflags & 0xF7) + return gs def gserialized_to_shapely_point(geom: 'const GSERIALIZED *', precision: int = 6) -> spg.Point: @@ -137,7 +146,7 @@ def as_tsequenceset(temporal: 'Temporal *') -> 'TSequenceSet *': function_modifiers = { 'cstring2text': cstring2text_modifier, 'text2cstring': text2cstring_modifier, - 'tstzset_make': tstzset_make_modifier, + 'timestampset_make': timestampset_make_modifier, 'tint_at_values': tint_at_values_modifier, 'tint_minus_values': tint_minus_values_modifier, 'tfloat_at_values': tfloat_at_values_modifier, @@ -150,6 +159,19 @@ def as_tsequenceset(temporal: 'Temporal *') -> 'TSequenceSet *': 'tpoint_minus_values': array_length_remover_modifier('values_converted'), 'gserialized_from_lwgeom': gserialized_from_lwgeom_modifier, 'tpointseq_make_coords': tpointseq_make_coords_modifier, + 'spanset_make': spanset_make_modifier, + 'temporal_from_wkb': from_wkb_modifier('temporal_from_wkb', 'Temporal'), + 'set_from_wkb': from_wkb_modifier('set_from_wkb', 'Set'), + 'span_from_wkb': from_wkb_modifier('span_from_wkb', 'Span'), + 'spanset_from_wkb': from_wkb_modifier('spanset_from_wkb', 'SpanSet'), + 'tbox_from_wkb': from_wkb_modifier('tbox_from_wkb', 'TBOX'), + 'stbox_from_wkb': from_wkb_modifier('stbox_from_wkb', 'STBOX'), + 'temporal_as_wkb': as_wkb_modifier, + 'set_as_wkb': as_wkb_modifier, + 'span_as_wkb': as_wkb_modifier, + 'spanset_as_wkb': as_wkb_modifier, + 'tbox_as_wkb': as_wkb_modifier, + 'stbox_as_wkb': as_wkb_modifier, } # List of result function parameters in tuples of (function, parameter) @@ -181,21 +203,19 @@ def as_tsequenceset(temporal: 'Temporal *') -> 'TSequenceSet *': # List of nullable function parameters in tuples of (function, parameter) nullable_parameters = { - ('period_shift_tscale', 'delta'), - ('period_shift_tscale', 'scale'), ('meos_initialize', 'tz_str'), + ('temporal_append_tinstant', 'maxt'), ('temporal_as_mfjson', 'srs'), ('gserialized_as_geojson', 'srs'), + ('period_shift_tscale', 'shift'), ('period_shift_tscale', 'duration'), - ('period_shift_tscale', 'start'), - ('period_shift_tscale', 'start'), - ('timestampset_shift_tscale', 'start'), + ('period_shift_tscale', 'delta'), + ('period_shift_tscale', 'scale'), + ('timestampset_shift_tscale', 'shift'), ('timestampset_shift_tscale', 'duration'), - ('timestampset_shift_tscale', 'start'), - ('periodset_shift_tscale', 'start'), + ('periodset_shift_tscale', 'shift'), ('periodset_shift_tscale', 'duration'), - ('periodset_shift_tscale', 'start'), - ('temporal_shift_tscale', 'start'), + ('temporal_shift_tscale', 'shift'), ('temporal_shift_tscale', 'duration'), ('temporal_shift_tscale', 'shift'), ('tbox_make', 'p'), @@ -432,10 +452,11 @@ def build_function_string(function_name: str, return_type: ReturnType, parameter # If original C function returned bool, use it to return it when result is True, or raise exception when False if return_type.return_type == 'bool': - result_manipulation = (result_manipulation or '') + " if result:\n" \ - f" return {returning_object} if " \ - f"{returning_object} != _ffi.NULL else None\n" \ - " raise Exception(f'C call went wrong: {result}')" + + result_manipulation = (result_manipulation or '') + \ + " if result:\n" \ + f" return {returning_object} if {returning_object} != _ffi.NULL else None\n" \ + " return None" # Otherwise, just return it normally else: result_manipulation = (result_manipulation or '') + f' return {returning_object} if {returning_object}' \ diff --git a/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions_modifiers.py b/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions_modifiers.py index 66828840..91fe41eb 100644 --- a/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions_modifiers.py +++ b/pymeos_cffi/pymeos_cffi/builder/build_pymeos_functions_modifiers.py @@ -21,11 +21,38 @@ def text2cstring_modifier(_: str) -> str: return result""" -def tstzset_make_modifier(function: str) -> str: +def from_wkb_modifier(function: str, return_type: str) -> Callable[[str], str]: + return lambda _: f"""def {function}(wkb: bytes) -> '{return_type} *': + wkb_converted = _ffi.new('uint8_t []', wkb) + result = _lib.{function}(wkb_converted, len(wkb)) + return result if result != _ffi.NULL else None""" + + +def as_wkb_modifier(function: str) -> str: return function \ - .replace('times: int', 'times: List[int]') \ - .replace("times_converted = _ffi.cast('const TimestampTz *', times)", - "times_converted = [_ffi.cast('const TimestampTz', x) for x in times]") + .replace('-> "Tuple[\'uint8_t *\', \'size_t *\']":', '-> bytes:') \ + .replace('return result if result != _ffi.NULL else None, size_out[0]', + 'result_converted = bytes(result[i] for i in range(size_out[0])) if result != _ffi.NULL else None\n' + ' return result_converted') + + +def timestampset_make_modifier(function: str) -> str: + return function \ + .replace('values: int', 'values: List[int]') \ + .replace("values_converted = _ffi.cast('const TimestampTz *', values)", + "values_converted = [_ffi.cast('const TimestampTz', x) for x in values]") + + +def tbool_at_values_modifier(function: str) -> str: + return function \ + .replace("values: 'bool *', count: int", 'values: List[bool]') \ + .replace("_ffi.cast('bool *', values)", + "_ffi.new('bool []', values)") \ + .replace(', count', ', len(values_converted)') + + +def tbool_minus_values_modifier(function: str) -> str: + return tbool_at_values_modifier(function) def tint_at_values_modifier(function: str) -> str: @@ -52,16 +79,12 @@ def tfloat_minus_values_modifier(function: str) -> str: return tfloat_at_values_modifier(function) -def tbool_at_values_modifier(function: str) -> str: +def spanset_make_modifier(function: str) -> str: return function \ - .replace("values: 'bool *', count: int", 'values: List[bool]') \ - .replace("_ffi.cast('bool *', values)", - "_ffi.new('bool []', values)") \ - .replace(', count', ', len(values_converted)') - - -def tbool_minus_values_modifier(function: str) -> str: - return tbool_at_values_modifier(function) + .replace("spans: 'Span *', count: int", "spans: 'List[Span *]'") \ + .replace("_ffi.cast('Span *', spans)", + "_ffi.new('Span []', spans)") \ + .replace(', count', ', len(spans)') def gserialized_from_lwgeom_modifier(function: str) -> str: diff --git a/pymeos_cffi/pymeos_cffi/builder/meos.h b/pymeos_cffi/pymeos_cffi/builder/meos.h index a56101ba..eae87162 100644 --- a/pymeos_cffi/pymeos_cffi/builder/meos.h +++ b/pymeos_cffi/pymeos_cffi/builder/meos.h @@ -23,7 +23,7 @@ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON * AN "AS IS" BASIS, AND UNIVERSITE LIBRE DE BRUXELLES HAS NO OBLIGATIONS TO - * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.  + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. * *****************************************************************************/ @@ -41,6 +41,9 @@ //#ifndef POSTGRES_H //#define POSTGRES_H +//#define DatumGetPointer(X) ((Pointer) (X)) + +typedef char *Pointer; typedef uintptr_t Datum; typedef signed char int8; @@ -84,6 +87,37 @@ typedef struct varlena bytea; +/** +* Macros for manipulating the 'flags' byte. A uint8_t used as follows: +* VVSRGBMZ +* Version bit, followed by +* Validty, Solid, ReadOnly, Geodetic, HasBBox, HasM and HasZ flags. +*/ +//#define LWFLAG_Z 0x01 +//#define LWFLAG_M 0x02 +//#define LWFLAG_BBOX 0x04 +//#define LWFLAG_GEODETIC 0x08 +//#define LWFLAG_READONLY 0x10 +//#define LWFLAG_SOLID 0x20 + +//#define FLAGS_GET_Z(flags) ((flags) & LWFLAG_Z) +//#define FLAGS_GET_M(flags) (((flags) & LWFLAG_M)>>1) +//#define FLAGS_GET_BBOX(flags) (((flags) & LWFLAG_BBOX)>>2) +//#define FLAGS_GET_GEODETIC(flags) (((flags) & LWFLAG_GEODETIC)>>3) +//#define FLAGS_GET_READONLY(flags) (((flags) & LWFLAG_READONLY)>>4) +//#define FLAGS_GET_SOLID(flags) (((flags) & LWFLAG_SOLID)>>5) + +//#define FLAGS_SET_Z(flags, value) ((flags) = (value) ? ((flags) | LWFLAG_Z) : ((flags) & ~LWFLAG_Z)) +//#define FLAGS_SET_M(flags, value) ((flags) = (value) ? ((flags) | LWFLAG_M) : ((flags) & ~LWFLAG_M)) +//#define FLAGS_SET_BBOX(flags, value) ((flags) = (value) ? ((flags) | LWFLAG_BBOX) : ((flags) & ~LWFLAG_BBOX)) +//#define FLAGS_SET_GEODETIC(flags, value) ((flags) = (value) ? ((flags) | LWFLAG_GEODETIC) : ((flags) & ~LWFLAG_GEODETIC)) +//#define FLAGS_SET_READONLY(flags, value) ((flags) = (value) ? ((flags) | LWFLAG_READONLY) : ((flags) & ~LWFLAG_READONLY)) +//#define FLAGS_SET_SOLID(flags, value) ((flags) = (value) ? ((flags) | LWFLAG_SOLID) : ((flags) & ~LWFLAG_SOLID)) + +//#define FLAGS_NDIMS(flags) (2 + FLAGS_GET_Z(flags) + FLAGS_GET_M(flags)) +//#define FLAGS_GET_ZM(flags) (FLAGS_GET_M(flags) + FLAGS_GET_Z(flags) * 2) +//#define FLAGS_NDIMS_BOX(flags) (FLAGS_GET_GEODETIC(flags) ? 3 : FLAGS_NDIMS(flags)) + /* ** Variants available for WKB and WKT output types */ @@ -463,6 +497,22 @@ extern int lwgeom_has_m(const LWGEOM *geom); //#endif +/***************************************************************************** + * Toolchain dependent definitions + *****************************************************************************/ + +//#ifdef _MSC_VER +/* + * Under MSVC, functions exported by a loadable module must be marked + * "dllexport". Other compilers don't need that. + * Borrowed from PostgreSQL file win32.h + */ +//#define PGDLLEXPORT __declspec (dllexport) +/* + * Avoids warning C4996: 'strdup': The POSIX name for this item is deprecated. + */ +//#define strdup _strdup +//#endif /***************************************************************************** * Type definitions @@ -484,7 +534,7 @@ typedef struct int16 flags; int32 count; int32 maxcount; - int32 bboxsize; + int16 bboxsize; } Set; /** @@ -554,6 +604,17 @@ typedef enum LINEAR = 3, } interpType; +/** + * @brief Enumeration that defines the spatial relationships for which a call + * to GEOS is made. + */ +typedef enum +{ + INTERSECTS = 0, + CONTAINS = 1, + TOUCHES = 2, +} spatialRel; + /** * Structure to represent the common structure of temporal values of * any temporal subtype @@ -683,6 +744,7 @@ extern void meos_finalize(void); extern bool bool_in(const char *in_str); extern char *bool_out(bool b); +extern text *cstring2text(const char *cstring); extern DateADT pg_date_in(const char *str); extern char *pg_date_out(DateADT date); extern int pg_interval_cmp(const Interval *interval1, const Interval *interval2); @@ -700,6 +762,12 @@ extern char *pg_timestamp_out(Timestamp dt); extern TimestampTz pg_timestamp_pl_interval(TimestampTz timestamp, const Interval *span); extern TimestampTz pg_timestamptz_in(const char *str, int32 typmod); extern char *pg_timestamptz_out(TimestampTz dt); +extern char *text2cstring(const text *textptr); +extern text *pg_timestamp_to_char(Timestamp dt, text *fmt); +extern text *pg_timestamptz_to_char(TimestampTz dt, text *fmt); +extern text *pg_interval_to_char(Interval *it, text *fmt); +extern TimestampTz pg_to_timestamp(text *date_txt, text *fmt); +extern DateADT pg_to_date(text *date_txt, text *fmt); /***************************************************************************** * Functions for input/output and manipulation of PostGIS types @@ -736,56 +804,63 @@ extern Span *floatspan_in(const char *str); extern char *floatspan_out(const Span *s, int maxdd); extern SpanSet *floatspanset_in(const char *str); extern char *floatspanset_out(const SpanSet *ss, int maxdd); +extern Set *geogset_in(const char *str); extern char *geogset_out(const Set *set, int maxdd); +extern Set *geomset_in(const char *str); extern char *geomset_out(const Set *set, int maxdd); -extern char *geoset_as_text(const Set *set, int maxdd); extern char *geoset_as_ewkt(const Set *set, int maxdd); +extern char *geoset_as_text(const Set *set, int maxdd); extern Set *intset_in(const char *str); extern char *intset_out(const Set *set); extern Span *intspan_in(const char *str); extern char *intspan_out(const Span *s); extern SpanSet *intspanset_in(const char *str); extern char *intspanset_out(const SpanSet *ss); - extern Span *period_in(const char *str); extern char *period_out(const Span *s); extern SpanSet *periodset_in(const char *str); extern char *periodset_out(const SpanSet *ss); -extern uint8_t *set_as_wkb(const Set *s, uint8_t variant, size_t *size_out); extern char *set_as_hexwkb(const Set *s, uint8_t variant, size_t *size_out); +extern uint8_t *set_as_wkb(const Set *s, uint8_t variant, size_t *size_out); extern Set *set_from_hexwkb(const char *hexwkb); -extern Set *set_from_wkb(const uint8_t *wkb, int size); +extern Set *set_from_wkb(const uint8_t *wkb, size_t size); extern char *set_out(const Set *s, int maxdd); extern uint8_t *span_as_wkb(const Span *s, uint8_t variant, size_t *size_out); extern char *span_as_hexwkb(const Span *s, uint8_t variant, size_t *size_out); extern Span *span_from_hexwkb(const char *hexwkb); -extern Span *span_from_wkb(const uint8_t *wkb, int size); +extern Span *span_from_wkb(const uint8_t *wkb, size_t size); extern char *span_out(const Span *s, int maxdd); extern uint8_t *spanset_as_wkb(const SpanSet *ss, uint8_t variant, size_t *size_out); extern char *spanset_as_hexwkb(const SpanSet *ss, uint8_t variant, size_t *size_out); extern SpanSet *spanset_from_hexwkb(const char *hexwkb); -extern SpanSet *spanset_from_wkb(const uint8_t *wkb, int size); +extern SpanSet *spanset_from_wkb(const uint8_t *wkb, size_t size); extern char *spanset_out(const SpanSet *ss, int maxdd); extern Set *textset_in(const char *str); extern char *textset_out(const Set *set); -extern Set *tstzset_in(const char *str); -extern char *tstzset_out(const Set *set); +extern Set *timestampset_in(const char *str); +extern char *timestampset_out(const Set *set); +extern Set *bigintset_make(const int64 *values, int count); extern Span *bigintspan_make(int64 lower, int64 upper, bool lower_inc, bool upper_inc); +extern Set *floatset_make(const double *values, int count); extern Span *floatspan_make(double lower, double upper, bool lower_inc, bool upper_inc); +extern Set *geogset_make(const GSERIALIZED **values, int count); +extern Set *geomset_make(const GSERIALIZED **values, int count); +extern Set *intset_make(const int *values, int count); extern Span *intspan_make(int lower, int upper, bool lower_inc, bool upper_inc); -extern Set *set_copy(const Set *ts); -extern Span *tstzspan_make(TimestampTz lower, TimestampTz upper, bool lower_inc, bool upper_inc); +extern Span *period_make(TimestampTz lower, TimestampTz upper, bool lower_inc, bool upper_inc); +extern Set *set_copy(const Set *s); extern Span *span_copy(const Span *s); extern SpanSet *spanset_copy(const SpanSet *ps); extern SpanSet *spanset_make(Span *spans, int count, bool normalize); extern SpanSet *spanset_make_exp(Span *spans, int count, int maxcount, bool normalize, bool ordered); extern SpanSet *spanset_make_free(Span *spans, int count, bool normalize); -extern Set *tstzset_make(const TimestampTz *times, int count); +extern Set *textset_make(const text **values, int count); +extern Set *timestampset_make(const TimestampTz *values, int count); @@ -793,17 +868,15 @@ extern Set *tstzset_make(const TimestampTz *times, int count); extern Set *bigint_to_bigintset(int64 i); extern Span *bigint_to_bigintspan(int i); -extern Span *float_to_floaspan(double d); +extern SpanSet *bigint_to_bigintspanset(int i); extern Set *float_to_floatset(double d); +extern Span *float_to_floatspan(double d); +extern SpanSet *float_to_floatspanset(double d); extern Set *int_to_intset(int i); extern Span *int_to_intspan(int i); -extern void set_set_span(const Set *os, Span *s); -extern Span *set_to_span(const Set *s); +extern SpanSet *int_to_intspanset(int i); extern SpanSet *set_to_spanset(const Set *s); extern SpanSet *span_to_spanset(const Span *s); -extern Span *spanset_to_span(const SpanSet *ss); -extern void spatialset_set_stbox(const Set *set, STBox *box); -extern STBox *spatialset_to_stbox(const Set *s); extern Span *timestamp_to_period(TimestampTz t); extern SpanSet *timestamp_to_periodset(TimestampTz t); extern Set *timestamp_to_tstzset(TimestampTz t); @@ -828,6 +901,7 @@ extern double floatspan_lower(const Span *s); extern double floatspan_upper(const Span *s); extern double floatspanset_lower(const SpanSet *ss); extern double floatspanset_upper(const SpanSet *ss); +extern int geoset_srid(const Set *set); extern int intset_end_value(const Set *s); extern int intset_start_value(const Set *s); extern bool intset_value_n(const Set *s, int n, int *result); @@ -836,14 +910,6 @@ extern int intspan_lower(const Span *s); extern int intspan_upper(const Span *s); extern int intspanset_lower(const SpanSet *ss); extern int intspanset_upper(const SpanSet *ss); -extern Datum set_end_value(const Set *s); -extern uint32 set_hash(const Set *s); -extern uint64 set_hash_extended(const Set *s, uint64 seed); -extern int set_mem_size(const Set *s); -extern int set_num_values(const Set *s); -extern Datum set_start_value(const Set *s); -extern bool set_value_n(const Set *s, int n, Datum *result); -extern Datum *set_values(const Set *s); extern Interval *period_duration(const Span *s); extern TimestampTz period_lower(const Span *p); extern TimestampTz period_upper(const Span *p); @@ -855,8 +921,13 @@ extern TimestampTz periodset_start_timestamp(const SpanSet *ps); extern bool periodset_timestamp_n(const SpanSet *ps, int n, TimestampTz *result); extern TimestampTz *periodset_timestamps(const SpanSet *ps, int *count); extern TimestampTz periodset_upper(const SpanSet *ps); +extern uint32 set_hash(const Set *s); +extern uint64 set_hash_extended(const Set *s, uint64 seed); +extern int set_mem_size(const Set *s); +extern int set_num_values(const Set *s); +extern Span *set_span(const Set *s); extern uint32 span_hash(const Span *s); -extern uint64 span_hash_extended(const Span *s, Datum seed); +extern uint64 span_hash_extended(const Span *s, uint64 seed); extern bool span_lower_inc(const Span *s); extern bool span_upper_inc(const Span *s); extern double span_width(const Span *s); @@ -866,33 +937,37 @@ extern uint64 spanset_hash_extended(const SpanSet *ps, uint64 seed); extern bool spanset_lower_inc(const SpanSet *ss); extern int spanset_mem_size(const SpanSet *ss); extern int spanset_num_spans(const SpanSet *ss); +extern Span *spanset_span(const SpanSet *ss); extern Span *spanset_span_n(const SpanSet *ss, int i); extern const Span **spanset_spans(const SpanSet *ss); extern Span *spanset_start_span(const SpanSet *ss); extern bool spanset_upper_inc(const SpanSet *ss); extern double spanset_width(const SpanSet *ss); -extern TimestampTz tstzset_end_timestamp(const Set *ts); -extern TimestampTz tstzset_start_timestamp(const Set *ts); -extern bool tstzset_timestamp_n(const Set *ts, int n, TimestampTz *result); -extern TimestampTz *tstzset_values(const Set *ts); -extern int geoset_srid(const Set *set); +extern STBox *spatialset_stbox(const Set *s); +extern TimestampTz timestampset_end_timestamp(const Set *ts); +extern TimestampTz timestampset_start_timestamp(const Set *ts); +extern bool timestampset_timestamp_n(const Set *ts, int n, TimestampTz *result); +extern TimestampTz *timestampset_values(const Set *ts); +extern Set *floatset_round(const Set *s, int maxdd); +extern Span *floatspan_round(const Span *s, int maxdd); +extern SpanSet *floatspanset_round(const SpanSet *ss, int maxdd); extern void floatspan_set_intspan(const Span *s1, Span *s2); +extern Set *geoset_round(const Set *s, int maxdd); extern void intspan_set_floatspan(const Span *s1, Span *s2); extern void numspan_set_floatspan(const Span *s1, Span *s2); extern Span *period_tprecision(const Span *s, const Interval *duration, TimestampTz torigin); extern SpanSet *periodset_tprecision(const SpanSet *ss, const Interval *duration, TimestampTz torigin); -extern void period_shift_tscale(Span *p, const Interval *shift, const Interval *duration, - TimestampTz *delta, double *scale); +extern Span *period_shift_tscale(const Span *p, const Interval *shift, const Interval *duration); extern SpanSet *periodset_shift_tscale(const SpanSet *ps, const Interval *shift, const Interval *duration); extern Set *set_shift(const Set *s, Datum shift); extern void span_expand(const Span *s1, Span *s2); extern TimestampTz timestamp_tprecision(TimestampTz t, const Interval *duration, TimestampTz torigin); -extern Set *tstzset_shift_tscale(const Set *ts, const Interval *shift, const Interval *duration); +extern Set *timestampset_shift_tscale(const Set *ts, const Interval *shift, const Interval *duration); /***************************************************************************** * Bounding box functions for set and span types @@ -1023,7 +1098,6 @@ extern SpanSet *union_periodset_timestamp(SpanSet *ps, TimestampTz t); extern SpanSet *union_span_span(const Span *s1, const Span *s2); extern SpanSet *union_spanset_span(const SpanSet *ss, const Span *s); extern SpanSet *union_spanset_spanset(const SpanSet *ss1, const SpanSet *ss2); -extern Set *union_timestamp_timestampset(TimestampTz t, const Set *ts); extern Set *union_timestampset_timestamp(const Set *ts, const TimestampTz t); @@ -1046,22 +1120,25 @@ extern double distance_timestampset_timestamp(const Set *ts, TimestampTz t); extern Span *bigint_extent_transfn(Span *s, int64 i); extern Set *bigint_union_transfn(Set *state, int64 i); -extern Span *int_extent_transfn(Span *s, int i); -extern Set *int_union_transfn(Set *state, int i); extern Span *float_extent_transfn(Span *s, double d); extern Set *float_union_transfn(Set *state, double d); +extern Span *int_extent_transfn(Span *s, int i); +extern Set *int_union_transfn(Set *state, int i); extern SkipList *period_tcount_transfn(SkipList *state, const Span *p); extern SkipList *periodset_tcount_transfn(SkipList *state, const SpanSet *ps); +extern Span *set_extent_transfn(Span *span, const Set *set); extern Set *set_union_finalfn(Set *state); extern Set *set_union_transfn(Set *state, Set *set); extern Span *span_extent_transfn(Span *s1, const Span *s2); +extern SpanSet *span_union_transfn(SpanSet *state, const Span *span); extern Span *spanset_extent_transfn(Span *s, const SpanSet *ss); +extern SpanSet *spanset_union_finalfn(SpanSet *state); +extern SpanSet *spanset_union_transfn(SpanSet *state, const SpanSet *ss); extern Set *text_union_transfn(Set *state, const text *txt); extern Span *timestamp_extent_transfn(Span *p, TimestampTz t); extern SkipList *timestamp_tcount_transfn(SkipList *state, TimestampTz t); extern Set *timestamp_union_transfn(Set *state, TimestampTz t); -extern Span *set_extent_transfn(Span *span, const Set *set); -extern SkipList *tstzset_tcount_transfn(SkipList *state, const Set *ts); +extern SkipList *timestampset_tcount_transfn(SkipList *state, const Set *ts); @@ -1097,9 +1174,9 @@ extern bool spanset_ne(const SpanSet *ss1, const SpanSet *ss2); extern TBox *tbox_in(const char *str); extern char *tbox_out(const TBox *box, int maxdd); -extern TBox *tbox_from_wkb(const uint8_t *wkb, int size); +extern TBox *tbox_from_wkb(const uint8_t *wkb, size_t size); extern TBox *tbox_from_hexwkb(const char *hexwkb); -extern STBox *stbox_from_wkb(const uint8_t *wkb, int size); +extern STBox *stbox_from_wkb(const uint8_t *wkb, size_t size); extern STBox *stbox_from_hexwkb(const char *hexwkb); extern uint8_t *tbox_as_wkb(const TBox *box, uint8_t variant, size_t *size_out); extern char *tbox_as_hexwkb(const TBox *box, uint8_t variant, size_t *size); @@ -1112,8 +1189,8 @@ extern char *stbox_out(const STBox *box, int maxdd); -extern TBox *tbox_make(const Span *p, const Span *s); -extern void tbox_set(const Span *p, const Span *s, TBox *box); +extern TBox *tbox_make(const Span *s, const Span *p); +extern void tbox_set(const Span *s, const Span *p, TBox *box); extern TBox *tbox_copy(const TBox *box); extern STBox * stbox_make(bool hasx, bool hasz, bool geodetic, int32 srid, double xmin, double xmax, double ymin, double ymax, double zmin, double zmax, const Span *p); @@ -1128,7 +1205,7 @@ extern STBox *stbox_copy(const STBox *box); extern TBox *int_to_tbox(int i); extern TBox *float_to_tbox(double d); extern TBox *timestamp_to_tbox(TimestampTz t); -extern TBox *tstzset_to_tbox(const Set *ss); +extern TBox *timestampset_to_tbox(const Set *ss); extern TBox *period_to_tbox(const Span *p); extern TBox *periodset_to_tbox(const SpanSet *ps); extern TBox *int_timestamp_to_tbox(int i, TimestampTz t); @@ -1147,9 +1224,8 @@ extern Span *stbox_to_period(const STBox *box); extern TBox *tnumber_to_tbox(const Temporal *temp); extern GSERIALIZED *stbox_to_geo(const STBox *box); extern STBox *tpoint_to_stbox(const Temporal *temp); - extern STBox *timestamp_to_stbox(TimestampTz t); -extern STBox *tstzset_to_stbox(const Set *ts); +extern STBox *timestampset_to_stbox(const Set *ts); extern STBox *period_to_stbox(const Span *p); extern STBox *periodset_to_stbox(const SpanSet *ps); @@ -1160,9 +1236,13 @@ extern STBox *periodset_to_stbox(const SpanSet *ps); extern bool tbox_hasx(const TBox *box); extern bool tbox_hast(const TBox *box); extern bool tbox_xmin(const TBox *box, double *result); +extern bool tbox_xmin_inc(const TBox *box, bool *result); extern bool tbox_xmax(const TBox *box, double *result); +extern bool tbox_xmax_inc(const TBox *box, bool *result); extern bool tbox_tmin(const TBox *box, TimestampTz *result); +extern bool tbox_tmin_inc(const TBox *box, bool *result); extern bool tbox_tmax(const TBox *box, TimestampTz *result); +extern bool tbox_tmax_inc(const TBox *box, bool *result); extern bool stbox_hasx(const STBox *box); extern bool stbox_hasz(const STBox *box); extern bool stbox_hast(const STBox *box); @@ -1174,21 +1254,25 @@ extern bool stbox_ymax(const STBox *box, double *result); extern bool stbox_zmin(const STBox *box, double *result); extern bool stbox_zmax(const STBox *box, double *result); extern bool stbox_tmin(const STBox *box, TimestampTz *result); +extern bool stbox_tmin_inc(const STBox *box, bool *result); extern bool stbox_tmax(const STBox *box, TimestampTz *result); +extern bool stbox_tmax_inc(const STBox *box, bool *result); extern int32 stbox_srid(const STBox *box); -extern void tbox_expand(const TBox *box1, TBox *box2); -extern TBox *tbox_expand_value(const TBox *box, const double d); -extern TBox *tbox_expand_time(const TBox *box, const Interval *interval); extern void stbox_expand(const STBox *box1, STBox *box2); -extern STBox *stbox_set_srid(const STBox *box, int32 srid); -extern STBox *stbox_get_space(const STBox *box); extern STBox *stbox_expand_space(const STBox *box, double d); extern STBox *stbox_expand_time(const STBox *box, const Interval *interval); +extern STBox *stbox_get_space(const STBox *box); +extern STBox *stbox_round(const STBox *box, int maxdd); +extern STBox *stbox_set_srid(const STBox *box, int32 srid); +extern void tbox_expand(const TBox *box1, TBox *box2); +extern TBox *tbox_expand_value(const TBox *box, const double d); +extern TBox *tbox_expand_time(const TBox *box, const Interval *interval); +extern TBox *tbox_round(const TBox *box, int maxdd); @@ -1276,11 +1360,6 @@ extern bool stbox_gt(const STBox *box1, const STBox *box2); -extern text *cstring2text(const char *cstring); -extern char *text2cstring(const text *textptr); - - - extern Temporal *tbool_in(const char *str); extern char *tbool_out(const Temporal *temp); extern char *temporal_as_hexwkb(const Temporal *temp, uint8_t variant, size_t *size_out); @@ -1288,7 +1367,7 @@ extern char *temporal_as_mfjson(const Temporal *temp, bool with_bbox, int flags, extern uint8_t *temporal_as_wkb(const Temporal *temp, uint8_t variant, size_t *size_out); extern Temporal *temporal_from_hexwkb(const char *hexwkb); extern Temporal *temporal_from_mfjson(const char *mfjson); -extern Temporal *temporal_from_wkb(const uint8_t *wkb, int size); +extern Temporal *temporal_from_wkb(const uint8_t *wkb, size_t size); extern Temporal *tfloat_in(const char *str); extern char *tfloat_out(const Temporal *temp, int maxdd); extern Temporal *tgeogpoint_in(const char *str); @@ -1305,67 +1384,51 @@ extern char *ttext_out(const Temporal *temp); -extern Temporal *tbool_from_base(bool b, const Temporal *temp); +extern Temporal *tbool_from_base_temp(bool b, const Temporal *temp); extern TInstant *tboolinst_make(bool b, TimestampTz t); -extern TSequence *tbooldiscseq_from_base_time(bool b, const Set *ts); -extern TSequence *tboolseq_from_base(bool b, const TSequence *seq); -extern TSequence *tboolseq_from_base_time(bool b, const Span *p); -extern TSequenceSet *tboolseqset_from_base(bool b, const TSequenceSet *ss); -extern TSequenceSet *tboolseqset_from_base_time(bool b, const SpanSet *ps); +extern TSequence *tboolseq_from_base_period(bool b, const Span *p); +extern TSequence *tboolseq_from_base_timestampset(bool b, const Set *ts); +extern TSequenceSet *tboolseqset_from_base_periodset(bool b, const SpanSet *ps); extern Temporal *temporal_copy(const Temporal *temp); -extern Temporal *tfloat_from_base(double d, const Temporal *temp, interpType interp); +extern Temporal *tfloat_from_base_temp(double d, const Temporal *temp); extern TInstant *tfloatinst_make(double d, TimestampTz t); -extern TSequence *tfloatdiscseq_from_base_time(double d, const Set *ts); -extern TSequence *tfloatseq_from_base(double d, const TSequence *seq, interpType interp); -extern TSequence *tfloatseq_from_base_time(double d, const Span *p, interpType interp); -extern TSequenceSet *tfloatseqset_from_base(double d, const TSequenceSet *ss, interpType interp); -extern TSequenceSet *tfloatseqset_from_base_time(double d, const SpanSet *ps, interpType interp); -extern Temporal *tgeogpoint_from_base(const GSERIALIZED *gs, const Temporal *temp, interpType interp); +extern TSequence *tfloatseq_from_base_period(double d, const Span *p, interpType interp); +extern TSequence *tfloatseq_from_base_timestampset(double d, const Set *ts); +extern TSequenceSet *tfloatseqset_from_base_periodset(double d, const SpanSet *ps, interpType interp); +extern Temporal *tgeogpoint_from_base_temp(const GSERIALIZED *gs, const Temporal *temp); extern TInstant *tgeogpointinst_make(const GSERIALIZED *gs, TimestampTz t); -extern TSequence *tgeogpointdiscseq_from_base_time(const GSERIALIZED *gs, const Set *ts); -extern TSequence *tgeogpointseq_from_base(const GSERIALIZED *gs, const TSequence *seq, interpType interp); -extern TSequence *tgeogpointseq_from_base_time(const GSERIALIZED *gs, const Span *p, interpType interp); -extern TSequenceSet *tgeogpointseqset_from_base(const GSERIALIZED *gs, const TSequenceSet *ss, interpType interp); -extern TSequenceSet *tgeogpointseqset_from_base_time(const GSERIALIZED *gs, const SpanSet *ps, interpType interp); -extern Temporal *tgeompoint_from_base(const GSERIALIZED *gs, const Temporal *temp, interpType interp); +extern TSequence *tgeogpointseq_from_base_period(const GSERIALIZED *gs, const Span *p, interpType interp); +extern TSequence *tgeogpointseq_from_base_timestampset(const GSERIALIZED *gs, const Set *ts); +extern TSequenceSet *tgeogpointseqset_from_base_periodset(const GSERIALIZED *gs, const SpanSet *ps, interpType interp); +extern Temporal *tgeompoint_from_base_temp(const GSERIALIZED *gs, const Temporal *temp); extern TInstant *tgeompointinst_make(const GSERIALIZED *gs, TimestampTz t); -extern TSequence *tgeompointdiscseq_from_base_time(const GSERIALIZED *gs, const Set *ts); -extern TSequence *tgeompointseq_from_base(const GSERIALIZED *gs, const TSequence *seq, interpType interp); -extern TSequence *tgeompointseq_from_base_time(const GSERIALIZED *gs, const Span *p, interpType interp); -extern TSequenceSet *tgeompointseqset_from_base(const GSERIALIZED *gs, const TSequenceSet *ss, interpType interp); -extern TSequenceSet *tgeompointseqset_from_base_time(const GSERIALIZED *gs, const SpanSet *ps, interpType interp); -extern Temporal *tint_from_base(int i, const Temporal *temp); +extern TSequence *tgeompointseq_from_base_period(const GSERIALIZED *gs, const Span *p, interpType interp); +extern TSequence *tgeompointseq_from_base_timestampset(const GSERIALIZED *gs, const Set *ts); +extern TSequenceSet *tgeompointseqset_from_base_periodset(const GSERIALIZED *gs, const SpanSet *ps, interpType interp); +extern Temporal *tint_from_base_temp(int i, const Temporal *temp); extern TInstant *tintinst_make(int i, TimestampTz t); -extern TSequence *tintdiscseq_from_base_time(int i, const Set *ts); -extern TSequence *tintseq_from_base(int i, const TSequence *seq); -extern TSequence *tintseq_from_base_time(int i, const Span *p); -extern TSequenceSet *tintseqset_from_base(int i, const TSequenceSet *ss); -extern TSequenceSet *tintseqset_from_base_time(int i, const SpanSet *ps); +extern TSequence *tintseq_from_base_period(int i, const Span *p); +extern TSequence *tintseq_from_base_timestampset(int i, const Set *ts); +extern TSequenceSet *tintseqset_from_base_periodset(int i, const SpanSet *ps); extern TSequence *tsequence_make(const TInstant **instants, int count, bool lower_inc, bool upper_inc, interpType interp, bool normalize); extern TSequence *tsequence_make_exp(const TInstant **instants, int count, int maxcount, bool lower_inc, bool upper_inc, interpType interp, bool normalize); -extern TSequence *tpointseq_make_coords(const double *xcoords, const double *ycoords, const double *zcoords, - const TimestampTz *times, int count, int32 srid, bool geodetic, bool lower_inc, bool upper_inc, interpType interp, bool normalize); -extern TSequence *tsequence_make_free(TInstant **instants, int count, bool lower_inc, bool upper_inc, interpType interp, bool normalize); extern TSequenceSet *tsequenceset_make(const TSequence **sequences, int count, bool normalize); extern TSequenceSet *tsequenceset_make_exp(const TSequence **sequences, int count, int maxcount, bool normalize); -extern TSequenceSet *tsequenceset_make_free(TSequence **sequences, int count, bool normalize); extern TSequenceSet *tsequenceset_make_gaps(const TInstant **instants, int count, interpType interp, Interval *maxt, double maxdist); -extern Temporal *ttext_from_base(const text *txt, const Temporal *temp); +extern Temporal *ttext_from_base_temp(const text *txt, const Temporal *temp); extern TInstant *ttextinst_make(const text *txt, TimestampTz t); -extern TSequence *ttextdiscseq_from_base_time(const text *txt, const Set *ts); -extern TSequence *ttextseq_from_base(const text *txt, const TSequence *seq); -extern TSequence *ttextseq_from_base_time(const text *txt, const Span *p); -extern TSequenceSet *ttextseqset_from_base(const text *txt, const TSequenceSet *ss); -extern TSequenceSet *ttextseqset_from_base_time(const text *txt, const SpanSet *ps); +extern TSequence *ttextseq_from_base_period(const text *txt, const Span *p); +extern TSequence *ttextseq_from_base_timestampset(const text *txt, const Set *ts); +extern TSequenceSet *ttextseqset_from_base_periodset(const text *txt, const SpanSet *ps); +extern Span *temporal_to_period(const Temporal *temp); extern Temporal *tfloat_to_tint(const Temporal *temp); extern Temporal *tint_to_tfloat(const Temporal *temp); extern Span *tnumber_to_span(const Temporal *temp); -extern Span *temporal_to_period(const Temporal *temp); @@ -1381,7 +1444,7 @@ extern TimestampTz temporal_end_timestamp(const Temporal *temp); extern uint32 temporal_hash(const Temporal *temp); extern const TInstant *temporal_instant_n(const Temporal *temp, int n); extern const TInstant **temporal_instants(const Temporal *temp, int *count); -extern char *temporal_interpolation(const Temporal *temp); +extern char *temporal_interp(const Temporal *temp); extern const TInstant *temporal_max_instant(const Temporal *temp); extern const TInstant *temporal_min_instant(const Temporal *temp); extern int temporal_num_instants(const Temporal *temp); @@ -1393,10 +1456,12 @@ extern TSequence **temporal_sequences(const Temporal *temp, int *count); extern const TInstant *temporal_start_instant(const Temporal *temp); extern TSequence *temporal_start_sequence(const Temporal *temp); extern TimestampTz temporal_start_timestamp(const Temporal *temp); +extern TSequenceSet *temporal_stops(const Temporal *temp, double maxdist, const Interval *minduration); extern char *temporal_subtype(const Temporal *temp); extern SpanSet *temporal_time(const Temporal *temp); extern bool temporal_timestamp_n(const Temporal *temp, int n, TimestampTz *result); extern TimestampTz *temporal_timestamps(const Temporal *temp, int *count); +extern Datum *temporal_values(const Temporal *temp, int *count); extern double tfloat_end_value(const Temporal *temp); extern double tfloat_max_value(const Temporal *temp); extern double tfloat_min_value(const Temporal *temp); @@ -1407,7 +1472,7 @@ extern int tint_max_value(const Temporal *temp); extern int tint_min_value(const Temporal *temp); extern int tint_start_value(const Temporal *temp); extern int *tint_values(const Temporal *temp, int *count); -extern SpanSet *tnumber_values(const Temporal *temp); +extern SpanSet *tnumber_valuespans(const Temporal *temp); extern GSERIALIZED *tpoint_end_value(const Temporal *temp); extern GSERIALIZED *tpoint_start_value(const Temporal *temp); extern GSERIALIZED **tpoint_values(const Temporal *temp, int *count); @@ -1421,20 +1486,15 @@ extern text **ttext_values(const Temporal *temp, int *count); -extern Temporal *temporal_append_tinstant(Temporal *temp, const TInstant *inst, double maxdist, Interval *maxt, bool expand); -extern Temporal *temporal_append_tsequence(Temporal *temp, const TSequence *seq, bool expand); -extern Temporal *temporal_merge(const Temporal *temp1, const Temporal *temp2); -extern Temporal *temporal_merge_array(Temporal **temparr, int count); +extern Temporal *temporal_set_interp(const Temporal *temp, interpType interp); extern Temporal *temporal_shift(const Temporal *temp, const Interval *shift); extern Temporal *temporal_shift_tscale(const Temporal *temp, const Interval *shift, const Interval *duration); -extern Temporal *temporal_step_to_linear(const Temporal *temp); extern Temporal *temporal_to_tinstant(const Temporal *temp); -extern Temporal *temporal_to_tdiscseq(const Temporal *temp); -extern Temporal *temporal_to_tcontseq(const Temporal *temp); +extern Temporal *temporal_to_tsequence(const Temporal *temp); extern Temporal *temporal_to_tsequenceset(const Temporal *temp); -extern Temporal *temporal_tscale(const Temporal *temp, const Interval *duration); extern Temporal *temporal_tprecision(const Temporal *temp, const Interval *duration, TimestampTz origin); extern Temporal *temporal_tsample(const Temporal *temp, const Interval *duration, TimestampTz origin); +extern Temporal *temporal_tscale(const Temporal *temp, const Interval *duration); @@ -1449,12 +1509,14 @@ extern Temporal *temporal_at_period(const Temporal *temp, const Span *p); extern Temporal *temporal_at_periodset(const Temporal *temp, const SpanSet *ps); extern Temporal *temporal_at_timestamp(const Temporal *temp, TimestampTz t); extern Temporal *temporal_at_timestampset(const Temporal *temp, const Set *ts); +extern Temporal *temporal_at_values(const Temporal *temp, const Set *set); extern Temporal *temporal_minus_max(const Temporal *temp); extern Temporal *temporal_minus_min(const Temporal *temp); extern Temporal *temporal_minus_period(const Temporal *temp, const Span *p); extern Temporal *temporal_minus_periodset(const Temporal *temp, const SpanSet *ps); extern Temporal *temporal_minus_timestamp(const Temporal *temp, TimestampTz t); extern Temporal *temporal_minus_timestampset(const Temporal *temp, const Set *ts); +extern Temporal *temporal_minus_values(const Temporal *temp, const Set *set); extern Temporal *tfloat_at_value(const Temporal *temp, double d); extern Temporal *tfloat_minus_value(const Temporal *temp, double d); extern bool tfloat_value_at_timestamp(const Temporal *temp, TimestampTz t, bool strict, double *value); @@ -1467,14 +1529,13 @@ extern Temporal *tnumber_at_tbox(const Temporal *temp, const TBox *box); extern Temporal *tnumber_minus_span(const Temporal *temp, const Span *span); extern Temporal *tnumber_minus_spanset(const Temporal *temp, const SpanSet *ss); extern Temporal *tnumber_minus_tbox(const Temporal *temp, const TBox *box); -extern Temporal *tpoint_at_geometry(const Temporal *temp, const GSERIALIZED *gs); -extern Temporal *tpoint_at_stbox(const Temporal *temp, const STBox *box); +extern Temporal *tpoint_at_geom_time(const Temporal *temp, const GSERIALIZED *gs, const Span *zspan, const Span *period); +extern Temporal *tpoint_at_stbox(const Temporal *temp, const STBox *box, bool border_inc); extern Temporal *tpoint_at_value(const Temporal *temp, GSERIALIZED *gs); -extern Temporal *tpoint_minus_geometry(const Temporal *temp, const GSERIALIZED *gs); -extern Temporal *tpoint_minus_stbox(const Temporal *temp, const STBox *box); +extern Temporal *tpoint_minus_geom_time(const Temporal *temp, const GSERIALIZED *gs, const Span *zspan, const Span *period); +extern Temporal *tpoint_minus_stbox(const Temporal *temp, const STBox *box, bool border_inc); extern Temporal *tpoint_minus_value(const Temporal *temp, GSERIALIZED *gs); extern bool tpoint_value_at_timestamp(const Temporal *temp, TimestampTz t, bool strict, GSERIALIZED **value); -extern TSequence *tsequence_at_period(const TSequence *seq, const Span *p); extern Temporal *ttext_at_value(const Temporal *temp, text *txt); extern Temporal *ttext_minus_value(const Temporal *temp, text *txt); extern bool ttext_value_at_timestamp(const Temporal *temp, TimestampTz t, bool strict, text **value); @@ -1483,46 +1544,62 @@ extern bool ttext_value_at_timestamp(const Temporal *temp, TimestampTz t, bool s +extern Temporal *temporal_append_tinstant(Temporal *temp, const TInstant *inst, double maxdist, Interval *maxt, bool expand); +extern Temporal *temporal_append_tsequence(Temporal *temp, const TSequence *seq, bool expand); +extern Temporal *temporal_delete_period(const Temporal *temp, const Span *p, bool connect); +extern Temporal *temporal_delete_periodset(const Temporal *temp, const SpanSet *ps, bool connect); +extern Temporal *temporal_delete_timestamp(const Temporal *temp, TimestampTz t, bool connect); +extern Temporal *temporal_delete_timestampset(const Temporal *temp, const Set *ts, bool connect); +extern Temporal *temporal_insert(const Temporal *temp1, const Temporal *temp2, bool connect); +extern Temporal *temporal_merge(const Temporal *temp1, const Temporal *temp2); +extern Temporal *temporal_merge_array(Temporal **temparr, int count); +extern Temporal *temporal_update(const Temporal *temp1, const Temporal *temp2, bool connect); + + + + + extern Temporal *tand_bool_tbool(bool b, const Temporal *temp); extern Temporal *tand_tbool_bool(const Temporal *temp, bool b); extern Temporal *tand_tbool_tbool(const Temporal *temp1, const Temporal *temp2); +extern SpanSet *tbool_when_true(const Temporal *temp); extern Temporal *tnot_tbool(const Temporal *temp); extern Temporal *tor_bool_tbool(bool b, const Temporal *temp); extern Temporal *tor_tbool_bool(const Temporal *temp, bool b); extern Temporal *tor_tbool_tbool(const Temporal *temp1, const Temporal *temp2); -extern SpanSet *tbool_when_true(const Temporal *temp); -extern Temporal *add_float_tfloat(double d, const Temporal *tnumber); -extern Temporal *add_int_tint(int i, const Temporal *tnumber); -extern Temporal *add_tfloat_float(const Temporal *tnumber, double d); -extern Temporal *add_tint_int(const Temporal *tnumber, int i); +extern Temporal *add_float_tnumber(double d, const Temporal *tnumber); +extern Temporal *add_int_tnumber(int i, const Temporal *tnumber); +extern Temporal *add_tnumber_float(const Temporal *tnumber, double d); +extern Temporal *add_tnumber_int(const Temporal *tnumber, int i); extern Temporal *add_tnumber_tnumber(const Temporal *tnumber1, const Temporal *tnumber2); -extern double float_degrees(double value, bool normalize); -extern Temporal *div_float_tfloat(double d, const Temporal *tnumber); -extern Temporal *div_int_tint(int i, const Temporal *tnumber); -extern Temporal *div_tfloat_float(const Temporal *tnumber, double d); -extern Temporal *div_tint_int(const Temporal *tnumber, int i); +extern Temporal *div_float_tnumber(double d, const Temporal *tnumber); +extern Temporal *div_int_tnumber(int i, const Temporal *tnumber); +extern Temporal *div_tnumber_float(const Temporal *tnumber, double d); +extern Temporal *div_tnumber_int(const Temporal *tnumber, int i); extern Temporal *div_tnumber_tnumber(const Temporal *tnumber1, const Temporal *tnumber2); -extern Temporal *mult_float_tfloat(double d, const Temporal *tnumber); -extern Temporal *mult_int_tint(int i, const Temporal *tnumber); -extern Temporal *mult_tfloat_float(const Temporal *tnumber, double d); -extern Temporal *mult_tint_int(const Temporal *tnumber, int i); +extern double float_degrees(double value, bool normalize); +extern Temporal *mult_float_tnumber(double d, const Temporal *tnumber); +extern Temporal *mult_int_tnumber(int i, const Temporal *tnumber); +extern Temporal *mult_tnumber_float(const Temporal *tnumber, double d); +extern Temporal *mult_tnumber_int(const Temporal *tnumber, int i); extern Temporal *mult_tnumber_tnumber(const Temporal *tnumber1, const Temporal *tnumber2); -extern Temporal *sub_float_tfloat(double d, const Temporal *tnumber); -extern Temporal *sub_int_tint(int i, const Temporal *tnumber); -extern Temporal *sub_tfloat_float(const Temporal *tnumber, double d); -extern Temporal *sub_tint_int(const Temporal *tnumber, int i); +extern Temporal *sub_float_tnumber(double d, const Temporal *tnumber); +extern Temporal *sub_int_tnumber(int i, const Temporal *tnumber); +extern Temporal *sub_tnumber_float(const Temporal *tnumber, double d); +extern Temporal *sub_tnumber_int(const Temporal *tnumber, int i); extern Temporal *sub_tnumber_tnumber(const Temporal *tnumber1, const Temporal *tnumber2); +extern Temporal *tfloat_round(const Temporal *temp, int maxdd); extern Temporal *tfloat_degrees(const Temporal *temp, bool normalize); -extern Temporal *tfloat_radians(const Temporal *temp); extern Temporal *tfloat_derivative(const Temporal *temp); +extern Temporal *tfloat_radians(const Temporal *temp); extern Temporal *tnumber_abs(const Temporal *temp); -extern Temporal *tnumber_delta_value(const Temporal *temp); extern Temporal *tnumber_angular_difference(const Temporal *temp); +extern Temporal *tnumber_delta_value(const Temporal *temp); @@ -1534,16 +1611,6 @@ extern Temporal *textcat_ttext_ttext(const Temporal *temp1, const Temporal *temp extern Temporal *ttext_upper(const Temporal *temp); extern Temporal *ttext_lower(const Temporal *temp); -/***************************************************************************** - * Bounding box functions for temporal types - *****************************************************************************/ - - - - - - - @@ -1591,6 +1658,8 @@ extern bool tint_always_lt(const Temporal *temp, int i); extern bool tint_ever_eq(const Temporal *temp, int i); extern bool tint_ever_le(const Temporal *temp, int i); extern bool tint_ever_lt(const Temporal *temp, int i); +extern bool tpoint_always_eq(const Temporal *temp, Datum value); +extern bool tpoint_ever_eq(const Temporal *temp, Datum value); extern bool ttext_always_eq(const Temporal *temp, text *txt); extern bool ttext_always_le(const Temporal *temp, text *txt); extern bool ttext_always_lt(const Temporal *temp, text *txt); @@ -1697,6 +1766,7 @@ extern GSERIALIZED *tpoint_trajectory(const Temporal *temp); extern STBox *geo_expand_space(const GSERIALIZED *gs, double d); extern Temporal *tgeompoint_tgeogpoint(const Temporal *temp, bool oper); extern STBox *tpoint_expand_space(const Temporal *temp, double d); +extern Temporal *tpoint_round(const Temporal *temp, int maxdd); extern Temporal **tpoint_make_simple(const Temporal *temp, int *count); extern Temporal *tpoint_set_srid(const Temporal *temp, int32 srid); @@ -1723,18 +1793,6 @@ extern Temporal *ttouches_tpoint_geo(const Temporal *temp, const GSERIALIZED *gs -extern Temporal *temporal_insert(const Temporal *temp1, const Temporal *temp2, bool connect); -extern Temporal *temporal_update(const Temporal *temp1, const Temporal *temp2, bool connect); -extern Temporal *temporal_delete_timestamp(const Temporal *temp, TimestampTz t, bool connect); -extern Temporal *temporal_delete_timestampset(const Temporal *temp, const Set *ts, bool connect); -extern Temporal *temporal_delete_period(const Temporal *temp, const Span *p, bool connect); -extern Temporal *temporal_delete_periodset(const Temporal *temp, const SpanSet *ps, bool connect); -extern TSequenceSet *temporal_stops(const Temporal *temp, double mindist, const Interval *minduration); - - - - - extern SkipList *tbool_tand_transfn(SkipList *state, const Temporal *temp); extern SkipList *tbool_tor_transfn(SkipList *state, const Temporal *temp); extern Span *temporal_extent_transfn(Span *p, const Temporal *temp); @@ -1746,12 +1804,14 @@ extern SkipList *tfloat_tsum_transfn(SkipList *state, const Temporal *temp); extern SkipList *tint_tmax_transfn(SkipList *state, const Temporal *temp); extern SkipList *tint_tmin_transfn(SkipList *state, const Temporal *temp); extern SkipList *tint_tsum_transfn(SkipList *state, const Temporal *temp); -extern double tnumber_integral(const Temporal *temp); extern TBox *tnumber_extent_transfn(TBox *box, const Temporal *temp); +extern double tnumber_integral(const Temporal *temp); extern Temporal *tnumber_tavg_finalfn(SkipList *state); extern SkipList *tnumber_tavg_transfn(SkipList *state, const Temporal *temp); extern double tnumber_twavg(const Temporal *temp); extern STBox *tpoint_extent_transfn(STBox *box, const Temporal *temp); +extern Temporal *tpoint_tcentroid_finalfn(SkipList *state); +extern SkipList *tpoint_tcentroid_transfn(SkipList *state, Temporal *temp); extern GSERIALIZED *tpoint_twcentroid(const Temporal *temp); extern SkipList *ttext_tmax_transfn(SkipList *state, const Temporal *temp); extern SkipList *ttext_tmin_transfn(SkipList *state, const Temporal *temp); @@ -1760,32 +1820,28 @@ extern SkipList *ttext_tmin_transfn(SkipList *state, const Temporal *temp); -extern int int_bucket(int value, int size, int origin); extern double float_bucket(double value, double size, double origin); -extern TimestampTz timestamptz_bucket(TimestampTz timestamp, const Interval *duration, TimestampTz origin); - -extern Span *intspan_bucket_list(const Span *bounds, int size, int origin, int *newcount); extern Span *floatspan_bucket_list(const Span *bounds, double size, double origin, int *newcount); +extern int int_bucket(int value, int size, int origin); +extern Span *intspan_bucket_list(const Span *bounds, int size, int origin, int *newcount); extern Span *period_bucket_list(const Span *bounds, const Interval *duration, TimestampTz origin, int *newcount); - +extern STBox *stbox_tile_list(const STBox *bounds, double xsize, double ysize, double zsize, const Interval *duration, GSERIALIZED *sorigin, TimestampTz torigin, int **cellcount); extern TBox *tbox_tile_list(const TBox *bounds, double xsize, const Interval *duration, double xorigin, TimestampTz torigin, int *rows, int *columns); - -extern Temporal **tint_value_split(Temporal *temp, int size, int origin, int *newcount); -extern Temporal **tfloat_value_split(Temporal *temp, double size, double origin, int *newcount); extern Temporal **temporal_time_split(Temporal *temp, Interval *duration, TimestampTz torigin, int *newcount); -extern Temporal **tint_value_time_split(Temporal *temp, int size, int vorigin, Interval *duration, TimestampTz torigin, int *newcount); +extern Temporal **tfloat_value_split(Temporal *temp, double size, double origin, int *newcount); extern Temporal **tfloat_value_time_split(Temporal *temp, double size, double vorigin, Interval *duration, TimestampTz torigin, int *newcount); - -extern STBox *stbox_tile_list(STBox *bounds, double size, const Interval *duration, GSERIALIZED *sorigin, TimestampTz torigin, int **cellcount); +extern TimestampTz timestamptz_bucket(TimestampTz timestamp, const Interval *duration, TimestampTz origin); +extern Temporal **tint_value_split(Temporal *temp, int size, int origin, int *newcount); +extern Temporal **tint_value_time_split(Temporal *temp, int size, int vorigin, Interval *duration, TimestampTz torigin, int *newcount); -extern double temporal_frechet_distance(const Temporal *temp1, const Temporal *temp2); extern double temporal_dyntimewarp_distance(const Temporal *temp1, const Temporal *temp2); -extern Match *temporal_frechet_path(const Temporal *temp1, const Temporal *temp2, int *count); extern Match *temporal_dyntimewarp_path(const Temporal *temp1, const Temporal *temp2, int *count); +extern double temporal_frechet_distance(const Temporal *temp1, const Temporal *temp2); +extern Match *temporal_frechet_path(const Temporal *temp1, const Temporal *temp2, int *count); extern double temporal_hausdorff_distance(const Temporal *temp1, const Temporal *temp2); @@ -1797,9 +1853,8 @@ Temporal *temporal_simplify_min_dist(const Temporal *temp, double dist); Temporal *temporal_simplify_min_tdelta(const Temporal *temp, const Interval *mint); Temporal *temporal_simplify_dp(const Temporal *temp, double eps_dist, bool synchronized); Temporal *temporal_simplify_max_dist(const Temporal *temp, double eps_dist, bool synchronized); -bool tpoint_AsMVTGeom(const Temporal *temp, const STBox *bounds, int32_t extent, - int32_t buffer, bool clip_geom, GSERIALIZED **geom, int64 **timesarr, int *count); -bool tpoint_to_geo_measure(const Temporal *tpoint, const Temporal *measure, bool segmentize, GSERIALIZED **result); +bool tpoint_AsMVTGeom(const Temporal *temp, const STBox *bounds, int32_t extent, int32_t buffer, bool clip_geom, GSERIALIZED **geom, int64 **timesarr, int *count); +bool tpoint_to_geo_meas(const Temporal *tpoint, const Temporal *measure, bool segmentize, GSERIALIZED **result); diff --git a/pymeos_cffi/pymeos_cffi/functions.py b/pymeos_cffi/pymeos_cffi/functions.py index 70b20256..6d7c0b9c 100644 --- a/pymeos_cffi/pymeos_cffi/functions.py +++ b/pymeos_cffi/pymeos_cffi/functions.py @@ -5,7 +5,7 @@ import postgis as pg import shapely.geometry as spg from dateutil.parser import parse -from shapely import wkt, wkb +from shapely import wkt, wkb, get_srid from shapely.geometry.base import BaseGeometry from spans.types import floatrange, intrange @@ -38,14 +38,23 @@ def interval_to_timedelta(interval: Any) -> timedelta: return timedelta(days=interval.day, microseconds=interval.time) -def geometry_to_gserialized(geom: Union[pg.Geometry, BaseGeometry]) -> 'GSERIALIZED *': +def geometry_to_gserialized(geom: Union[pg.Geometry, BaseGeometry], geodetic: Optional[bool] = None) -> 'GSERIALIZED *': if isinstance(geom, pg.Geometry): text = geom.to_ewkb() + # if geom.has_srid(): + # text = f'SRID={geom.srid};{text}' elif isinstance(geom, BaseGeometry): text = wkb.dumps(geom, hex=True) + if get_srid(geom) > 0: + text = f'SRID={get_srid(geom)};{text}' else: raise TypeError('Parameter geom must be either a PostGIS Geometry or a Shapely BaseGeometry') - return gserialized_in(text, -1) + gs = gserialized_in(text, -1) + if geodetic is not None: + # GFlags is an 8-bit integer, where the 4th bit is the geodetic flag (0x80) + # If geodetic is True, then set the 4th bit to 1, otherwise set it to 0 + gs.gflags = (gs.gflags | 0x08) if geodetic else (gs.gflags & 0xF7) + return gs def gserialized_to_shapely_point(geom: 'const GSERIALIZED *', precision: int = 6) -> spg.Point: @@ -172,6 +181,12 @@ def bool_out(b: bool) -> str: return result if result != _ffi.NULL else None +def cstring2text(cstring: str) -> 'text *': + cstring_converted = cstring.encode('utf-8') + result = _lib.cstring2text(cstring_converted) + return result + + def pg_date_in(string: str) -> 'DateADT': string_converted = string.encode('utf-8') result = _lib.pg_date_in(string_converted) @@ -293,6 +308,50 @@ def pg_timestamptz_out(dt: int) -> str: return result if result != _ffi.NULL else None +def text2cstring(textptr: 'text *') -> str: + result = _lib.text2cstring(textptr) + result = _ffi.string(result).decode('utf-8') + return result + + +def pg_timestamp_to_char(dt: int, fmt: str) -> str: + dt_converted = _ffi.cast('Timestamp', dt) + fmt_converted = cstring2text(fmt) + result = _lib.pg_timestamp_to_char(dt_converted, fmt_converted) + result = text2cstring(result) + return result if result != _ffi.NULL else None + + +def pg_timestamptz_to_char(dt: int, fmt: str) -> str: + dt_converted = _ffi.cast('TimestampTz', dt) + fmt_converted = cstring2text(fmt) + result = _lib.pg_timestamptz_to_char(dt_converted, fmt_converted) + result = text2cstring(result) + return result if result != _ffi.NULL else None + + +def pg_interval_to_char(it: 'Interval *', fmt: str) -> str: + it_converted = _ffi.cast('Interval *', it) + fmt_converted = cstring2text(fmt) + result = _lib.pg_interval_to_char(it_converted, fmt_converted) + result = text2cstring(result) + return result if result != _ffi.NULL else None + + +def pg_to_timestamp(date_txt: str, fmt: str) -> 'TimestampTz': + date_txt_converted = cstring2text(date_txt) + fmt_converted = cstring2text(fmt) + result = _lib.pg_to_timestamp(date_txt_converted, fmt_converted) + return result if result != _ffi.NULL else None + + +def pg_to_date(date_txt: str, fmt: str) -> 'DateADT': + date_txt_converted = cstring2text(date_txt) + fmt_converted = cstring2text(fmt) + result = _lib.pg_to_date(date_txt_converted, fmt_converted) + return result if result != _ffi.NULL else None + + def gserialized_as_ewkb(geom: 'const GSERIALIZED *', type: str) -> 'bytea *': geom_converted = _ffi.cast('const GSERIALIZED *', geom) type_converted = type.encode('utf-8') @@ -454,6 +513,12 @@ def floatspanset_out(ss: 'const SpanSet *', maxdd: int) -> str: return result if result != _ffi.NULL else None +def geogset_in(string: str) -> 'Set *': + string_converted = string.encode('utf-8') + result = _lib.geogset_in(string_converted) + return result if result != _ffi.NULL else None + + def geogset_out(set: 'const Set *', maxdd: int) -> str: set_converted = _ffi.cast('const Set *', set) result = _lib.geogset_out(set_converted, maxdd) @@ -461,6 +526,12 @@ def geogset_out(set: 'const Set *', maxdd: int) -> str: return result if result != _ffi.NULL else None +def geomset_in(string: str) -> 'Set *': + string_converted = string.encode('utf-8') + result = _lib.geomset_in(string_converted) + return result if result != _ffi.NULL else None + + def geomset_out(set: 'const Set *', maxdd: int) -> str: set_converted = _ffi.cast('const Set *', set) result = _lib.geomset_out(set_converted, maxdd) @@ -468,16 +539,16 @@ def geomset_out(set: 'const Set *', maxdd: int) -> str: return result if result != _ffi.NULL else None -def geoset_as_text(set: 'const Set *', maxdd: int) -> str: +def geoset_as_ewkt(set: 'const Set *', maxdd: int) -> str: set_converted = _ffi.cast('const Set *', set) - result = _lib.geoset_as_text(set_converted, maxdd) + result = _lib.geoset_as_ewkt(set_converted, maxdd) result = _ffi.string(result).decode('utf-8') return result if result != _ffi.NULL else None -def geoset_as_ewkt(set: 'const Set *', maxdd: int) -> str: +def geoset_as_text(set: 'const Set *', maxdd: int) -> str: set_converted = _ffi.cast('const Set *', set) - result = _lib.geoset_as_ewkt(set_converted, maxdd) + result = _lib.geoset_as_text(set_converted, maxdd) result = _ffi.string(result).decode('utf-8') return result if result != _ffi.NULL else None @@ -547,21 +618,22 @@ def periodset_out(ss: 'const SpanSet *') -> str: return result if result != _ffi.NULL else None -def set_as_wkb(s: 'const Set *', variant: int) -> "Tuple['uint8_t *', 'size_t *']": +def set_as_hexwkb(s: 'const Set *', variant: int) -> "Tuple[str, 'size_t *']": s_converted = _ffi.cast('const Set *', s) variant_converted = _ffi.cast('uint8_t', variant) size_out = _ffi.new('size_t *') - result = _lib.set_as_wkb(s_converted, variant_converted, size_out) + result = _lib.set_as_hexwkb(s_converted, variant_converted, size_out) + result = _ffi.string(result).decode('utf-8') return result if result != _ffi.NULL else None, size_out[0] -def set_as_hexwkb(s: 'const Set *', variant: int) -> "Tuple[str, 'size_t *']": +def set_as_wkb(s: 'const Set *', variant: int) -> bytes: s_converted = _ffi.cast('const Set *', s) variant_converted = _ffi.cast('uint8_t', variant) size_out = _ffi.new('size_t *') - result = _lib.set_as_hexwkb(s_converted, variant_converted, size_out) - result = _ffi.string(result).decode('utf-8') - return result if result != _ffi.NULL else None, size_out[0] + result = _lib.set_as_wkb(s_converted, variant_converted, size_out) + result_converted = bytes(result[i] for i in range(size_out[0])) if result != _ffi.NULL else None + return result_converted def set_from_hexwkb(hexwkb: str) -> 'Set *': @@ -570,9 +642,9 @@ def set_from_hexwkb(hexwkb: str) -> 'Set *': return result if result != _ffi.NULL else None -def set_from_wkb(wkb: 'const uint8_t *', size: int) -> 'Set *': - wkb_converted = _ffi.cast('const uint8_t *', wkb) - result = _lib.set_from_wkb(wkb_converted, size) +def set_from_wkb(wkb: bytes) -> 'Set *': + wkb_converted = _ffi.new('uint8_t []', wkb) + result = _lib.set_from_wkb(wkb_converted, len(wkb)) return result if result != _ffi.NULL else None @@ -583,12 +655,13 @@ def set_out(s: 'const Set *', maxdd: int) -> str: return result if result != _ffi.NULL else None -def span_as_wkb(s: 'const Span *', variant: int) -> "Tuple['uint8_t *', 'size_t *']": +def span_as_wkb(s: 'const Span *', variant: int) -> bytes: s_converted = _ffi.cast('const Span *', s) variant_converted = _ffi.cast('uint8_t', variant) size_out = _ffi.new('size_t *') result = _lib.span_as_wkb(s_converted, variant_converted, size_out) - return result if result != _ffi.NULL else None, size_out[0] + result_converted = bytes(result[i] for i in range(size_out[0])) if result != _ffi.NULL else None + return result_converted def span_as_hexwkb(s: 'const Span *', variant: int) -> "Tuple[str, 'size_t *']": @@ -606,9 +679,9 @@ def span_from_hexwkb(hexwkb: str) -> 'Span *': return result if result != _ffi.NULL else None -def span_from_wkb(wkb: 'const uint8_t *', size: int) -> 'Span *': - wkb_converted = _ffi.cast('const uint8_t *', wkb) - result = _lib.span_from_wkb(wkb_converted, size) +def span_from_wkb(wkb: bytes) -> 'Span *': + wkb_converted = _ffi.new('uint8_t []', wkb) + result = _lib.span_from_wkb(wkb_converted, len(wkb)) return result if result != _ffi.NULL else None @@ -619,12 +692,13 @@ def span_out(s: 'const Span *', maxdd: int) -> str: return result if result != _ffi.NULL else None -def spanset_as_wkb(ss: 'const SpanSet *', variant: int) -> "Tuple['uint8_t *', 'size_t *']": +def spanset_as_wkb(ss: 'const SpanSet *', variant: int) -> bytes: ss_converted = _ffi.cast('const SpanSet *', ss) variant_converted = _ffi.cast('uint8_t', variant) size_out = _ffi.new('size_t *') result = _lib.spanset_as_wkb(ss_converted, variant_converted, size_out) - return result if result != _ffi.NULL else None, size_out[0] + result_converted = bytes(result[i] for i in range(size_out[0])) if result != _ffi.NULL else None + return result_converted def spanset_as_hexwkb(ss: 'const SpanSet *', variant: int) -> "Tuple[str, 'size_t *']": @@ -642,9 +716,9 @@ def spanset_from_hexwkb(hexwkb: str) -> 'SpanSet *': return result if result != _ffi.NULL else None -def spanset_from_wkb(wkb: 'const uint8_t *', size: int) -> 'SpanSet *': - wkb_converted = _ffi.cast('const uint8_t *', wkb) - result = _lib.spanset_from_wkb(wkb_converted, size) +def spanset_from_wkb(wkb: bytes) -> 'SpanSet *': + wkb_converted = _ffi.new('uint8_t []', wkb) + result = _lib.spanset_from_wkb(wkb_converted, len(wkb)) return result if result != _ffi.NULL else None @@ -668,19 +742,25 @@ def textset_out(set: 'const Set *') -> str: return result if result != _ffi.NULL else None -def tstzset_in(string: str) -> 'Set *': +def timestampset_in(string: str) -> 'Set *': string_converted = string.encode('utf-8') - result = _lib.tstzset_in(string_converted) + result = _lib.timestampset_in(string_converted) return result if result != _ffi.NULL else None -def tstzset_out(set: 'const Set *') -> str: +def timestampset_out(set: 'const Set *') -> str: set_converted = _ffi.cast('const Set *', set) - result = _lib.tstzset_out(set_converted) + result = _lib.timestampset_out(set_converted) result = _ffi.string(result).decode('utf-8') return result if result != _ffi.NULL else None +def bigintset_make(values: 'const int64 *', count: int) -> 'Set *': + values_converted = _ffi.cast('const int64 *', values) + result = _lib.bigintset_make(values_converted, count) + return result if result != _ffi.NULL else None + + def bigintspan_make(lower: int, upper: int, lower_inc: bool, upper_inc: bool) -> 'Span *': lower_converted = _ffi.cast('int64', lower) upper_converted = _ffi.cast('int64', upper) @@ -688,26 +768,50 @@ def bigintspan_make(lower: int, upper: int, lower_inc: bool, upper_inc: bool) -> return result if result != _ffi.NULL else None +def floatset_make(values: 'const double *', count: int) -> 'Set *': + values_converted = _ffi.cast('const double *', values) + result = _lib.floatset_make(values_converted, count) + return result if result != _ffi.NULL else None + + def floatspan_make(lower: float, upper: float, lower_inc: bool, upper_inc: bool) -> 'Span *': result = _lib.floatspan_make(lower, upper, lower_inc, upper_inc) return result if result != _ffi.NULL else None -def intspan_make(lower: int, upper: int, lower_inc: bool, upper_inc: bool) -> 'Span *': - result = _lib.intspan_make(lower, upper, lower_inc, upper_inc) +def geogset_make(values: 'const GSERIALIZED **', count: int) -> 'Set *': + values_converted = [_ffi.cast('const GSERIALIZED *', x) for x in values] + result = _lib.geogset_make(values_converted, count) return result if result != _ffi.NULL else None -def set_copy(ts: 'const Set *') -> 'Set *': - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.set_copy(ts_converted) +def geomset_make(values: 'const GSERIALIZED **', count: int) -> 'Set *': + values_converted = [_ffi.cast('const GSERIALIZED *', x) for x in values] + result = _lib.geomset_make(values_converted, count) + return result if result != _ffi.NULL else None + + +def intset_make(values: 'const int *', count: int) -> 'Set *': + values_converted = _ffi.cast('const int *', values) + result = _lib.intset_make(values_converted, count) + return result if result != _ffi.NULL else None + + +def intspan_make(lower: int, upper: int, lower_inc: bool, upper_inc: bool) -> 'Span *': + result = _lib.intspan_make(lower, upper, lower_inc, upper_inc) return result if result != _ffi.NULL else None -def tstzspan_make(lower: int, upper: int, lower_inc: bool, upper_inc: bool) -> 'Span *': +def period_make(lower: int, upper: int, lower_inc: bool, upper_inc: bool) -> 'Span *': lower_converted = _ffi.cast('TimestampTz', lower) upper_converted = _ffi.cast('TimestampTz', upper) - result = _lib.tstzspan_make(lower_converted, upper_converted, lower_inc, upper_inc) + result = _lib.period_make(lower_converted, upper_converted, lower_inc, upper_inc) + return result if result != _ffi.NULL else None + + +def set_copy(s: 'const Set *') -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.set_copy(s_converted) return result if result != _ffi.NULL else None @@ -723,9 +827,9 @@ def spanset_copy(ps: 'const SpanSet *') -> 'SpanSet *': return result if result != _ffi.NULL else None -def spanset_make(spans: 'Span *', count: int, normalize: bool) -> 'SpanSet *': - spans_converted = _ffi.cast('Span *', spans) - result = _lib.spanset_make(spans_converted, count, normalize) +def spanset_make(spans: 'List[Span *]', normalize: bool) -> 'SpanSet *': + spans_converted = _ffi.new('Span []', spans) + result = _lib.spanset_make(spans_converted, len(spans), normalize) return result if result != _ffi.NULL else None @@ -741,9 +845,15 @@ def spanset_make_free(spans: 'Span *', count: int, normalize: bool) -> 'SpanSet return result if result != _ffi.NULL else None -def tstzset_make(times: List[int], count: int) -> 'Set *': - times_converted = [_ffi.cast('const TimestampTz', x) for x in times] - result = _lib.tstzset_make(times_converted, count) +def textset_make(values: 'const text **', count: int) -> 'Set *': + values_converted = [_ffi.cast('const text *', x) for x in values] + result = _lib.textset_make(values_converted, count) + return result if result != _ffi.NULL else None + + +def timestampset_make(values: List[int], count: int) -> 'Set *': + values_converted = [_ffi.cast('const TimestampTz', x) for x in values] + result = _lib.timestampset_make(values_converted, count) return result if result != _ffi.NULL else None @@ -758,8 +868,8 @@ def bigint_to_bigintspan(i: int) -> 'Span *': return result if result != _ffi.NULL else None -def float_to_floaspan(d: float) -> 'Span *': - result = _lib.float_to_floaspan(d) +def bigint_to_bigintspanset(i: int) -> 'SpanSet *': + result = _lib.bigint_to_bigintspanset(i) return result if result != _ffi.NULL else None @@ -768,6 +878,16 @@ def float_to_floatset(d: float) -> 'Set *': return result if result != _ffi.NULL else None +def float_to_floatspan(d: float) -> 'Span *': + result = _lib.float_to_floatspan(d) + return result if result != _ffi.NULL else None + + +def float_to_floatspanset(d: float) -> 'SpanSet *': + result = _lib.float_to_floatspanset(d) + return result if result != _ffi.NULL else None + + def int_to_intset(i: int) -> 'Set *': result = _lib.int_to_intset(i) return result if result != _ffi.NULL else None @@ -778,15 +898,8 @@ def int_to_intspan(i: int) -> 'Span *': return result if result != _ffi.NULL else None -def set_set_span(os: 'const Set *', s: 'Span *') -> None: - os_converted = _ffi.cast('const Set *', os) - s_converted = _ffi.cast('Span *', s) - _lib.set_set_span(os_converted, s_converted) - - -def set_to_span(s: 'const Set *') -> 'Span *': - s_converted = _ffi.cast('const Set *', s) - result = _lib.set_to_span(s_converted) +def int_to_intspanset(i: int) -> 'SpanSet *': + result = _lib.int_to_intspanset(i) return result if result != _ffi.NULL else None @@ -802,24 +915,6 @@ def span_to_spanset(s: 'const Span *') -> 'SpanSet *': return result if result != _ffi.NULL else None -def spanset_to_span(ss: 'const SpanSet *') -> 'Span *': - ss_converted = _ffi.cast('const SpanSet *', ss) - result = _lib.spanset_to_span(ss_converted) - return result if result != _ffi.NULL else None - - -def spatialset_set_stbox(set: 'const Set *', box: 'STBox *') -> None: - set_converted = _ffi.cast('const Set *', set) - box_converted = _ffi.cast('STBox *', box) - _lib.spatialset_set_stbox(set_converted, box_converted) - - -def spatialset_to_stbox(s: 'const Set *') -> 'STBox *': - s_converted = _ffi.cast('const Set *', s) - result = _lib.spatialset_to_stbox(s_converted) - return result if result != _ffi.NULL else None - - def timestamp_to_period(t: int) -> 'Span *': t_converted = _ffi.cast('TimestampTz', t) result = _lib.timestamp_to_period(t_converted) @@ -856,7 +951,7 @@ def bigintset_value_n(s: 'const Set *', n: int) -> 'int64': result = _lib.bigintset_value_n(s_converted, n, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def bigintset_values(s: 'const Set *') -> 'int64 *': @@ -907,7 +1002,7 @@ def floatset_value_n(s: 'const Set *', n: int) -> 'double': result = _lib.floatset_value_n(s_converted, n, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def floatset_values(s: 'const Set *') -> 'double *': @@ -940,6 +1035,12 @@ def floatspanset_upper(ss: 'const SpanSet *') -> 'double': return result if result != _ffi.NULL else None +def geoset_srid(set: 'const Set *') -> 'int': + set_converted = _ffi.cast('const Set *', set) + result = _lib.geoset_srid(set_converted) + return result if result != _ffi.NULL else None + + def intset_end_value(s: 'const Set *') -> 'int': s_converted = _ffi.cast('const Set *', s) result = _lib.intset_end_value(s_converted) @@ -958,7 +1059,7 @@ def intset_value_n(s: 'const Set *', n: int) -> 'int': result = _lib.intset_value_n(s_converted, n, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def intset_values(s: 'const Set *') -> 'int *': @@ -991,58 +1092,6 @@ def intspanset_upper(ss: 'const SpanSet *') -> 'int': return result if result != _ffi.NULL else None -def set_end_value(s: 'const Set *') -> 'Datum': - s_converted = _ffi.cast('const Set *', s) - result = _lib.set_end_value(s_converted) - return result if result != _ffi.NULL else None - - -def set_hash(s: 'const Set *') -> 'uint32': - s_converted = _ffi.cast('const Set *', s) - result = _lib.set_hash(s_converted) - return result if result != _ffi.NULL else None - - -def set_hash_extended(s: 'const Set *', seed: int) -> 'uint64': - s_converted = _ffi.cast('const Set *', s) - seed_converted = _ffi.cast('uint64', seed) - result = _lib.set_hash_extended(s_converted, seed_converted) - return result if result != _ffi.NULL else None - - -def set_mem_size(s: 'const Set *') -> 'int': - s_converted = _ffi.cast('const Set *', s) - result = _lib.set_mem_size(s_converted) - return result if result != _ffi.NULL else None - - -def set_num_values(s: 'const Set *') -> 'int': - s_converted = _ffi.cast('const Set *', s) - result = _lib.set_num_values(s_converted) - return result if result != _ffi.NULL else None - - -def set_start_value(s: 'const Set *') -> 'Datum': - s_converted = _ffi.cast('const Set *', s) - result = _lib.set_start_value(s_converted) - return result if result != _ffi.NULL else None - - -def set_value_n(s: 'const Set *', n: int) -> 'Datum *': - s_converted = _ffi.cast('const Set *', s) - out_result = _ffi.new('Datum *') - result = _lib.set_value_n(s_converted, n, out_result) - if result: - return out_result if out_result != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') - - -def set_values(s: 'const Set *') -> 'Datum *': - s_converted = _ffi.cast('const Set *', s) - result = _lib.set_values(s_converted) - return result if result != _ffi.NULL else None - - def period_duration(s: 'const Span *') -> 'Interval *': s_converted = _ffi.cast('const Span *', s) result = _lib.period_duration(s_converted) @@ -1097,7 +1146,7 @@ def periodset_timestamp_n(ps: 'const SpanSet *', n: int) -> int: result = _lib.periodset_timestamp_n(ps_converted, n, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def periodset_timestamps(ps: 'const SpanSet *') -> "Tuple['TimestampTz *', 'int']": @@ -1113,15 +1162,46 @@ def periodset_upper(ps: 'const SpanSet *') -> 'TimestampTz': return result if result != _ffi.NULL else None +def set_hash(s: 'const Set *') -> 'uint32': + s_converted = _ffi.cast('const Set *', s) + result = _lib.set_hash(s_converted) + return result if result != _ffi.NULL else None + + +def set_hash_extended(s: 'const Set *', seed: int) -> 'uint64': + s_converted = _ffi.cast('const Set *', s) + seed_converted = _ffi.cast('uint64', seed) + result = _lib.set_hash_extended(s_converted, seed_converted) + return result if result != _ffi.NULL else None + + +def set_mem_size(s: 'const Set *') -> 'int': + s_converted = _ffi.cast('const Set *', s) + result = _lib.set_mem_size(s_converted) + return result if result != _ffi.NULL else None + + +def set_num_values(s: 'const Set *') -> 'int': + s_converted = _ffi.cast('const Set *', s) + result = _lib.set_num_values(s_converted) + return result if result != _ffi.NULL else None + + +def set_span(s: 'const Set *') -> 'Span *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.set_span(s_converted) + return result if result != _ffi.NULL else None + + def span_hash(s: 'const Span *') -> 'uint32': s_converted = _ffi.cast('const Span *', s) result = _lib.span_hash(s_converted) return result if result != _ffi.NULL else None -def span_hash_extended(s: 'const Span *', seed: 'Datum') -> 'uint64': +def span_hash_extended(s: 'const Span *', seed: int) -> 'uint64': s_converted = _ffi.cast('const Span *', s) - seed_converted = _ffi.cast('Datum', seed) + seed_converted = _ffi.cast('uint64', seed) result = _lib.span_hash_extended(s_converted, seed_converted) return result if result != _ffi.NULL else None @@ -1181,6 +1261,12 @@ def spanset_num_spans(ss: 'const SpanSet *') -> 'int': return result if result != _ffi.NULL else None +def spanset_span(ss: 'const SpanSet *') -> 'Span *': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.spanset_span(ss_converted) + return result if result != _ffi.NULL else None + + def spanset_span_n(ss: 'const SpanSet *', i: int) -> 'Span *': ss_converted = _ffi.cast('const SpanSet *', ss) result = _lib.spanset_span_n(ss_converted, i) @@ -1211,36 +1297,54 @@ def spanset_width(ss: 'const SpanSet *') -> 'double': return result if result != _ffi.NULL else None -def tstzset_end_timestamp(ts: 'const Set *') -> 'TimestampTz': +def spatialset_stbox(s: 'const Set *') -> 'STBox *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.spatialset_stbox(s_converted) + return result if result != _ffi.NULL else None + + +def timestampset_end_timestamp(ts: 'const Set *') -> 'TimestampTz': ts_converted = _ffi.cast('const Set *', ts) - result = _lib.tstzset_end_timestamp(ts_converted) + result = _lib.timestampset_end_timestamp(ts_converted) return result if result != _ffi.NULL else None -def tstzset_start_timestamp(ts: 'const Set *') -> 'TimestampTz': +def timestampset_start_timestamp(ts: 'const Set *') -> 'TimestampTz': ts_converted = _ffi.cast('const Set *', ts) - result = _lib.tstzset_start_timestamp(ts_converted) + result = _lib.timestampset_start_timestamp(ts_converted) return result if result != _ffi.NULL else None -def tstzset_timestamp_n(ts: 'const Set *', n: int) -> int: +def timestampset_timestamp_n(ts: 'const Set *', n: int) -> int: ts_converted = _ffi.cast('const Set *', ts) out_result = _ffi.new('TimestampTz *') - result = _lib.tstzset_timestamp_n(ts_converted, n, out_result) + result = _lib.timestampset_timestamp_n(ts_converted, n, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None -def tstzset_values(ts: 'const Set *') -> 'TimestampTz *': +def timestampset_values(ts: 'const Set *') -> 'TimestampTz *': ts_converted = _ffi.cast('const Set *', ts) - result = _lib.tstzset_values(ts_converted) + result = _lib.timestampset_values(ts_converted) return result if result != _ffi.NULL else None -def geoset_srid(set: 'const Set *') -> 'int': - set_converted = _ffi.cast('const Set *', set) - result = _lib.geoset_srid(set_converted) +def floatset_round(s: 'const Set *', maxdd: int) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.floatset_round(s_converted, maxdd) + return result if result != _ffi.NULL else None + + +def floatspan_round(s: 'const Span *', maxdd: int) -> 'Span *': + s_converted = _ffi.cast('const Span *', s) + result = _lib.floatspan_round(s_converted, maxdd) + return result if result != _ffi.NULL else None + + +def floatspanset_round(ss: 'const SpanSet *', maxdd: int) -> 'SpanSet *': + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.floatspanset_round(ss_converted, maxdd) return result if result != _ffi.NULL else None @@ -1250,6 +1354,12 @@ def floatspan_set_intspan(s1: 'const Span *', s2: 'Span *') -> None: _lib.floatspan_set_intspan(s1_converted, s2_converted) +def geoset_round(s: 'const Set *', maxdd: int) -> 'Set *': + s_converted = _ffi.cast('const Set *', s) + result = _lib.geoset_round(s_converted, maxdd) + return result if result != _ffi.NULL else None + + def intspan_set_floatspan(s1: 'const Span *', s2: 'Span *') -> None: s1_converted = _ffi.cast('const Span *', s1) s2_converted = _ffi.cast('Span *', s2) @@ -1278,18 +1388,17 @@ def periodset_tprecision(ss: 'const SpanSet *', duration: 'const Interval *', to return result if result != _ffi.NULL else None -def period_shift_tscale(p: 'Span *', shift: 'const Interval *', duration: "Optional['const Interval *']", delta: "Optional[int]", scale: "Optional['double *']") -> None: - p_converted = _ffi.cast('Span *', p) - shift_converted = _ffi.cast('const Interval *', shift) +def period_shift_tscale(p: 'const Span *', shift: "Optional['const Interval *']", duration: "Optional['const Interval *']") -> 'Span *': + p_converted = _ffi.cast('const Span *', p) + shift_converted = _ffi.cast('const Interval *', shift) if shift is not None else _ffi.NULL duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL - delta_converted = _ffi.cast('TimestampTz *', delta) if delta is not None else _ffi.NULL - scale_converted = _ffi.cast('double *', scale) if scale is not None else _ffi.NULL - _lib.period_shift_tscale(p_converted, shift_converted, duration_converted, delta_converted, scale_converted) + result = _lib.period_shift_tscale(p_converted, shift_converted, duration_converted) + return result if result != _ffi.NULL else None -def periodset_shift_tscale(ps: 'const SpanSet *', shift: 'const Interval *', duration: "Optional['const Interval *']") -> 'SpanSet *': +def periodset_shift_tscale(ps: 'const SpanSet *', shift: "Optional['const Interval *']", duration: "Optional['const Interval *']") -> 'SpanSet *': ps_converted = _ffi.cast('const SpanSet *', ps) - shift_converted = _ffi.cast('const Interval *', shift) + shift_converted = _ffi.cast('const Interval *', shift) if shift is not None else _ffi.NULL duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL result = _lib.periodset_shift_tscale(ps_converted, shift_converted, duration_converted) return result if result != _ffi.NULL else None @@ -1316,11 +1425,11 @@ def timestamp_tprecision(t: int, duration: 'const Interval *', torigin: int) -> return result if result != _ffi.NULL else None -def tstzset_shift_tscale(ts: 'const Set *', shift: 'const Interval *', duration: 'const Interval *') -> 'Set *': +def timestampset_shift_tscale(ts: 'const Set *', shift: "Optional['const Interval *']", duration: "Optional['const Interval *']") -> 'Set *': ts_converted = _ffi.cast('const Set *', ts) - shift_converted = _ffi.cast('const Interval *', shift) - duration_converted = _ffi.cast('const Interval *', duration) - result = _lib.tstzset_shift_tscale(ts_converted, shift_converted, duration_converted) + shift_converted = _ffi.cast('const Interval *', shift) if shift is not None else _ffi.NULL + duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL + result = _lib.timestampset_shift_tscale(ts_converted, shift_converted, duration_converted) return result if result != _ffi.NULL else None @@ -1937,7 +2046,7 @@ def intersection_period_timestamp(p: 'const Span *', t: int) -> int: result = _lib.intersection_period_timestamp(p_converted, t_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def intersection_periodset_timestamp(ps: 'const SpanSet *', t: int) -> int: @@ -1947,7 +2056,7 @@ def intersection_periodset_timestamp(ps: 'const SpanSet *', t: int) -> int: result = _lib.intersection_periodset_timestamp(ps_converted, t_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def intersection_span_span(s1: 'const Span *', s2: 'const Span *') -> 'Span *': @@ -1978,7 +2087,7 @@ def intersection_timestampset_timestamp(ts: 'const Set *', t: int) -> int: result = _lib.intersection_timestampset_timestamp(ts_converted, t_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def minus_set_set(s1: 'const Set *', s2: 'const Set *') -> 'Set *': @@ -2037,7 +2146,7 @@ def minus_timestamp_period(t: int, p: 'const Span *') -> int: result = _lib.minus_timestamp_period(t_converted, p_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def minus_timestamp_periodset(t: int, ps: 'const SpanSet *') -> int: @@ -2047,7 +2156,7 @@ def minus_timestamp_periodset(t: int, ps: 'const SpanSet *') -> int: result = _lib.minus_timestamp_periodset(t_converted, ps_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def minus_timestampset_timestamp(ts: 'const Set *', t: int) -> 'Set *': @@ -2099,13 +2208,6 @@ def union_spanset_spanset(ss1: 'const SpanSet *', ss2: 'const SpanSet *') -> 'Sp return result if result != _ffi.NULL else None -def union_timestamp_timestampset(t: int, ts: 'const Set *') -> 'Set *': - t_converted = _ffi.cast('TimestampTz', t) - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.union_timestamp_timestampset(t_converted, ts_converted) - return result if result != _ffi.NULL else None - - def union_timestampset_timestamp(ts: 'const Set *', t: int) -> 'Set *': ts_converted = _ffi.cast('const Set *', ts) t_converted = _ffi.cast('const TimestampTz', t) @@ -2188,27 +2290,27 @@ def bigint_union_transfn(state: 'Set *', i: int) -> 'Set *': return result if result != _ffi.NULL else None -def int_extent_transfn(s: 'Span *', i: int) -> 'Span *': +def float_extent_transfn(s: 'Span *', d: float) -> 'Span *': s_converted = _ffi.cast('Span *', s) - result = _lib.int_extent_transfn(s_converted, i) + result = _lib.float_extent_transfn(s_converted, d) return result if result != _ffi.NULL else None -def int_union_transfn(state: 'Set *', i: int) -> 'Set *': +def float_union_transfn(state: 'Set *', d: float) -> 'Set *': state_converted = _ffi.cast('Set *', state) - result = _lib.int_union_transfn(state_converted, i) + result = _lib.float_union_transfn(state_converted, d) return result if result != _ffi.NULL else None -def float_extent_transfn(s: 'Span *', d: float) -> 'Span *': +def int_extent_transfn(s: 'Span *', i: int) -> 'Span *': s_converted = _ffi.cast('Span *', s) - result = _lib.float_extent_transfn(s_converted, d) + result = _lib.int_extent_transfn(s_converted, i) return result if result != _ffi.NULL else None -def float_union_transfn(state: 'Set *', d: float) -> 'Set *': +def int_union_transfn(state: 'Set *', i: int) -> 'Set *': state_converted = _ffi.cast('Set *', state) - result = _lib.float_union_transfn(state_converted, d) + result = _lib.int_union_transfn(state_converted, i) return result if result != _ffi.NULL else None @@ -2226,6 +2328,13 @@ def periodset_tcount_transfn(state: "Optional['SkipList *']", ps: 'const SpanSet return result if result != _ffi.NULL else None +def set_extent_transfn(span: 'Span *', set: 'const Set *') -> 'Span *': + span_converted = _ffi.cast('Span *', span) + set_converted = _ffi.cast('const Set *', set) + result = _lib.set_extent_transfn(span_converted, set_converted) + return result if result != _ffi.NULL else None + + def set_union_finalfn(state: 'Set *') -> 'Set *': state_converted = _ffi.cast('Set *', state) result = _lib.set_union_finalfn(state_converted) @@ -2246,6 +2355,13 @@ def span_extent_transfn(s1: 'Span *', s2: 'const Span *') -> 'Span *': return result if result != _ffi.NULL else None +def span_union_transfn(state: 'SpanSet *', span: 'const Span *') -> 'SpanSet *': + state_converted = _ffi.cast('SpanSet *', state) + span_converted = _ffi.cast('const Span *', span) + result = _lib.span_union_transfn(state_converted, span_converted) + return result if result != _ffi.NULL else None + + def spanset_extent_transfn(s: 'Span *', ss: 'const SpanSet *') -> 'Span *': s_converted = _ffi.cast('Span *', s) ss_converted = _ffi.cast('const SpanSet *', ss) @@ -2253,15 +2369,28 @@ def spanset_extent_transfn(s: 'Span *', ss: 'const SpanSet *') -> 'Span *': return result if result != _ffi.NULL else None -def text_union_transfn(state: 'Set *', txt: str) -> 'Set *': - state_converted = _ffi.cast('Set *', state) - txt_converted = cstring2text(txt) - result = _lib.text_union_transfn(state_converted, txt_converted) +def spanset_union_finalfn(state: 'SpanSet *') -> 'SpanSet *': + state_converted = _ffi.cast('SpanSet *', state) + result = _lib.spanset_union_finalfn(state_converted) return result if result != _ffi.NULL else None -def timestamp_extent_transfn(p: "Optional['Span *']", t: int) -> 'Span *': - p_converted = _ffi.cast('Span *', p) if p is not None else _ffi.NULL +def spanset_union_transfn(state: 'SpanSet *', ss: 'const SpanSet *') -> 'SpanSet *': + state_converted = _ffi.cast('SpanSet *', state) + ss_converted = _ffi.cast('const SpanSet *', ss) + result = _lib.spanset_union_transfn(state_converted, ss_converted) + return result if result != _ffi.NULL else None + + +def text_union_transfn(state: 'Set *', txt: str) -> 'Set *': + state_converted = _ffi.cast('Set *', state) + txt_converted = cstring2text(txt) + result = _lib.text_union_transfn(state_converted, txt_converted) + return result if result != _ffi.NULL else None + + +def timestamp_extent_transfn(p: "Optional['Span *']", t: int) -> 'Span *': + p_converted = _ffi.cast('Span *', p) if p is not None else _ffi.NULL t_converted = _ffi.cast('TimestampTz', t) result = _lib.timestamp_extent_transfn(p_converted, t_converted) return result if result != _ffi.NULL else None @@ -2281,17 +2410,10 @@ def timestamp_union_transfn(state: 'Set *', t: int) -> 'Set *': return result if result != _ffi.NULL else None -def set_extent_transfn(span: 'Span *', set: 'const Set *') -> 'Span *': - span_converted = _ffi.cast('Span *', span) - set_converted = _ffi.cast('const Set *', set) - result = _lib.set_extent_transfn(span_converted, set_converted) - return result if result != _ffi.NULL else None - - -def tstzset_tcount_transfn(state: 'SkipList *', ts: 'const Set *') -> 'SkipList *': - state_converted = _ffi.cast('SkipList *', state) +def timestampset_tcount_transfn(state: "Optional['SkipList *']", ts: 'const Set *') -> 'SkipList *': + state_converted = _ffi.cast('SkipList *', state) if state is not None else _ffi.NULL ts_converted = _ffi.cast('const Set *', ts) - result = _lib.tstzset_tcount_transfn(state_converted, ts_converted) + result = _lib.timestampset_tcount_transfn(state_converted, ts_converted) return result if result != _ffi.NULL else None @@ -2455,9 +2577,9 @@ def tbox_out(box: 'const TBox *', maxdd: int) -> str: return result if result != _ffi.NULL else None -def tbox_from_wkb(wkb: 'const uint8_t *', size: int) -> 'TBox *': - wkb_converted = _ffi.cast('const uint8_t *', wkb) - result = _lib.tbox_from_wkb(wkb_converted, size) +def tbox_from_wkb(wkb: bytes) -> 'TBOX *': + wkb_converted = _ffi.new('uint8_t []', wkb) + result = _lib.tbox_from_wkb(wkb_converted, len(wkb)) return result if result != _ffi.NULL else None @@ -2467,9 +2589,9 @@ def tbox_from_hexwkb(hexwkb: str) -> 'TBox *': return result if result != _ffi.NULL else None -def stbox_from_wkb(wkb: 'const uint8_t *', size: int) -> 'STBox *': - wkb_converted = _ffi.cast('const uint8_t *', wkb) - result = _lib.stbox_from_wkb(wkb_converted, size) +def stbox_from_wkb(wkb: bytes) -> 'STBOX *': + wkb_converted = _ffi.new('uint8_t []', wkb) + result = _lib.stbox_from_wkb(wkb_converted, len(wkb)) return result if result != _ffi.NULL else None @@ -2479,12 +2601,13 @@ def stbox_from_hexwkb(hexwkb: str) -> 'STBox *': return result if result != _ffi.NULL else None -def tbox_as_wkb(box: 'const TBox *', variant: int) -> "Tuple['uint8_t *', 'size_t *']": +def tbox_as_wkb(box: 'const TBox *', variant: int) -> bytes: box_converted = _ffi.cast('const TBox *', box) variant_converted = _ffi.cast('uint8_t', variant) size_out = _ffi.new('size_t *') result = _lib.tbox_as_wkb(box_converted, variant_converted, size_out) - return result if result != _ffi.NULL else None, size_out[0] + result_converted = bytes(result[i] for i in range(size_out[0])) if result != _ffi.NULL else None + return result_converted def tbox_as_hexwkb(box: 'const TBox *', variant: int) -> "Tuple[str, 'size_t *']": @@ -2496,12 +2619,13 @@ def tbox_as_hexwkb(box: 'const TBox *', variant: int) -> "Tuple[str, 'size_t *'] return result if result != _ffi.NULL else None, size[0] -def stbox_as_wkb(box: 'const STBox *', variant: int) -> "Tuple['uint8_t *', 'size_t *']": +def stbox_as_wkb(box: 'const STBox *', variant: int) -> bytes: box_converted = _ffi.cast('const STBox *', box) variant_converted = _ffi.cast('uint8_t', variant) size_out = _ffi.new('size_t *') result = _lib.stbox_as_wkb(box_converted, variant_converted, size_out) - return result if result != _ffi.NULL else None, size_out[0] + result_converted = bytes(result[i] for i in range(size_out[0])) if result != _ffi.NULL else None + return result_converted def stbox_as_hexwkb(box: 'const STBox *', variant: int) -> "Tuple[str, 'size_t *']": @@ -2526,18 +2650,18 @@ def stbox_out(box: 'const STBox *', maxdd: int) -> str: return result if result != _ffi.NULL else None -def tbox_make(p: "Optional['const Span *']", s: "Optional['const Span *']") -> 'TBox *': - p_converted = _ffi.cast('const Span *', p) if p is not None else _ffi.NULL +def tbox_make(s: "Optional['const Span *']", p: "Optional['const Span *']") -> 'TBox *': s_converted = _ffi.cast('const Span *', s) if s is not None else _ffi.NULL - result = _lib.tbox_make(p_converted, s_converted) + p_converted = _ffi.cast('const Span *', p) if p is not None else _ffi.NULL + result = _lib.tbox_make(s_converted, p_converted) return result if result != _ffi.NULL else None -def tbox_set(p: 'const Span *', s: 'const Span *', box: 'TBox *') -> None: - p_converted = _ffi.cast('const Span *', p) +def tbox_set(s: 'const Span *', p: 'const Span *', box: 'TBox *') -> None: s_converted = _ffi.cast('const Span *', s) + p_converted = _ffi.cast('const Span *', p) box_converted = _ffi.cast('TBox *', box) - _lib.tbox_set(p_converted, s_converted, box_converted) + _lib.tbox_set(s_converted, p_converted, box_converted) def tbox_copy(box: 'const TBox *') -> 'TBox *': @@ -2582,9 +2706,9 @@ def timestamp_to_tbox(t: int) -> 'TBox *': return result if result != _ffi.NULL else None -def tstzset_to_tbox(ss: 'const Set *') -> 'TBox *': +def timestampset_to_tbox(ss: 'const Set *') -> 'TBox *': ss_converted = _ffi.cast('const Set *', ss) - result = _lib.tstzset_to_tbox(ss_converted) + result = _lib.timestampset_to_tbox(ss_converted) return result if result != _ffi.NULL else None @@ -2706,9 +2830,9 @@ def timestamp_to_stbox(t: int) -> 'STBox *': return result if result != _ffi.NULL else None -def tstzset_to_stbox(ts: 'const Set *') -> 'STBox *': +def timestampset_to_stbox(ts: 'const Set *') -> 'STBox *': ts_converted = _ffi.cast('const Set *', ts) - result = _lib.tstzset_to_stbox(ts_converted) + result = _lib.timestampset_to_stbox(ts_converted) return result if result != _ffi.NULL else None @@ -2742,7 +2866,16 @@ def tbox_xmin(box: 'const TBox *') -> 'double': result = _lib.tbox_xmin(box_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None + + +def tbox_xmin_inc(box: 'const TBox *') -> 'bool': + box_converted = _ffi.cast('const TBox *', box) + out_result = _ffi.new('bool *') + result = _lib.tbox_xmin_inc(box_converted, out_result) + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None def tbox_xmax(box: 'const TBox *') -> 'double': @@ -2751,7 +2884,16 @@ def tbox_xmax(box: 'const TBox *') -> 'double': result = _lib.tbox_xmax(box_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None + + +def tbox_xmax_inc(box: 'const TBox *') -> 'bool': + box_converted = _ffi.cast('const TBox *', box) + out_result = _ffi.new('bool *') + result = _lib.tbox_xmax_inc(box_converted, out_result) + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None def tbox_tmin(box: 'const TBox *') -> int: @@ -2760,7 +2902,16 @@ def tbox_tmin(box: 'const TBox *') -> int: result = _lib.tbox_tmin(box_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None + + +def tbox_tmin_inc(box: 'const TBox *') -> 'bool': + box_converted = _ffi.cast('const TBox *', box) + out_result = _ffi.new('bool *') + result = _lib.tbox_tmin_inc(box_converted, out_result) + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None def tbox_tmax(box: 'const TBox *') -> int: @@ -2769,7 +2920,16 @@ def tbox_tmax(box: 'const TBox *') -> int: result = _lib.tbox_tmax(box_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None + + +def tbox_tmax_inc(box: 'const TBox *') -> 'bool': + box_converted = _ffi.cast('const TBox *', box) + out_result = _ffi.new('bool *') + result = _lib.tbox_tmax_inc(box_converted, out_result) + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None def stbox_hasx(box: 'const STBox *') -> 'bool': @@ -2802,7 +2962,7 @@ def stbox_xmin(box: 'const STBox *') -> 'double': result = _lib.stbox_xmin(box_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def stbox_xmax(box: 'const STBox *') -> 'double': @@ -2811,7 +2971,7 @@ def stbox_xmax(box: 'const STBox *') -> 'double': result = _lib.stbox_xmax(box_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def stbox_ymin(box: 'const STBox *') -> 'double': @@ -2820,7 +2980,7 @@ def stbox_ymin(box: 'const STBox *') -> 'double': result = _lib.stbox_ymin(box_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def stbox_ymax(box: 'const STBox *') -> 'double': @@ -2829,7 +2989,7 @@ def stbox_ymax(box: 'const STBox *') -> 'double': result = _lib.stbox_ymax(box_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def stbox_zmin(box: 'const STBox *') -> 'double': @@ -2838,7 +2998,7 @@ def stbox_zmin(box: 'const STBox *') -> 'double': result = _lib.stbox_zmin(box_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def stbox_zmax(box: 'const STBox *') -> 'double': @@ -2847,7 +3007,7 @@ def stbox_zmax(box: 'const STBox *') -> 'double': result = _lib.stbox_zmax(box_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def stbox_tmin(box: 'const STBox *') -> int: @@ -2856,7 +3016,16 @@ def stbox_tmin(box: 'const STBox *') -> int: result = _lib.stbox_tmin(box_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None + + +def stbox_tmin_inc(box: 'const STBox *') -> 'bool': + box_converted = _ffi.cast('const STBox *', box) + out_result = _ffi.new('bool *') + result = _lib.stbox_tmin_inc(box_converted, out_result) + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None def stbox_tmax(box: 'const STBox *') -> int: @@ -2865,7 +3034,16 @@ def stbox_tmax(box: 'const STBox *') -> int: result = _lib.stbox_tmax(box_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None + + +def stbox_tmax_inc(box: 'const STBox *') -> 'bool': + box_converted = _ffi.cast('const STBox *', box) + out_result = _ffi.new('bool *') + result = _lib.stbox_tmax_inc(box_converted, out_result) + if result: + return out_result[0] if out_result[0] != _ffi.NULL else None + return None def stbox_srid(box: 'const STBox *') -> 'int32': @@ -2874,30 +3052,35 @@ def stbox_srid(box: 'const STBox *') -> 'int32': return result if result != _ffi.NULL else None -def tbox_expand(box1: 'const TBox *', box2: 'TBox *') -> None: - box1_converted = _ffi.cast('const TBox *', box1) - box2_converted = _ffi.cast('TBox *', box2) - _lib.tbox_expand(box1_converted, box2_converted) +def stbox_expand(box1: 'const STBox *', box2: 'STBox *') -> None: + box1_converted = _ffi.cast('const STBox *', box1) + box2_converted = _ffi.cast('STBox *', box2) + _lib.stbox_expand(box1_converted, box2_converted) -def tbox_expand_value(box: 'const TBox *', d: 'const double') -> 'TBox *': - box_converted = _ffi.cast('const TBox *', box) - d_converted = _ffi.cast('const double', d) - result = _lib.tbox_expand_value(box_converted, d_converted) +def stbox_expand_space(box: 'const STBox *', d: float) -> 'STBox *': + box_converted = _ffi.cast('const STBox *', box) + result = _lib.stbox_expand_space(box_converted, d) return result if result != _ffi.NULL else None -def tbox_expand_time(box: 'const TBox *', interval: 'const Interval *') -> 'TBox *': - box_converted = _ffi.cast('const TBox *', box) +def stbox_expand_time(box: 'const STBox *', interval: 'const Interval *') -> 'STBox *': + box_converted = _ffi.cast('const STBox *', box) interval_converted = _ffi.cast('const Interval *', interval) - result = _lib.tbox_expand_time(box_converted, interval_converted) + result = _lib.stbox_expand_time(box_converted, interval_converted) return result if result != _ffi.NULL else None -def stbox_expand(box1: 'const STBox *', box2: 'STBox *') -> None: - box1_converted = _ffi.cast('const STBox *', box1) - box2_converted = _ffi.cast('STBox *', box2) - _lib.stbox_expand(box1_converted, box2_converted) +def stbox_get_space(box: 'const STBox *') -> 'STBox *': + box_converted = _ffi.cast('const STBox *', box) + result = _lib.stbox_get_space(box_converted) + return result if result != _ffi.NULL else None + + +def stbox_round(box: 'const STBox *', maxdd: int) -> 'STBox *': + box_converted = _ffi.cast('const STBox *', box) + result = _lib.stbox_round(box_converted, maxdd) + return result if result != _ffi.NULL else None def stbox_set_srid(box: 'const STBox *', srid: int) -> 'STBox *': @@ -2907,22 +3090,29 @@ def stbox_set_srid(box: 'const STBox *', srid: int) -> 'STBox *': return result if result != _ffi.NULL else None -def stbox_get_space(box: 'const STBox *') -> 'STBox *': - box_converted = _ffi.cast('const STBox *', box) - result = _lib.stbox_get_space(box_converted) - return result if result != _ffi.NULL else None +def tbox_expand(box1: 'const TBox *', box2: 'TBox *') -> None: + box1_converted = _ffi.cast('const TBox *', box1) + box2_converted = _ffi.cast('TBox *', box2) + _lib.tbox_expand(box1_converted, box2_converted) -def stbox_expand_space(box: 'const STBox *', d: float) -> 'STBox *': - box_converted = _ffi.cast('const STBox *', box) - result = _lib.stbox_expand_space(box_converted, d) +def tbox_expand_value(box: 'const TBox *', d: 'const double') -> 'TBox *': + box_converted = _ffi.cast('const TBox *', box) + d_converted = _ffi.cast('const double', d) + result = _lib.tbox_expand_value(box_converted, d_converted) return result if result != _ffi.NULL else None -def stbox_expand_time(box: 'const STBox *', interval: 'const Interval *') -> 'STBox *': - box_converted = _ffi.cast('const STBox *', box) +def tbox_expand_time(box: 'const TBox *', interval: 'const Interval *') -> 'TBox *': + box_converted = _ffi.cast('const TBox *', box) interval_converted = _ffi.cast('const Interval *', interval) - result = _lib.stbox_expand_time(box_converted, interval_converted) + result = _lib.tbox_expand_time(box_converted, interval_converted) + return result if result != _ffi.NULL else None + + +def tbox_round(box: 'const TBox *', maxdd: int) -> 'TBox *': + box_converted = _ffi.cast('const TBox *', box) + result = _lib.tbox_round(box_converted, maxdd) return result if result != _ffi.NULL else None @@ -3178,7 +3368,7 @@ def inter_tbox_tbox(box1: 'const TBox *', box2: 'const TBox *') -> 'TBox *': result = _lib.inter_tbox_tbox(box1_converted, box2_converted, out_result) if result: return out_result if out_result != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def intersection_tbox_tbox(box1: 'const TBox *', box2: 'const TBox *') -> 'TBox *': @@ -3202,7 +3392,7 @@ def inter_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *') -> 'STBox *' result = _lib.inter_stbox_stbox(box1_converted, box2_converted, out_result) if result: return out_result if out_result != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def intersection_stbox_stbox(box1: 'const STBox *', box2: 'const STBox *') -> 'STBox *': @@ -3317,18 +3507,6 @@ def stbox_gt(box1: 'const STBox *', box2: 'const STBox *') -> 'bool': return result if result != _ffi.NULL else None -def cstring2text(cstring: str) -> 'text *': - cstring_converted = cstring.encode('utf-8') - result = _lib.cstring2text(cstring_converted) - return result - - -def text2cstring(textptr: 'text *') -> str: - result = _lib.text2cstring(textptr) - result = _ffi.string(result).decode('utf-8') - return result - - def tbool_in(string: str) -> 'Temporal *': string_converted = string.encode('utf-8') result = _lib.tbool_in(string_converted) @@ -3359,12 +3537,13 @@ def temporal_as_mfjson(temp: 'const Temporal *', with_bbox: bool, flags: int, pr return result if result != _ffi.NULL else None -def temporal_as_wkb(temp: 'const Temporal *', variant: int) -> "Tuple['uint8_t *', 'size_t *']": +def temporal_as_wkb(temp: 'const Temporal *', variant: int) -> bytes: temp_converted = _ffi.cast('const Temporal *', temp) variant_converted = _ffi.cast('uint8_t', variant) size_out = _ffi.new('size_t *') result = _lib.temporal_as_wkb(temp_converted, variant_converted, size_out) - return result if result != _ffi.NULL else None, size_out[0] + result_converted = bytes(result[i] for i in range(size_out[0])) if result != _ffi.NULL else None + return result_converted def temporal_from_hexwkb(hexwkb: str) -> 'Temporal *': @@ -3379,9 +3558,9 @@ def temporal_from_mfjson(mfjson: str) -> 'Temporal *': return result if result != _ffi.NULL else None -def temporal_from_wkb(wkb: 'const uint8_t *', size: int) -> 'Temporal *': - wkb_converted = _ffi.cast('const uint8_t *', wkb) - result = _lib.temporal_from_wkb(wkb_converted, size) +def temporal_from_wkb(wkb: bytes) -> 'Temporal *': + wkb_converted = _ffi.new('uint8_t []', wkb) + result = _lib.temporal_from_wkb(wkb_converted, len(wkb)) return result if result != _ffi.NULL else None @@ -3457,9 +3636,9 @@ def ttext_out(temp: 'const Temporal *') -> str: return result if result != _ffi.NULL else None -def tbool_from_base(b: bool, temp: 'const Temporal *') -> 'Temporal *': +def tbool_from_base_temp(b: bool, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tbool_from_base(b, temp_converted) + result = _lib.tbool_from_base_temp(b, temp_converted) return result if result != _ffi.NULL else None @@ -3469,33 +3648,21 @@ def tboolinst_make(b: bool, t: int) -> 'TInstant *': return result if result != _ffi.NULL else None -def tbooldiscseq_from_base_time(b: bool, ts: 'const Set *') -> 'TSequence *': - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.tbooldiscseq_from_base_time(b, ts_converted) - return result if result != _ffi.NULL else None - - -def tboolseq_from_base(b: bool, seq: 'const TSequence *') -> 'TSequence *': - seq_converted = _ffi.cast('const TSequence *', seq) - result = _lib.tboolseq_from_base(b, seq_converted) - return result if result != _ffi.NULL else None - - -def tboolseq_from_base_time(b: bool, p: 'const Span *') -> 'TSequence *': +def tboolseq_from_base_period(b: bool, p: 'const Span *') -> 'TSequence *': p_converted = _ffi.cast('const Span *', p) - result = _lib.tboolseq_from_base_time(b, p_converted) + result = _lib.tboolseq_from_base_period(b, p_converted) return result if result != _ffi.NULL else None -def tboolseqset_from_base(b: bool, ss: 'const TSequenceSet *') -> 'TSequenceSet *': - ss_converted = _ffi.cast('const TSequenceSet *', ss) - result = _lib.tboolseqset_from_base(b, ss_converted) +def tboolseq_from_base_timestampset(b: bool, ts: 'const Set *') -> 'TSequence *': + ts_converted = _ffi.cast('const Set *', ts) + result = _lib.tboolseq_from_base_timestampset(b, ts_converted) return result if result != _ffi.NULL else None -def tboolseqset_from_base_time(b: bool, ps: 'const SpanSet *') -> 'TSequenceSet *': +def tboolseqset_from_base_periodset(b: bool, ps: 'const SpanSet *') -> 'TSequenceSet *': ps_converted = _ffi.cast('const SpanSet *', ps) - result = _lib.tboolseqset_from_base_time(b, ps_converted) + result = _lib.tboolseqset_from_base_periodset(b, ps_converted) return result if result != _ffi.NULL else None @@ -3505,10 +3672,9 @@ def temporal_copy(temp: 'const Temporal *') -> 'Temporal *': return result if result != _ffi.NULL else None -def tfloat_from_base(d: float, temp: 'const Temporal *', interp: 'interpType') -> 'Temporal *': +def tfloat_from_base_temp(d: float, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - interp_converted = _ffi.cast('interpType', interp) - result = _lib.tfloat_from_base(d, temp_converted, interp_converted) + result = _lib.tfloat_from_base_temp(d, temp_converted) return result if result != _ffi.NULL else None @@ -3518,45 +3684,30 @@ def tfloatinst_make(d: float, t: int) -> 'TInstant *': return result if result != _ffi.NULL else None -def tfloatdiscseq_from_base_time(d: float, ts: 'const Set *') -> 'TSequence *': - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.tfloatdiscseq_from_base_time(d, ts_converted) - return result if result != _ffi.NULL else None - - -def tfloatseq_from_base(d: float, seq: 'const TSequence *', interp: 'interpType') -> 'TSequence *': - seq_converted = _ffi.cast('const TSequence *', seq) - interp_converted = _ffi.cast('interpType', interp) - result = _lib.tfloatseq_from_base(d, seq_converted, interp_converted) - return result if result != _ffi.NULL else None - - -def tfloatseq_from_base_time(d: float, p: 'const Span *', interp: 'interpType') -> 'TSequence *': +def tfloatseq_from_base_period(d: float, p: 'const Span *', interp: 'interpType') -> 'TSequence *': p_converted = _ffi.cast('const Span *', p) interp_converted = _ffi.cast('interpType', interp) - result = _lib.tfloatseq_from_base_time(d, p_converted, interp_converted) + result = _lib.tfloatseq_from_base_period(d, p_converted, interp_converted) return result if result != _ffi.NULL else None -def tfloatseqset_from_base(d: float, ss: 'const TSequenceSet *', interp: 'interpType') -> 'TSequenceSet *': - ss_converted = _ffi.cast('const TSequenceSet *', ss) - interp_converted = _ffi.cast('interpType', interp) - result = _lib.tfloatseqset_from_base(d, ss_converted, interp_converted) +def tfloatseq_from_base_timestampset(d: float, ts: 'const Set *') -> 'TSequence *': + ts_converted = _ffi.cast('const Set *', ts) + result = _lib.tfloatseq_from_base_timestampset(d, ts_converted) return result if result != _ffi.NULL else None -def tfloatseqset_from_base_time(d: float, ps: 'const SpanSet *', interp: 'interpType') -> 'TSequenceSet *': +def tfloatseqset_from_base_periodset(d: float, ps: 'const SpanSet *', interp: 'interpType') -> 'TSequenceSet *': ps_converted = _ffi.cast('const SpanSet *', ps) interp_converted = _ffi.cast('interpType', interp) - result = _lib.tfloatseqset_from_base_time(d, ps_converted, interp_converted) + result = _lib.tfloatseqset_from_base_periodset(d, ps_converted, interp_converted) return result if result != _ffi.NULL else None -def tgeogpoint_from_base(gs: 'const GSERIALIZED *', temp: 'const Temporal *', interp: 'interpType') -> 'Temporal *': +def tgeogpoint_from_base_temp(gs: 'const GSERIALIZED *', temp: 'const Temporal *') -> 'Temporal *': gs_converted = _ffi.cast('const GSERIALIZED *', gs) temp_converted = _ffi.cast('const Temporal *', temp) - interp_converted = _ffi.cast('interpType', interp) - result = _lib.tgeogpoint_from_base(gs_converted, temp_converted, interp_converted) + result = _lib.tgeogpoint_from_base_temp(gs_converted, temp_converted) return result if result != _ffi.NULL else None @@ -3567,50 +3718,33 @@ def tgeogpointinst_make(gs: 'const GSERIALIZED *', t: int) -> 'TInstant *': return result if result != _ffi.NULL else None -def tgeogpointdiscseq_from_base_time(gs: 'const GSERIALIZED *', ts: 'const Set *') -> 'TSequence *': - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.tgeogpointdiscseq_from_base_time(gs_converted, ts_converted) - return result if result != _ffi.NULL else None - - -def tgeogpointseq_from_base(gs: 'const GSERIALIZED *', seq: 'const TSequence *', interp: 'interpType') -> 'TSequence *': - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - seq_converted = _ffi.cast('const TSequence *', seq) - interp_converted = _ffi.cast('interpType', interp) - result = _lib.tgeogpointseq_from_base(gs_converted, seq_converted, interp_converted) - return result if result != _ffi.NULL else None - - -def tgeogpointseq_from_base_time(gs: 'const GSERIALIZED *', p: 'const Span *', interp: 'interpType') -> 'TSequence *': +def tgeogpointseq_from_base_period(gs: 'const GSERIALIZED *', p: 'const Span *', interp: 'interpType') -> 'TSequence *': gs_converted = _ffi.cast('const GSERIALIZED *', gs) p_converted = _ffi.cast('const Span *', p) interp_converted = _ffi.cast('interpType', interp) - result = _lib.tgeogpointseq_from_base_time(gs_converted, p_converted, interp_converted) + result = _lib.tgeogpointseq_from_base_period(gs_converted, p_converted, interp_converted) return result if result != _ffi.NULL else None -def tgeogpointseqset_from_base(gs: 'const GSERIALIZED *', ss: 'const TSequenceSet *', interp: 'interpType') -> 'TSequenceSet *': +def tgeogpointseq_from_base_timestampset(gs: 'const GSERIALIZED *', ts: 'const Set *') -> 'TSequence *': gs_converted = _ffi.cast('const GSERIALIZED *', gs) - ss_converted = _ffi.cast('const TSequenceSet *', ss) - interp_converted = _ffi.cast('interpType', interp) - result = _lib.tgeogpointseqset_from_base(gs_converted, ss_converted, interp_converted) + ts_converted = _ffi.cast('const Set *', ts) + result = _lib.tgeogpointseq_from_base_timestampset(gs_converted, ts_converted) return result if result != _ffi.NULL else None -def tgeogpointseqset_from_base_time(gs: 'const GSERIALIZED *', ps: 'const SpanSet *', interp: 'interpType') -> 'TSequenceSet *': +def tgeogpointseqset_from_base_periodset(gs: 'const GSERIALIZED *', ps: 'const SpanSet *', interp: 'interpType') -> 'TSequenceSet *': gs_converted = _ffi.cast('const GSERIALIZED *', gs) ps_converted = _ffi.cast('const SpanSet *', ps) interp_converted = _ffi.cast('interpType', interp) - result = _lib.tgeogpointseqset_from_base_time(gs_converted, ps_converted, interp_converted) + result = _lib.tgeogpointseqset_from_base_periodset(gs_converted, ps_converted, interp_converted) return result if result != _ffi.NULL else None -def tgeompoint_from_base(gs: 'const GSERIALIZED *', temp: 'const Temporal *', interp: 'interpType') -> 'Temporal *': +def tgeompoint_from_base_temp(gs: 'const GSERIALIZED *', temp: 'const Temporal *') -> 'Temporal *': gs_converted = _ffi.cast('const GSERIALIZED *', gs) temp_converted = _ffi.cast('const Temporal *', temp) - interp_converted = _ffi.cast('interpType', interp) - result = _lib.tgeompoint_from_base(gs_converted, temp_converted, interp_converted) + result = _lib.tgeompoint_from_base_temp(gs_converted, temp_converted) return result if result != _ffi.NULL else None @@ -3621,48 +3755,32 @@ def tgeompointinst_make(gs: 'const GSERIALIZED *', t: int) -> 'TInstant *': return result if result != _ffi.NULL else None -def tgeompointdiscseq_from_base_time(gs: 'const GSERIALIZED *', ts: 'const Set *') -> 'TSequence *': - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.tgeompointdiscseq_from_base_time(gs_converted, ts_converted) - return result if result != _ffi.NULL else None - - -def tgeompointseq_from_base(gs: 'const GSERIALIZED *', seq: 'const TSequence *', interp: 'interpType') -> 'TSequence *': - gs_converted = _ffi.cast('const GSERIALIZED *', gs) - seq_converted = _ffi.cast('const TSequence *', seq) - interp_converted = _ffi.cast('interpType', interp) - result = _lib.tgeompointseq_from_base(gs_converted, seq_converted, interp_converted) - return result if result != _ffi.NULL else None - - -def tgeompointseq_from_base_time(gs: 'const GSERIALIZED *', p: 'const Span *', interp: 'interpType') -> 'TSequence *': +def tgeompointseq_from_base_period(gs: 'const GSERIALIZED *', p: 'const Span *', interp: 'interpType') -> 'TSequence *': gs_converted = _ffi.cast('const GSERIALIZED *', gs) p_converted = _ffi.cast('const Span *', p) interp_converted = _ffi.cast('interpType', interp) - result = _lib.tgeompointseq_from_base_time(gs_converted, p_converted, interp_converted) + result = _lib.tgeompointseq_from_base_period(gs_converted, p_converted, interp_converted) return result if result != _ffi.NULL else None -def tgeompointseqset_from_base(gs: 'const GSERIALIZED *', ss: 'const TSequenceSet *', interp: 'interpType') -> 'TSequenceSet *': +def tgeompointseq_from_base_timestampset(gs: 'const GSERIALIZED *', ts: 'const Set *') -> 'TSequence *': gs_converted = _ffi.cast('const GSERIALIZED *', gs) - ss_converted = _ffi.cast('const TSequenceSet *', ss) - interp_converted = _ffi.cast('interpType', interp) - result = _lib.tgeompointseqset_from_base(gs_converted, ss_converted, interp_converted) + ts_converted = _ffi.cast('const Set *', ts) + result = _lib.tgeompointseq_from_base_timestampset(gs_converted, ts_converted) return result if result != _ffi.NULL else None -def tgeompointseqset_from_base_time(gs: 'const GSERIALIZED *', ps: 'const SpanSet *', interp: 'interpType') -> 'TSequenceSet *': +def tgeompointseqset_from_base_periodset(gs: 'const GSERIALIZED *', ps: 'const SpanSet *', interp: 'interpType') -> 'TSequenceSet *': gs_converted = _ffi.cast('const GSERIALIZED *', gs) ps_converted = _ffi.cast('const SpanSet *', ps) interp_converted = _ffi.cast('interpType', interp) - result = _lib.tgeompointseqset_from_base_time(gs_converted, ps_converted, interp_converted) + result = _lib.tgeompointseqset_from_base_periodset(gs_converted, ps_converted, interp_converted) return result if result != _ffi.NULL else None -def tint_from_base(i: int, temp: 'const Temporal *') -> 'Temporal *': +def tint_from_base_temp(i: int, temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tint_from_base(i, temp_converted) + result = _lib.tint_from_base_temp(i, temp_converted) return result if result != _ffi.NULL else None @@ -3672,33 +3790,21 @@ def tintinst_make(i: int, t: int) -> 'TInstant *': return result if result != _ffi.NULL else None -def tintdiscseq_from_base_time(i: int, ts: 'const Set *') -> 'TSequence *': - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.tintdiscseq_from_base_time(i, ts_converted) - return result if result != _ffi.NULL else None - - -def tintseq_from_base(i: int, seq: 'const TSequence *') -> 'TSequence *': - seq_converted = _ffi.cast('const TSequence *', seq) - result = _lib.tintseq_from_base(i, seq_converted) - return result if result != _ffi.NULL else None - - -def tintseq_from_base_time(i: int, p: 'const Span *') -> 'TSequence *': +def tintseq_from_base_period(i: int, p: 'const Span *') -> 'TSequence *': p_converted = _ffi.cast('const Span *', p) - result = _lib.tintseq_from_base_time(i, p_converted) + result = _lib.tintseq_from_base_period(i, p_converted) return result if result != _ffi.NULL else None -def tintseqset_from_base(i: int, ss: 'const TSequenceSet *') -> 'TSequenceSet *': - ss_converted = _ffi.cast('const TSequenceSet *', ss) - result = _lib.tintseqset_from_base(i, ss_converted) +def tintseq_from_base_timestampset(i: int, ts: 'const Set *') -> 'TSequence *': + ts_converted = _ffi.cast('const Set *', ts) + result = _lib.tintseq_from_base_timestampset(i, ts_converted) return result if result != _ffi.NULL else None -def tintseqset_from_base_time(i: int, ps: 'const SpanSet *') -> 'TSequenceSet *': +def tintseqset_from_base_periodset(i: int, ps: 'const SpanSet *') -> 'TSequenceSet *': ps_converted = _ffi.cast('const SpanSet *', ps) - result = _lib.tintseqset_from_base_time(i, ps_converted) + result = _lib.tintseqset_from_base_periodset(i, ps_converted) return result if result != _ffi.NULL else None @@ -3716,21 +3822,6 @@ def tsequence_make_exp(instants: 'const TInstant **', count: int, maxcount: int, return result if result != _ffi.NULL else None -def tpointseq_make_coords(xcoords: 'const double *', ycoords: 'const double *', zcoords: "Optional['const double *']", times: 'const TimestampTz *', count: int, srid: int, geodetic: bool, lower_inc: bool, upper_inc: bool, interp: 'interpType', normalize: bool) -> 'TSequence *': - zcoords_converted = zcoords if zcoords is not None else _ffi.NULL - srid_converted = _ffi.cast('int32', srid) - interp_converted = _ffi.cast('interpType', interp) - result = _lib.tpointseq_make_coords(xcoords, ycoords, zcoords_converted, times, count, srid_converted, geodetic, lower_inc, upper_inc, interp_converted, normalize) - return result if result != _ffi.NULL else None - - -def tsequence_make_free(instants: 'TInstant **', count: int, lower_inc: bool, upper_inc: bool, interp: 'interpType', normalize: bool) -> 'TSequence *': - instants_converted = [_ffi.cast('TInstant *', x) for x in instants] - interp_converted = _ffi.cast('interpType', interp) - result = _lib.tsequence_make_free(instants_converted, count, lower_inc, upper_inc, interp_converted, normalize) - return result if result != _ffi.NULL else None - - def tsequenceset_make(sequences: 'const TSequence **', count: int, normalize: bool) -> 'TSequenceSet *': sequences_converted = [_ffi.cast('const TSequence *', x) for x in sequences] result = _lib.tsequenceset_make(sequences_converted, count, normalize) @@ -3743,12 +3834,6 @@ def tsequenceset_make_exp(sequences: 'const TSequence **', count: int, maxcount: return result if result != _ffi.NULL else None -def tsequenceset_make_free(sequences: 'TSequence **', count: int, normalize: bool) -> 'TSequenceSet *': - sequences_converted = [_ffi.cast('TSequence *', x) for x in sequences] - result = _lib.tsequenceset_make_free(sequences_converted, count, normalize) - return result if result != _ffi.NULL else None - - def tsequenceset_make_gaps(instants: 'const TInstant **', count: int, interp: 'interpType', maxt: 'Interval *', maxdist: float) -> 'TSequenceSet *': instants_converted = [_ffi.cast('const TInstant *', x) for x in instants] interp_converted = _ffi.cast('interpType', interp) @@ -3757,10 +3842,10 @@ def tsequenceset_make_gaps(instants: 'const TInstant **', count: int, interp: 'i return result if result != _ffi.NULL else None -def ttext_from_base(txt: str, temp: 'const Temporal *') -> 'Temporal *': +def ttext_from_base_temp(txt: str, temp: 'const Temporal *') -> 'Temporal *': txt_converted = cstring2text(txt) temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.ttext_from_base(txt_converted, temp_converted) + result = _lib.ttext_from_base_temp(txt_converted, temp_converted) return result if result != _ffi.NULL else None @@ -3771,38 +3856,30 @@ def ttextinst_make(txt: str, t: int) -> 'TInstant *': return result if result != _ffi.NULL else None -def ttextdiscseq_from_base_time(txt: str, ts: 'const Set *') -> 'TSequence *': - txt_converted = cstring2text(txt) - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.ttextdiscseq_from_base_time(txt_converted, ts_converted) - return result if result != _ffi.NULL else None - - -def ttextseq_from_base(txt: str, seq: 'const TSequence *') -> 'TSequence *': +def ttextseq_from_base_period(txt: str, p: 'const Span *') -> 'TSequence *': txt_converted = cstring2text(txt) - seq_converted = _ffi.cast('const TSequence *', seq) - result = _lib.ttextseq_from_base(txt_converted, seq_converted) + p_converted = _ffi.cast('const Span *', p) + result = _lib.ttextseq_from_base_period(txt_converted, p_converted) return result if result != _ffi.NULL else None -def ttextseq_from_base_time(txt: str, p: 'const Span *') -> 'TSequence *': +def ttextseq_from_base_timestampset(txt: str, ts: 'const Set *') -> 'TSequence *': txt_converted = cstring2text(txt) - p_converted = _ffi.cast('const Span *', p) - result = _lib.ttextseq_from_base_time(txt_converted, p_converted) + ts_converted = _ffi.cast('const Set *', ts) + result = _lib.ttextseq_from_base_timestampset(txt_converted, ts_converted) return result if result != _ffi.NULL else None -def ttextseqset_from_base(txt: str, ss: 'const TSequenceSet *') -> 'TSequenceSet *': +def ttextseqset_from_base_periodset(txt: str, ps: 'const SpanSet *') -> 'TSequenceSet *': txt_converted = cstring2text(txt) - ss_converted = _ffi.cast('const TSequenceSet *', ss) - result = _lib.ttextseqset_from_base(txt_converted, ss_converted) + ps_converted = _ffi.cast('const SpanSet *', ps) + result = _lib.ttextseqset_from_base_periodset(txt_converted, ps_converted) return result if result != _ffi.NULL else None -def ttextseqset_from_base_time(txt: str, ps: 'const SpanSet *') -> 'TSequenceSet *': - txt_converted = cstring2text(txt) - ps_converted = _ffi.cast('const SpanSet *', ps) - result = _lib.ttextseqset_from_base_time(txt_converted, ps_converted) +def temporal_to_period(temp: 'const Temporal *') -> 'Span *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.temporal_to_period(temp_converted) return result if result != _ffi.NULL else None @@ -3824,12 +3901,6 @@ def tnumber_to_span(temp: 'const Temporal *') -> 'Span *': return result if result != _ffi.NULL else None -def temporal_to_period(temp: 'const Temporal *') -> 'Span *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.temporal_to_period(temp_converted) - return result if result != _ffi.NULL else None - - def tbool_end_value(temp: 'const Temporal *') -> 'bool': temp_converted = _ffi.cast('const Temporal *', temp) result = _lib.tbool_end_value(temp_converted) @@ -3892,9 +3963,9 @@ def temporal_instants(temp: 'const Temporal *') -> "Tuple['const TInstant **', ' return result if result != _ffi.NULL else None, count[0] -def temporal_interpolation(temp: 'const Temporal *') -> str: +def temporal_interp(temp: 'const Temporal *') -> str: temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.temporal_interpolation(temp_converted) + result = _lib.temporal_interp(temp_converted) result = _ffi.string(result).decode('utf-8') return result if result != _ffi.NULL else None @@ -3967,6 +4038,13 @@ def temporal_start_timestamp(temp: 'const Temporal *') -> 'TimestampTz': return result if result != _ffi.NULL else None +def temporal_stops(temp: 'const Temporal *', maxdist: float, minduration: 'const Interval *') -> 'TSequenceSet *': + temp_converted = _ffi.cast('const Temporal *', temp) + minduration_converted = _ffi.cast('const Interval *', minduration) + result = _lib.temporal_stops(temp_converted, maxdist, minduration_converted) + return result if result != _ffi.NULL else None + + def temporal_subtype(temp: 'const Temporal *') -> str: temp_converted = _ffi.cast('const Temporal *', temp) result = _lib.temporal_subtype(temp_converted) @@ -3986,7 +4064,7 @@ def temporal_timestamp_n(temp: 'const Temporal *', n: int) -> int: result = _lib.temporal_timestamp_n(temp_converted, n, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def temporal_timestamps(temp: 'const Temporal *') -> "Tuple['TimestampTz *', 'int']": @@ -3996,6 +4074,13 @@ def temporal_timestamps(temp: 'const Temporal *') -> "Tuple['TimestampTz *', 'in return result if result != _ffi.NULL else None, count[0] +def temporal_values(temp: 'const Temporal *') -> "Tuple['Datum *', 'int']": + temp_converted = _ffi.cast('const Temporal *', temp) + count = _ffi.new('int *') + result = _lib.temporal_values(temp_converted, count) + return result if result != _ffi.NULL else None, count[0] + + def tfloat_end_value(temp: 'const Temporal *') -> 'double': temp_converted = _ffi.cast('const Temporal *', temp) result = _lib.tfloat_end_value(temp_converted) @@ -4058,9 +4143,9 @@ def tint_values(temp: 'const Temporal *') -> "Tuple['int *', 'int']": return result if result != _ffi.NULL else None, count[0] -def tnumber_values(temp: 'const Temporal *') -> 'SpanSet *': +def tnumber_valuespans(temp: 'const Temporal *') -> 'SpanSet *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tnumber_values(temp_converted) + result = _lib.tnumber_valuespans(temp_converted) return result if result != _ffi.NULL else None @@ -4118,38 +4203,17 @@ def ttext_values(temp: 'const Temporal *') -> "Tuple['text **', 'int']": return result if result != _ffi.NULL else None, count[0] -def temporal_append_tinstant(temp: 'Temporal *', inst: 'const TInstant *', maxdist: float, maxt: 'Interval *', expand: bool) -> 'Temporal *': - temp_converted = _ffi.cast('Temporal *', temp) - inst_converted = _ffi.cast('const TInstant *', inst) - maxt_converted = _ffi.cast('Interval *', maxt) - result = _lib.temporal_append_tinstant(temp_converted, inst_converted, maxdist, maxt_converted, expand) +def temporal_set_interp(temp: 'const Temporal *', interp: 'interpType') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + interp_converted = _ffi.cast('interpType', interp) + result = _lib.temporal_set_interp(temp_converted, interp_converted) return result if result != _ffi.NULL else None -def temporal_append_tsequence(temp: 'Temporal *', seq: 'const TSequence *', expand: bool) -> 'Temporal *': - temp_converted = _ffi.cast('Temporal *', temp) - seq_converted = _ffi.cast('const TSequence *', seq) - result = _lib.temporal_append_tsequence(temp_converted, seq_converted, expand) - return result if result != _ffi.NULL else None - - -def temporal_merge(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_merge(temp1_converted, temp2_converted) - return result if result != _ffi.NULL else None - - -def temporal_merge_array(temparr: 'Temporal **', count: int) -> 'Temporal *': - temparr_converted = [_ffi.cast('Temporal *', x) for x in temparr] - result = _lib.temporal_merge_array(temparr_converted, count) - return result if result != _ffi.NULL else None - - -def temporal_shift(temp: 'const Temporal *', shift: 'const Interval *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - shift_converted = _ffi.cast('const Interval *', shift) - result = _lib.temporal_shift(temp_converted, shift_converted) +def temporal_shift(temp: 'const Temporal *', shift: 'const Interval *') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + shift_converted = _ffi.cast('const Interval *', shift) + result = _lib.temporal_shift(temp_converted, shift_converted) return result if result != _ffi.NULL else None @@ -4161,27 +4225,15 @@ def temporal_shift_tscale(temp: 'const Temporal *', shift: "Optional['const Inte return result if result != _ffi.NULL else None -def temporal_step_to_linear(temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.temporal_step_to_linear(temp_converted) - return result if result != _ffi.NULL else None - - def temporal_to_tinstant(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) result = _lib.temporal_to_tinstant(temp_converted) return result if result != _ffi.NULL else None -def temporal_to_tdiscseq(temp: 'const Temporal *') -> 'Temporal *': +def temporal_to_tsequence(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.temporal_to_tdiscseq(temp_converted) - return result if result != _ffi.NULL else None - - -def temporal_to_tcontseq(temp: 'const Temporal *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.temporal_to_tcontseq(temp_converted) + result = _lib.temporal_to_tsequence(temp_converted) return result if result != _ffi.NULL else None @@ -4191,13 +4243,6 @@ def temporal_to_tsequenceset(temp: 'const Temporal *') -> 'Temporal *': return result if result != _ffi.NULL else None -def temporal_tscale(temp: 'const Temporal *', duration: 'const Interval *') -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - duration_converted = _ffi.cast('const Interval *', duration) - result = _lib.temporal_tscale(temp_converted, duration_converted) - return result if result != _ffi.NULL else None - - def temporal_tprecision(temp: 'const Temporal *', duration: 'const Interval *', origin: int) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) duration_converted = _ffi.cast('const Interval *', duration) @@ -4214,6 +4259,13 @@ def temporal_tsample(temp: 'const Temporal *', duration: 'const Interval *', ori return result if result != _ffi.NULL else None +def temporal_tscale(temp: 'const Temporal *', duration: 'const Interval *') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + duration_converted = _ffi.cast('const Interval *', duration) + result = _lib.temporal_tscale(temp_converted, duration_converted) + return result if result != _ffi.NULL else None + + def tbool_at_value(temp: 'const Temporal *', b: bool) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) result = _lib.tbool_at_value(temp_converted, b) @@ -4233,7 +4285,7 @@ def tbool_value_at_timestamp(temp: 'const Temporal *', t: int, strict: bool) -> result = _lib.tbool_value_at_timestamp(temp_converted, t_converted, strict, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def temporal_at_max(temp: 'const Temporal *') -> 'Temporal *': @@ -4276,6 +4328,13 @@ def temporal_at_timestampset(temp: 'const Temporal *', ts: 'const Set *') -> 'Te return result if result != _ffi.NULL else None +def temporal_at_values(temp: 'const Temporal *', set: 'const Set *') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + set_converted = _ffi.cast('const Set *', set) + result = _lib.temporal_at_values(temp_converted, set_converted) + return result if result != _ffi.NULL else None + + def temporal_minus_max(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) result = _lib.temporal_minus_max(temp_converted) @@ -4316,6 +4375,13 @@ def temporal_minus_timestampset(temp: 'const Temporal *', ts: 'const Set *') -> return result if result != _ffi.NULL else None +def temporal_minus_values(temp: 'const Temporal *', set: 'const Set *') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + set_converted = _ffi.cast('const Set *', set) + result = _lib.temporal_minus_values(temp_converted, set_converted) + return result if result != _ffi.NULL else None + + def tfloat_at_value(temp: 'const Temporal *', d: float) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) result = _lib.tfloat_at_value(temp_converted, d) @@ -4335,7 +4401,7 @@ def tfloat_value_at_timestamp(temp: 'const Temporal *', t: int, strict: bool) -> result = _lib.tfloat_value_at_timestamp(temp_converted, t_converted, strict, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def tint_at_value(temp: 'const Temporal *', i: int) -> 'Temporal *': @@ -4357,7 +4423,7 @@ def tint_value_at_timestamp(temp: 'const Temporal *', t: int, strict: bool) -> ' result = _lib.tint_value_at_timestamp(temp_converted, t_converted, strict, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def tnumber_at_span(temp: 'const Temporal *', span: 'const Span *') -> 'Temporal *': @@ -4402,17 +4468,19 @@ def tnumber_minus_tbox(temp: 'const Temporal *', box: 'const TBox *') -> 'Tempor return result if result != _ffi.NULL else None -def tpoint_at_geometry(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'Temporal *': +def tpoint_at_geom_time(temp: 'const Temporal *', gs: 'const GSERIALIZED *', zspan: 'const Span *', period: 'const Span *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) gs_converted = _ffi.cast('const GSERIALIZED *', gs) - result = _lib.tpoint_at_geometry(temp_converted, gs_converted) + zspan_converted = _ffi.cast('const Span *', zspan) + period_converted = _ffi.cast('const Span *', period) + result = _lib.tpoint_at_geom_time(temp_converted, gs_converted, zspan_converted, period_converted) return result if result != _ffi.NULL else None -def tpoint_at_stbox(temp: 'const Temporal *', box: 'const STBox *') -> 'Temporal *': +def tpoint_at_stbox(temp: 'const Temporal *', box: 'const STBox *', border_inc: bool) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) box_converted = _ffi.cast('const STBox *', box) - result = _lib.tpoint_at_stbox(temp_converted, box_converted) + result = _lib.tpoint_at_stbox(temp_converted, box_converted, border_inc) return result if result != _ffi.NULL else None @@ -4423,17 +4491,19 @@ def tpoint_at_value(temp: 'const Temporal *', gs: 'GSERIALIZED *') -> 'Temporal return result if result != _ffi.NULL else None -def tpoint_minus_geometry(temp: 'const Temporal *', gs: 'const GSERIALIZED *') -> 'Temporal *': +def tpoint_minus_geom_time(temp: 'const Temporal *', gs: 'const GSERIALIZED *', zspan: 'const Span *', period: 'const Span *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) gs_converted = _ffi.cast('const GSERIALIZED *', gs) - result = _lib.tpoint_minus_geometry(temp_converted, gs_converted) + zspan_converted = _ffi.cast('const Span *', zspan) + period_converted = _ffi.cast('const Span *', period) + result = _lib.tpoint_minus_geom_time(temp_converted, gs_converted, zspan_converted, period_converted) return result if result != _ffi.NULL else None -def tpoint_minus_stbox(temp: 'const Temporal *', box: 'const STBox *') -> 'Temporal *': +def tpoint_minus_stbox(temp: 'const Temporal *', box: 'const STBox *', border_inc: bool) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) box_converted = _ffi.cast('const STBox *', box) - result = _lib.tpoint_minus_stbox(temp_converted, box_converted) + result = _lib.tpoint_minus_stbox(temp_converted, box_converted, border_inc) return result if result != _ffi.NULL else None @@ -4451,14 +4521,7 @@ def tpoint_value_at_timestamp(temp: 'const Temporal *', t: int, strict: bool) -> result = _lib.tpoint_value_at_timestamp(temp_converted, t_converted, strict, out_result) if result: return out_result if out_result != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') - - -def tsequence_at_period(seq: 'const TSequence *', p: 'const Span *') -> 'TSequence *': - seq_converted = _ffi.cast('const TSequence *', seq) - p_converted = _ffi.cast('const Span *', p) - result = _lib.tsequence_at_period(seq_converted, p_converted) - return result if result != _ffi.NULL else None + return None def ttext_at_value(temp: 'const Temporal *', txt: str) -> 'Temporal *': @@ -4482,7 +4545,77 @@ def ttext_value_at_timestamp(temp: 'const Temporal *', t: int, strict: bool) -> result = _lib.ttext_value_at_timestamp(temp_converted, t_converted, strict, out_result) if result: return out_result if out_result != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None + + +def temporal_append_tinstant(temp: 'Temporal *', inst: 'const TInstant *', maxdist: float, maxt: "Optional['Interval *']", expand: bool) -> 'Temporal *': + temp_converted = _ffi.cast('Temporal *', temp) + inst_converted = _ffi.cast('const TInstant *', inst) + maxt_converted = _ffi.cast('Interval *', maxt) if maxt is not None else _ffi.NULL + result = _lib.temporal_append_tinstant(temp_converted, inst_converted, maxdist, maxt_converted, expand) + return result if result != _ffi.NULL else None + + +def temporal_append_tsequence(temp: 'Temporal *', seq: 'const TSequence *', expand: bool) -> 'Temporal *': + temp_converted = _ffi.cast('Temporal *', temp) + seq_converted = _ffi.cast('const TSequence *', seq) + result = _lib.temporal_append_tsequence(temp_converted, seq_converted, expand) + return result if result != _ffi.NULL else None + + +def temporal_delete_period(temp: 'const Temporal *', p: 'const Span *', connect: bool) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + p_converted = _ffi.cast('const Span *', p) + result = _lib.temporal_delete_period(temp_converted, p_converted, connect) + return result if result != _ffi.NULL else None + + +def temporal_delete_periodset(temp: 'const Temporal *', ps: 'const SpanSet *', connect: bool) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + ps_converted = _ffi.cast('const SpanSet *', ps) + result = _lib.temporal_delete_periodset(temp_converted, ps_converted, connect) + return result if result != _ffi.NULL else None + + +def temporal_delete_timestamp(temp: 'const Temporal *', t: int, connect: bool) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + t_converted = _ffi.cast('TimestampTz', t) + result = _lib.temporal_delete_timestamp(temp_converted, t_converted, connect) + return result if result != _ffi.NULL else None + + +def temporal_delete_timestampset(temp: 'const Temporal *', ts: 'const Set *', connect: bool) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + ts_converted = _ffi.cast('const Set *', ts) + result = _lib.temporal_delete_timestampset(temp_converted, ts_converted, connect) + return result if result != _ffi.NULL else None + + +def temporal_insert(temp1: 'const Temporal *', temp2: 'const Temporal *', connect: bool) -> 'Temporal *': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.temporal_insert(temp1_converted, temp2_converted, connect) + return result if result != _ffi.NULL else None + + +def temporal_merge(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Temporal *': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.temporal_merge(temp1_converted, temp2_converted) + return result if result != _ffi.NULL else None + + +def temporal_merge_array(temparr: 'Temporal **', count: int) -> 'Temporal *': + temparr_converted = [_ffi.cast('Temporal *', x) for x in temparr] + result = _lib.temporal_merge_array(temparr_converted, count) + return result if result != _ffi.NULL else None + + +def temporal_update(temp1: 'const Temporal *', temp2: 'const Temporal *', connect: bool) -> 'Temporal *': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.temporal_update(temp1_converted, temp2_converted, connect) + return result if result != _ffi.NULL else None def tand_bool_tbool(b: bool, temp: 'const Temporal *') -> 'Temporal *': @@ -4504,6 +4637,12 @@ def tand_tbool_tbool(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'T return result if result != _ffi.NULL else None +def tbool_when_true(temp: 'const Temporal *') -> 'SpanSet *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tbool_when_true(temp_converted) + return result if result != _ffi.NULL else None + + def tnot_tbool(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) result = _lib.tnot_tbool(temp_converted) @@ -4529,33 +4668,27 @@ def tor_tbool_tbool(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'Te return result if result != _ffi.NULL else None -def tbool_when_true(temp: 'const Temporal *') -> 'SpanSet *': - temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tbool_when_true(temp_converted) - return result if result != _ffi.NULL else None - - -def add_float_tfloat(d: float, tnumber: 'const Temporal *') -> 'Temporal *': +def add_float_tnumber(d: float, tnumber: 'const Temporal *') -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.add_float_tfloat(d, tnumber_converted) + result = _lib.add_float_tnumber(d, tnumber_converted) return result if result != _ffi.NULL else None -def add_int_tint(i: int, tnumber: 'const Temporal *') -> 'Temporal *': +def add_int_tnumber(i: int, tnumber: 'const Temporal *') -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.add_int_tint(i, tnumber_converted) + result = _lib.add_int_tnumber(i, tnumber_converted) return result if result != _ffi.NULL else None -def add_tfloat_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': +def add_tnumber_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.add_tfloat_float(tnumber_converted, d) + result = _lib.add_tnumber_float(tnumber_converted, d) return result if result != _ffi.NULL else None -def add_tint_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': +def add_tnumber_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.add_tint_int(tnumber_converted, i) + result = _lib.add_tnumber_int(tnumber_converted, i) return result if result != _ffi.NULL else None @@ -4566,32 +4699,27 @@ def add_tnumber_tnumber(tnumber1: 'const Temporal *', tnumber2: 'const Temporal return result if result != _ffi.NULL else None -def float_degrees(value: float, normalize: bool) -> 'double': - result = _lib.float_degrees(value, normalize) - return result if result != _ffi.NULL else None - - -def div_float_tfloat(d: float, tnumber: 'const Temporal *') -> 'Temporal *': +def div_float_tnumber(d: float, tnumber: 'const Temporal *') -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.div_float_tfloat(d, tnumber_converted) + result = _lib.div_float_tnumber(d, tnumber_converted) return result if result != _ffi.NULL else None -def div_int_tint(i: int, tnumber: 'const Temporal *') -> 'Temporal *': +def div_int_tnumber(i: int, tnumber: 'const Temporal *') -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.div_int_tint(i, tnumber_converted) + result = _lib.div_int_tnumber(i, tnumber_converted) return result if result != _ffi.NULL else None -def div_tfloat_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': +def div_tnumber_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.div_tfloat_float(tnumber_converted, d) + result = _lib.div_tnumber_float(tnumber_converted, d) return result if result != _ffi.NULL else None -def div_tint_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': +def div_tnumber_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.div_tint_int(tnumber_converted, i) + result = _lib.div_tnumber_int(tnumber_converted, i) return result if result != _ffi.NULL else None @@ -4602,27 +4730,32 @@ def div_tnumber_tnumber(tnumber1: 'const Temporal *', tnumber2: 'const Temporal return result if result != _ffi.NULL else None -def mult_float_tfloat(d: float, tnumber: 'const Temporal *') -> 'Temporal *': +def float_degrees(value: float, normalize: bool) -> 'double': + result = _lib.float_degrees(value, normalize) + return result if result != _ffi.NULL else None + + +def mult_float_tnumber(d: float, tnumber: 'const Temporal *') -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.mult_float_tfloat(d, tnumber_converted) + result = _lib.mult_float_tnumber(d, tnumber_converted) return result if result != _ffi.NULL else None -def mult_int_tint(i: int, tnumber: 'const Temporal *') -> 'Temporal *': +def mult_int_tnumber(i: int, tnumber: 'const Temporal *') -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.mult_int_tint(i, tnumber_converted) + result = _lib.mult_int_tnumber(i, tnumber_converted) return result if result != _ffi.NULL else None -def mult_tfloat_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': +def mult_tnumber_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.mult_tfloat_float(tnumber_converted, d) + result = _lib.mult_tnumber_float(tnumber_converted, d) return result if result != _ffi.NULL else None -def mult_tint_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': +def mult_tnumber_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.mult_tint_int(tnumber_converted, i) + result = _lib.mult_tnumber_int(tnumber_converted, i) return result if result != _ffi.NULL else None @@ -4633,27 +4766,27 @@ def mult_tnumber_tnumber(tnumber1: 'const Temporal *', tnumber2: 'const Temporal return result if result != _ffi.NULL else None -def sub_float_tfloat(d: float, tnumber: 'const Temporal *') -> 'Temporal *': +def sub_float_tnumber(d: float, tnumber: 'const Temporal *') -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.sub_float_tfloat(d, tnumber_converted) + result = _lib.sub_float_tnumber(d, tnumber_converted) return result if result != _ffi.NULL else None -def sub_int_tint(i: int, tnumber: 'const Temporal *') -> 'Temporal *': +def sub_int_tnumber(i: int, tnumber: 'const Temporal *') -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.sub_int_tint(i, tnumber_converted) + result = _lib.sub_int_tnumber(i, tnumber_converted) return result if result != _ffi.NULL else None -def sub_tfloat_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': +def sub_tnumber_float(tnumber: 'const Temporal *', d: float) -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.sub_tfloat_float(tnumber_converted, d) + result = _lib.sub_tnumber_float(tnumber_converted, d) return result if result != _ffi.NULL else None -def sub_tint_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': +def sub_tnumber_int(tnumber: 'const Temporal *', i: int) -> 'Temporal *': tnumber_converted = _ffi.cast('const Temporal *', tnumber) - result = _lib.sub_tint_int(tnumber_converted, i) + result = _lib.sub_tnumber_int(tnumber_converted, i) return result if result != _ffi.NULL else None @@ -4664,15 +4797,15 @@ def sub_tnumber_tnumber(tnumber1: 'const Temporal *', tnumber2: 'const Temporal return result if result != _ffi.NULL else None -def tfloat_degrees(temp: 'const Temporal *', normalize: bool) -> 'Temporal *': +def tfloat_round(temp: 'const Temporal *', maxdd: int) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tfloat_degrees(temp_converted, normalize) + result = _lib.tfloat_round(temp_converted, maxdd) return result if result != _ffi.NULL else None -def tfloat_radians(temp: 'const Temporal *') -> 'Temporal *': +def tfloat_degrees(temp: 'const Temporal *', normalize: bool) -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tfloat_radians(temp_converted) + result = _lib.tfloat_degrees(temp_converted, normalize) return result if result != _ffi.NULL else None @@ -4682,15 +4815,15 @@ def tfloat_derivative(temp: 'const Temporal *') -> 'Temporal *': return result if result != _ffi.NULL else None -def tnumber_abs(temp: 'const Temporal *') -> 'Temporal *': +def tfloat_radians(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tnumber_abs(temp_converted) + result = _lib.tfloat_radians(temp_converted) return result if result != _ffi.NULL else None -def tnumber_delta_value(temp: 'const Temporal *') -> 'Temporal *': +def tnumber_abs(temp: 'const Temporal *') -> 'Temporal *': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tnumber_delta_value(temp_converted) + result = _lib.tnumber_abs(temp_converted) return result if result != _ffi.NULL else None @@ -4700,6 +4833,12 @@ def tnumber_angular_difference(temp: 'const Temporal *') -> 'Temporal *': return result if result != _ffi.NULL else None +def tnumber_delta_value(temp: 'const Temporal *') -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tnumber_delta_value(temp_converted) + return result if result != _ffi.NULL else None + + def textcat_text_ttext(txt: str, temp: 'const Temporal *') -> 'Temporal *': txt_converted = cstring2text(txt) temp_converted = _ffi.cast('const Temporal *', temp) @@ -4862,7 +5001,7 @@ def shortestline_tpoint_geo(temp: 'const Temporal *', gs: 'const GSERIALIZED *') result = _lib.shortestline_tpoint_geo(temp_converted, gs_converted, out_result) if result: return out_result if out_result != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def shortestline_tpoint_tpoint(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'GSERIALIZED **': @@ -4872,7 +5011,7 @@ def shortestline_tpoint_tpoint(temp1: 'const Temporal *', temp2: 'const Temporal result = _lib.shortestline_tpoint_tpoint(temp1_converted, temp2_converted, out_result) if result: return out_result if out_result != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def tbool_always_eq(temp: 'const Temporal *', b: bool) -> 'bool': @@ -4987,6 +5126,20 @@ def tint_ever_lt(temp: 'const Temporal *', i: int) -> 'bool': return result if result != _ffi.NULL else None +def tpoint_always_eq(temp: 'const Temporal *', value: 'Datum') -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + value_converted = _ffi.cast('Datum', value) + result = _lib.tpoint_always_eq(temp_converted, value_converted) + return result if result != _ffi.NULL else None + + +def tpoint_ever_eq(temp: 'const Temporal *', value: 'Datum') -> 'bool': + temp_converted = _ffi.cast('const Temporal *', temp) + value_converted = _ffi.cast('Datum', value) + result = _lib.tpoint_ever_eq(temp_converted, value_converted) + return result if result != _ffi.NULL else None + + def ttext_always_eq(temp: 'const Temporal *', txt: str) -> 'bool': temp_converted = _ffi.cast('const Temporal *', temp) txt_converted = cstring2text(txt) @@ -5463,7 +5616,7 @@ def bearing_point_point(geo1: 'const GSERIALIZED *', geo2: 'const GSERIALIZED *' result = _lib.bearing_point_point(geo1_converted, geo2_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def bearing_tpoint_point(temp: 'const Temporal *', gs: 'const GSERIALIZED *', invert: bool) -> 'Temporal *': @@ -5510,7 +5663,7 @@ def tpoint_direction(temp: 'const Temporal *') -> 'double': result = _lib.tpoint_direction(temp_converted, out_result) if result: return out_result[0] if out_result[0] != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None def tpoint_get_coord(temp: 'const Temporal *', coord: int) -> 'Temporal *': @@ -5574,6 +5727,12 @@ def tpoint_expand_space(temp: 'const Temporal *', d: float) -> 'STBox *': return result if result != _ffi.NULL else None +def tpoint_round(temp: 'const Temporal *', maxdd: int) -> 'Temporal *': + temp_converted = _ffi.cast('const Temporal *', temp) + result = _lib.tpoint_round(temp_converted, maxdd) + return result if result != _ffi.NULL else None + + def tpoint_make_simple(temp: 'const Temporal *') -> "Tuple['Temporal **', 'int']": temp_converted = _ffi.cast('const Temporal *', temp) count = _ffi.new('int *') @@ -5686,55 +5845,6 @@ def ttouches_tpoint_geo(temp: 'const Temporal *', gs: 'const GSERIALIZED *', res return result if result != _ffi.NULL else None -def temporal_insert(temp1: 'const Temporal *', temp2: 'const Temporal *', connect: bool) -> 'Temporal *': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_insert(temp1_converted, temp2_converted, connect) - return result if result != _ffi.NULL else None - - -def temporal_update(temp1: 'const Temporal *', temp2: 'const Temporal *', connect: bool) -> 'Temporal *': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_update(temp1_converted, temp2_converted, connect) - return result if result != _ffi.NULL else None - - -def temporal_delete_timestamp(temp: 'const Temporal *', t: int, connect: bool) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - t_converted = _ffi.cast('TimestampTz', t) - result = _lib.temporal_delete_timestamp(temp_converted, t_converted, connect) - return result if result != _ffi.NULL else None - - -def temporal_delete_timestampset(temp: 'const Temporal *', ts: 'const Set *', connect: bool) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - ts_converted = _ffi.cast('const Set *', ts) - result = _lib.temporal_delete_timestampset(temp_converted, ts_converted, connect) - return result if result != _ffi.NULL else None - - -def temporal_delete_period(temp: 'const Temporal *', p: 'const Span *', connect: bool) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - p_converted = _ffi.cast('const Span *', p) - result = _lib.temporal_delete_period(temp_converted, p_converted, connect) - return result if result != _ffi.NULL else None - - -def temporal_delete_periodset(temp: 'const Temporal *', ps: 'const SpanSet *', connect: bool) -> 'Temporal *': - temp_converted = _ffi.cast('const Temporal *', temp) - ps_converted = _ffi.cast('const SpanSet *', ps) - result = _lib.temporal_delete_periodset(temp_converted, ps_converted, connect) - return result if result != _ffi.NULL else None - - -def temporal_stops(temp: 'const Temporal *', mindist: float, minduration: 'const Interval *') -> 'TSequenceSet *': - temp_converted = _ffi.cast('const Temporal *', temp) - minduration_converted = _ffi.cast('const Interval *', minduration) - result = _lib.temporal_stops(temp_converted, mindist, minduration_converted) - return result if result != _ffi.NULL else None - - def tbool_tand_transfn(state: "Optional['SkipList *']", temp: 'const Temporal *') -> 'SkipList *': state_converted = _ffi.cast('SkipList *', state) if state is not None else _ffi.NULL temp_converted = _ffi.cast('const Temporal *', temp) @@ -5811,16 +5921,16 @@ def tint_tsum_transfn(state: "Optional['SkipList *']", temp: 'const Temporal *') return result if result != _ffi.NULL else None -def tnumber_integral(temp: 'const Temporal *') -> 'double': +def tnumber_extent_transfn(box: "Optional['TBox *']", temp: 'const Temporal *') -> 'TBox *': + box_converted = _ffi.cast('TBox *', box) if box is not None else _ffi.NULL temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tnumber_integral(temp_converted) + result = _lib.tnumber_extent_transfn(box_converted, temp_converted) return result if result != _ffi.NULL else None -def tnumber_extent_transfn(box: "Optional['TBox *']", temp: 'const Temporal *') -> 'TBox *': - box_converted = _ffi.cast('TBox *', box) if box is not None else _ffi.NULL +def tnumber_integral(temp: 'const Temporal *') -> 'double': temp_converted = _ffi.cast('const Temporal *', temp) - result = _lib.tnumber_extent_transfn(box_converted, temp_converted) + result = _lib.tnumber_integral(temp_converted) return result if result != _ffi.NULL else None @@ -5850,6 +5960,19 @@ def tpoint_extent_transfn(box: "Optional['STBox *']", temp: 'const Temporal *') return result if result != _ffi.NULL else None +def tpoint_tcentroid_finalfn(state: 'SkipList *') -> 'Temporal *': + state_converted = _ffi.cast('SkipList *', state) + result = _lib.tpoint_tcentroid_finalfn(state_converted) + return result if result != _ffi.NULL else None + + +def tpoint_tcentroid_transfn(state: 'SkipList *', temp: 'Temporal *') -> 'SkipList *': + state_converted = _ffi.cast('SkipList *', state) + temp_converted = _ffi.cast('Temporal *', temp) + result = _lib.tpoint_tcentroid_transfn(state_converted, temp_converted) + return result if result != _ffi.NULL else None + + def tpoint_twcentroid(temp: 'const Temporal *') -> 'GSERIALIZED *': temp_converted = _ffi.cast('const Temporal *', temp) result = _lib.tpoint_twcentroid(temp_converted) @@ -5870,35 +5993,27 @@ def ttext_tmin_transfn(state: "Optional['SkipList *']", temp: 'const Temporal *' return result if result != _ffi.NULL else None -def int_bucket(value: int, size: int, origin: int) -> 'int': - result = _lib.int_bucket(value, size, origin) - return result if result != _ffi.NULL else None - - def float_bucket(value: float, size: float, origin: float) -> 'double': result = _lib.float_bucket(value, size, origin) return result if result != _ffi.NULL else None -def timestamptz_bucket(timestamp: int, duration: 'const Interval *', origin: int) -> 'TimestampTz': - timestamp_converted = _ffi.cast('TimestampTz', timestamp) - duration_converted = _ffi.cast('const Interval *', duration) - origin_converted = _ffi.cast('TimestampTz', origin) - result = _lib.timestamptz_bucket(timestamp_converted, duration_converted, origin_converted) +def floatspan_bucket_list(bounds: 'const Span *', size: float, origin: float, newcount: 'int *') -> 'Span *': + bounds_converted = _ffi.cast('const Span *', bounds) + newcount_converted = _ffi.cast('int *', newcount) + result = _lib.floatspan_bucket_list(bounds_converted, size, origin, newcount_converted) return result if result != _ffi.NULL else None -def intspan_bucket_list(bounds: 'const Span *', size: int, origin: int, newcount: 'int *') -> 'Span *': - bounds_converted = _ffi.cast('const Span *', bounds) - newcount_converted = _ffi.cast('int *', newcount) - result = _lib.intspan_bucket_list(bounds_converted, size, origin, newcount_converted) +def int_bucket(value: int, size: int, origin: int) -> 'int': + result = _lib.int_bucket(value, size, origin) return result if result != _ffi.NULL else None -def floatspan_bucket_list(bounds: 'const Span *', size: float, origin: float, newcount: 'int *') -> 'Span *': +def intspan_bucket_list(bounds: 'const Span *', size: int, origin: int, newcount: 'int *') -> 'Span *': bounds_converted = _ffi.cast('const Span *', bounds) newcount_converted = _ffi.cast('int *', newcount) - result = _lib.floatspan_bucket_list(bounds_converted, size, origin, newcount_converted) + result = _lib.intspan_bucket_list(bounds_converted, size, origin, newcount_converted) return result if result != _ffi.NULL else None @@ -5911,6 +6026,16 @@ def period_bucket_list(bounds: 'const Span *', duration: 'const Interval *', ori return result if result != _ffi.NULL else None +def stbox_tile_list(bounds: 'const STBox *', xsize: float, ysize: float, zsize: float, duration: "Optional['const Interval *']", sorigin: 'GSERIALIZED *', torigin: int) -> "Tuple['STBox *', 'int *']": + bounds_converted = _ffi.cast('const STBox *', bounds) + duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL + sorigin_converted = _ffi.cast('GSERIALIZED *', sorigin) + torigin_converted = _ffi.cast('TimestampTz', torigin) + cellcount = _ffi.new('int **') + result = _lib.stbox_tile_list(bounds_converted, xsize, ysize, zsize, duration_converted, sorigin_converted, torigin_converted, cellcount) + return result if result != _ffi.NULL else None, cellcount[0] + + def tbox_tile_list(bounds: 'const TBox *', xsize: float, duration: 'const Interval *', xorigin: 'Optional[float]', torigin: "Optional[int]") -> "Tuple['TBox *', 'int', 'int']": bounds_converted = _ffi.cast('const TBox *', bounds) duration_converted = _ffi.cast('const Interval *', duration) @@ -5922,10 +6047,12 @@ def tbox_tile_list(bounds: 'const TBox *', xsize: float, duration: 'const Interv return result if result != _ffi.NULL else None, rows[0], columns[0] -def tint_value_split(temp: 'Temporal *', size: int, origin: int) -> "Tuple['Temporal **', 'int']": +def temporal_time_split(temp: 'Temporal *', duration: 'Interval *', torigin: int) -> "Tuple['Temporal **', 'int']": temp_converted = _ffi.cast('Temporal *', temp) + duration_converted = _ffi.cast('Interval *', duration) + torigin_converted = _ffi.cast('TimestampTz', torigin) newcount = _ffi.new('int *') - result = _lib.tint_value_split(temp_converted, size, origin, newcount) + result = _lib.temporal_time_split(temp_converted, duration_converted, torigin_converted, newcount) return result if result != _ffi.NULL else None, newcount[0] @@ -5936,50 +6063,39 @@ def tfloat_value_split(temp: 'Temporal *', size: float, origin: float) -> "Tuple return result if result != _ffi.NULL else None, newcount[0] -def temporal_time_split(temp: 'Temporal *', duration: 'Interval *', torigin: int) -> "Tuple['Temporal **', 'int']": +def tfloat_value_time_split(temp: 'Temporal *', size: float, vorigin: float, duration: 'Interval *', torigin: int) -> "Tuple['Temporal **', 'int']": temp_converted = _ffi.cast('Temporal *', temp) duration_converted = _ffi.cast('Interval *', duration) torigin_converted = _ffi.cast('TimestampTz', torigin) newcount = _ffi.new('int *') - result = _lib.temporal_time_split(temp_converted, duration_converted, torigin_converted, newcount) + result = _lib.tfloat_value_time_split(temp_converted, size, vorigin, duration_converted, torigin_converted, newcount) return result if result != _ffi.NULL else None, newcount[0] -def tint_value_time_split(temp: 'Temporal *', size: int, vorigin: int, duration: 'Interval *', torigin: int) -> "Tuple['Temporal **', 'int']": +def timestamptz_bucket(timestamp: int, duration: 'const Interval *', origin: int) -> 'TimestampTz': + timestamp_converted = _ffi.cast('TimestampTz', timestamp) + duration_converted = _ffi.cast('const Interval *', duration) + origin_converted = _ffi.cast('TimestampTz', origin) + result = _lib.timestamptz_bucket(timestamp_converted, duration_converted, origin_converted) + return result if result != _ffi.NULL else None + + +def tint_value_split(temp: 'Temporal *', size: int, origin: int) -> "Tuple['Temporal **', 'int']": temp_converted = _ffi.cast('Temporal *', temp) - duration_converted = _ffi.cast('Interval *', duration) - torigin_converted = _ffi.cast('TimestampTz', torigin) newcount = _ffi.new('int *') - result = _lib.tint_value_time_split(temp_converted, size, vorigin, duration_converted, torigin_converted, newcount) + result = _lib.tint_value_split(temp_converted, size, origin, newcount) return result if result != _ffi.NULL else None, newcount[0] -def tfloat_value_time_split(temp: 'Temporal *', size: float, vorigin: float, duration: 'Interval *', torigin: int) -> "Tuple['Temporal **', 'int']": +def tint_value_time_split(temp: 'Temporal *', size: int, vorigin: int, duration: 'Interval *', torigin: int) -> "Tuple['Temporal **', 'int']": temp_converted = _ffi.cast('Temporal *', temp) duration_converted = _ffi.cast('Interval *', duration) torigin_converted = _ffi.cast('TimestampTz', torigin) newcount = _ffi.new('int *') - result = _lib.tfloat_value_time_split(temp_converted, size, vorigin, duration_converted, torigin_converted, newcount) + result = _lib.tint_value_time_split(temp_converted, size, vorigin, duration_converted, torigin_converted, newcount) return result if result != _ffi.NULL else None, newcount[0] -def stbox_tile_list(bounds: 'STBox *', size: float, duration: "Optional['const Interval *']", sorigin: 'GSERIALIZED *', torigin: int) -> "Tuple['STBox *', 'int *']": - bounds_converted = _ffi.cast('STBox *', bounds) - duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL - sorigin_converted = _ffi.cast('GSERIALIZED *', sorigin) - torigin_converted = _ffi.cast('TimestampTz', torigin) - cellcount = _ffi.new('int **') - result = _lib.stbox_tile_list(bounds_converted, size, duration_converted, sorigin_converted, torigin_converted, cellcount) - return result if result != _ffi.NULL else None, cellcount[0] - - -def temporal_frechet_distance(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'double': - temp1_converted = _ffi.cast('const Temporal *', temp1) - temp2_converted = _ffi.cast('const Temporal *', temp2) - result = _lib.temporal_frechet_distance(temp1_converted, temp2_converted) - return result if result != _ffi.NULL else None - - def temporal_dyntimewarp_distance(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'double': temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) @@ -5987,19 +6103,26 @@ def temporal_dyntimewarp_distance(temp1: 'const Temporal *', temp2: 'const Tempo return result if result != _ffi.NULL else None -def temporal_frechet_path(temp1: 'const Temporal *', temp2: 'const Temporal *') -> "Tuple['Match *', 'int']": +def temporal_dyntimewarp_path(temp1: 'const Temporal *', temp2: 'const Temporal *') -> "Tuple['Match *', 'int']": temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) count = _ffi.new('int *') - result = _lib.temporal_frechet_path(temp1_converted, temp2_converted, count) + result = _lib.temporal_dyntimewarp_path(temp1_converted, temp2_converted, count) return result if result != _ffi.NULL else None, count[0] -def temporal_dyntimewarp_path(temp1: 'const Temporal *', temp2: 'const Temporal *') -> "Tuple['Match *', 'int']": +def temporal_frechet_distance(temp1: 'const Temporal *', temp2: 'const Temporal *') -> 'double': + temp1_converted = _ffi.cast('const Temporal *', temp1) + temp2_converted = _ffi.cast('const Temporal *', temp2) + result = _lib.temporal_frechet_distance(temp1_converted, temp2_converted) + return result if result != _ffi.NULL else None + + +def temporal_frechet_path(temp1: 'const Temporal *', temp2: 'const Temporal *') -> "Tuple['Match *', 'int']": temp1_converted = _ffi.cast('const Temporal *', temp1) temp2_converted = _ffi.cast('const Temporal *', temp2) count = _ffi.new('int *') - result = _lib.temporal_dyntimewarp_path(temp1_converted, temp2_converted, count) + result = _lib.temporal_frechet_path(temp1_converted, temp2_converted, count) return result if result != _ffi.NULL else None, count[0] @@ -6053,13 +6176,13 @@ def tpoint_AsMVTGeom(temp: 'const Temporal *', bounds: 'const STBox *', extent: return result if result != _ffi.NULL else None, count[0] -def tpoint_to_geo_measure(tpoint: 'const Temporal *', measure: 'const Temporal *', segmentize: bool) -> 'GSERIALIZED **': +def tpoint_to_geo_meas(tpoint: 'const Temporal *', measure: 'const Temporal *', segmentize: bool) -> 'GSERIALIZED **': tpoint_converted = _ffi.cast('const Temporal *', tpoint) measure_converted = _ffi.cast('const Temporal *', measure) out_result = _ffi.new('GSERIALIZED **') - result = _lib.tpoint_to_geo_measure(tpoint_converted, measure_converted, segmentize, out_result) + result = _lib.tpoint_to_geo_meas(tpoint_converted, measure_converted, segmentize, out_result) if result: return out_result if out_result != _ffi.NULL else None - raise Exception(f'C call went wrong: {result}') + return None diff --git a/pymeos_cffi/pyproject.toml b/pymeos_cffi/pyproject.toml index b7b25ad2..8a74b572 100644 --- a/pymeos_cffi/pyproject.toml +++ b/pymeos_cffi/pyproject.toml @@ -7,7 +7,7 @@ py-modules = [] [project] name = 'pymeos_cffi' -version = '1.1.0-alpha' +version = '1.1.0-alpha.4' authors = [ { name = 'Victor Divi', email = 'vdiviloper@gmail.com' } ]