From a5a558f3e289e5a4f6409036588932b12cacdd57 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Tue, 19 May 2026 09:24:44 +0200 Subject: [PATCH] feat: object-oriented support for the cbuffer, npoint, pose and rgeometry temporal types Add first-class PyMEOS classes for the four extended temporal type families, mirroring the existing tpoint/ttext pattern and reusing the exact pymeos_cffi backing functions (no reimplementation): - main: TNpoint/TCbuffer/TPose/TRgeometry (+ Inst/Seq/SeqSet) - collections: base value types Npoint/Cbuffer/Pose (+ their *Set) and Nsegment; rgeometry's base is a shapely geometry (reuses GeometrySet) - factory + package exports wired for all four families; tests added Each type covers 100% of its real MEOS-backed surface, verified against the MEOS headers: - TCbuffer exposes the full spatial-relationship surface MEOS provides (ever / always / temporal: contains, covers, disjoint, within-distance, intersects, touches), mirroring TPoint. - MEOS defines no spatial-relationship API for npoint, pose or rgeometry, so their relationship surface is complete at zero by construction (documented, not excluded, not faked). - TRgeometry additionally exposes its full backed surface: segments, delete (timestamptz/tstzset/tstzspan/tstzspanset), before/after a timestamp, to_instant, and the sequence-set accessors (start_sequence/end_sequence/sequence_n/sequences) -- each delegating to the rigid-geometry-specific trgeo_* function. - TNpoint.positions() exposes tnpoint_positions via a new Nsegment wrapper (full nsegment_* surface). Omitted only where MEOS provides no usable backing: trgeometry has no string input parser (built via from_geometry_tpose / hexwkb), and the Datum-bearing trgeo_restrict_value/values helpers are MEOS-internal (consistent with how the ecosystem treats Datum helpers). The factory resolves the four type tags through the compiled binding (`_meos_cffi.lib`, the same source pymeos_cffi builds its enums from), so the build imports cleanly and the types activate even where pymeos_cffi's MeosType enum does not yet wrap the cbuffer/pose/rgeometry tags. TCbuffer's value accessors and value-based constructors (start_value/end_value/value_set/value_at_timestamp, from_base_time, from_mfjson) raise NotImplementedError citing the missing MEOS backing (MEOS exposes no typed tcbuffer_* value accessor / tcbufferinst_make; MF-JSON input is tracked upstream by MobilityDB#1051). This keeps TCbuffer/Inst/Seq/SeqSet concrete and instantiable rather than abstract. --- pymeos/__init__.py | 23 + pymeos/collections/__init__.py | 10 + pymeos/collections/cbuffer/__init__.py | 4 + pymeos/collections/cbuffer/cbuffer.py | 673 +++++++++++++ pymeos/collections/cbuffer/cbufferset.py | 229 +++++ pymeos/collections/npoint/__init__.py | 5 + pymeos/collections/npoint/npoint.py | 421 +++++++++ pymeos/collections/npoint/npointset.py | 139 +++ pymeos/collections/npoint/nsegment.py | 155 +++ pymeos/collections/pose/__init__.py | 4 + pymeos/collections/pose/pose.py | 551 +++++++++++ pymeos/collections/pose/poseset.py | 228 +++++ pymeos/factory.py | 68 ++ pymeos/main/__init__.py | 25 + pymeos/main/tcbuffer.py | 1094 ++++++++++++++++++++++ pymeos/main/tnpoint.py | 700 ++++++++++++++ pymeos/main/tpose.py | 736 +++++++++++++++ pymeos/main/trgeometry.py | 1076 +++++++++++++++++++++ tests/main/tcbuffer_test.py | 176 ++++ tests/main/tnpoint_test.py | 261 ++++++ tests/main/tpose_test.py | 184 ++++ tests/main/trgeometry_test.py | 198 ++++ 22 files changed, 6960 insertions(+) create mode 100644 pymeos/collections/cbuffer/__init__.py create mode 100644 pymeos/collections/cbuffer/cbuffer.py create mode 100644 pymeos/collections/cbuffer/cbufferset.py create mode 100644 pymeos/collections/npoint/__init__.py create mode 100644 pymeos/collections/npoint/npoint.py create mode 100644 pymeos/collections/npoint/npointset.py create mode 100644 pymeos/collections/npoint/nsegment.py create mode 100644 pymeos/collections/pose/__init__.py create mode 100644 pymeos/collections/pose/pose.py create mode 100644 pymeos/collections/pose/poseset.py create mode 100644 pymeos/main/tcbuffer.py create mode 100644 pymeos/main/tnpoint.py create mode 100644 pymeos/main/tpose.py create mode 100644 pymeos/main/trgeometry.py create mode 100644 tests/main/tcbuffer_test.py create mode 100644 tests/main/tnpoint_test.py create mode 100644 tests/main/tpose_test.py create mode 100644 tests/main/trgeometry_test.py diff --git a/pymeos/__init__.py b/pymeos/__init__.py index 7d9abca5..298cac3b 100644 --- a/pymeos/__init__.py +++ b/pymeos/__init__.py @@ -66,6 +66,22 @@ "TGeogPointInst", "TGeogPointSeq", "TGeogPointSeqSet", + "TNpoint", + "TNpointInst", + "TNpointSeq", + "TNpointSeqSet", + "TCbuffer", + "TCbufferInst", + "TCbufferSeq", + "TCbufferSeqSet", + "TPose", + "TPoseInst", + "TPoseSeq", + "TPoseSeqSet", + "TRgeometry", + "TRgeometryInst", + "TRgeometrySeq", + "TRgeometrySeqSet", # temporal "Temporal", "TInstant", @@ -86,6 +102,13 @@ "GeoSet", "GeometrySet", "GeographySet", + "Npoint", + "NpointSet", + "Nsegment", + "Cbuffer", + "CbufferSet", + "Pose", + "PoseSet", # extras "TInterpolation", # aggregators diff --git a/pymeos/collections/__init__.py b/pymeos/collections/__init__.py index 23a0bd61..a413dd68 100644 --- a/pymeos/collections/__init__.py +++ b/pymeos/collections/__init__.py @@ -4,6 +4,9 @@ from .time import * from .text import * from .geo import * +from .npoint import * +from .cbuffer import * +from .pose import * __all__ = [ "Set", @@ -30,4 +33,11 @@ "FloatSet", "FloatSpan", "FloatSpanSet", + "Npoint", + "NpointSet", + "Nsegment", + "Cbuffer", + "CbufferSet", + "Pose", + "PoseSet", ] diff --git a/pymeos/collections/cbuffer/__init__.py b/pymeos/collections/cbuffer/__init__.py new file mode 100644 index 00000000..43b0d6d8 --- /dev/null +++ b/pymeos/collections/cbuffer/__init__.py @@ -0,0 +1,4 @@ +from .cbuffer import Cbuffer +from .cbufferset import CbufferSet + +__all__ = ["Cbuffer", "CbufferSet"] diff --git a/pymeos/collections/cbuffer/cbuffer.py b/pymeos/collections/cbuffer/cbuffer.py new file mode 100644 index 00000000..81bffa40 --- /dev/null +++ b/pymeos/collections/cbuffer/cbuffer.py @@ -0,0 +1,673 @@ +from __future__ import annotations + +from typing import Optional, Union, TYPE_CHECKING + +import shapely.geometry.base as shp +from pymeos_cffi import * + +if TYPE_CHECKING: + from ...boxes import STBox + + +class Cbuffer: + """ + Class for representing a circular buffer, that is, a point together with a + radius. + + ``Cbuffer`` objects can be created with a single argument of type string as + in MobilityDB. + + >>> Cbuffer('Cbuffer(Point(1 1), 2)') + + Another possibility is to provide the ``point`` and the ``radius`` + arguments. + + >>> Cbuffer(point=Point(1, 1), radius=2.0) + + """ + + __slots__ = ["_inner"] + + _mobilitydb_name = "cbuffer" + + # ------------------------- Constructors ---------------------------------- + def __init__( + self, + string: Optional[str] = None, + *, + point: Optional[Union[str, shp.BaseGeometry]] = None, + radius: Optional[float] = None, + _inner=None, + ): + assert (_inner is not None) or (string is not None) != ( + point is not None and radius is not None + ), ( + "Either string must be not None or both point and radius must be" + " not None" + ) + if _inner is not None: + self._inner = _inner + elif string is not None: + self._inner = cbuffer_in(string) + else: + if isinstance(point, str): + gs = geom_in(point, -1) + else: + gs = geometry_to_gserialized(point) + self._inner = cbuffer_make(gs, float(radius)) + + def __copy__(self) -> Cbuffer: + """ + Returns a copy of ``self``. + + Returns: + A :class:`Cbuffer` instance. + + MEOS Functions: + cbuffer_copy + """ + inner_copy = cbuffer_copy(self._inner) + return Cbuffer(_inner=inner_copy) + + @staticmethod + def from_wkb(wkb: bytes) -> Cbuffer: + """ + Returns a `Cbuffer` from its WKB representation. + + Args: + wkb: WKB representation + + Returns: + A new :class:`Cbuffer` instance + + MEOS Functions: + cbuffer_from_wkb + """ + result = cbuffer_from_wkb(wkb, len(wkb)) + return Cbuffer(_inner=result) + + @staticmethod + def from_hexwkb(hexwkb: str) -> Cbuffer: + """ + Returns a `Cbuffer` from its WKB representation in hex-encoded ASCII. + + Args: + hexwkb: WKB representation in hex-encoded ASCII + + Returns: + A new :class:`Cbuffer` instance + + MEOS Functions: + cbuffer_from_hexwkb + """ + result = cbuffer_from_hexwkb(hexwkb) + return Cbuffer(_inner=result) + + # ------------------------- Output ---------------------------------------- + def __str__(self, max_decimals: int = 15): + """ + Returns the string representation of ``self``. + + Returns: + A :class:`str` instance. + + MEOS Functions: + cbuffer_out + """ + return cbuffer_out(self._inner, max_decimals) + + def __repr__(self): + """ + Returns the string representation of ``self``. + + Returns: + A :class:`str` instance. + + MEOS Functions: + cbuffer_out + """ + return f"{self.__class__.__name__}" f"({self})" + + def as_wkt(self, max_decimals: int = 15) -> str: + """ + Returns the WKT representation of ``self``. + + Args: + max_decimals: The number of decimal places to use. + + Returns: + A :class:`str` instance. + + MEOS Functions: + cbuffer_as_text + """ + return cbuffer_as_text(self._inner, max_decimals) + + def as_text(self, max_decimals: int = 15) -> str: + """ + Returns the WKT representation of ``self``. + + Args: + max_decimals: The number of decimal places to use. + + Returns: + A :class:`str` instance. + + MEOS Functions: + cbuffer_as_text + """ + return cbuffer_as_text(self._inner, max_decimals) + + def as_ewkt(self, max_decimals: int = 15) -> str: + """ + Returns the EWKT representation of ``self``. + + Args: + max_decimals: The number of decimal places to use. + + Returns: + A :class:`str` instance. + + MEOS Functions: + cbuffer_as_ewkt + """ + return cbuffer_as_ewkt(self._inner, max_decimals) + + def as_wkb(self) -> bytes: + """ + Returns the WKB representation of ``self``. + + Returns: + A :class:`bytes` object with the WKB representation of ``self``. + + MEOS Functions: + cbuffer_as_wkb + """ + return cbuffer_as_wkb(self._inner, 4)[0] + + 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: + cbuffer_as_hexwkb + """ + return cbuffer_as_hexwkb(self._inner, -1, None) + + # ------------------------- Conversions ----------------------------------- + def to_geometry(self, precision: int = 15) -> shp.BaseGeometry: + """ + Returns the geometry representation of ``self``. + + Args: + precision: The number of decimal places to use for the coordinates. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` instance. + + MEOS Functions: + cbuffer_to_geom + """ + return gserialized_to_shapely_geometry( + cbuffer_to_geom(self._inner), precision + ) + + def to_stbox(self) -> STBox: + """ + Returns the bounding box of ``self``. + + Returns: + A new :class:`~pymeos.boxes.STBox` instance. + + MEOS Functions: + cbuffer_to_stbox + """ + from ...boxes import STBox + + return STBox(_inner=cbuffer_to_stbox(self._inner)) + + @staticmethod + def from_geometry(geom: shp.BaseGeometry) -> Cbuffer: + """ + Returns a `Cbuffer` from a geometry. + + Args: + geom: A :class:`~shapely.geometry.base.BaseGeometry` instance. + + Returns: + A new :class:`Cbuffer` instance. + + MEOS Functions: + geom_to_cbuffer + """ + result = geom_to_cbuffer(geometry_to_gserialized(geom)) + return Cbuffer(_inner=result) + + # ------------------------- Accessors ------------------------------------- + def point(self, precision: int = 15) -> shp.BaseGeometry: + """ + Returns the point of ``self``. + + Args: + precision: The number of decimal places to use for the coordinates. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` instance. + + MEOS Functions: + cbuffer_point + """ + return gserialized_to_shapely_geometry( + cbuffer_point(self._inner), precision + ) + + def radius(self) -> float: + """ + Returns the radius of ``self``. + + Returns: + A :class:`float` instance. + + MEOS Functions: + cbuffer_radius + """ + return cbuffer_radius(self._inner) + + def srid(self) -> int: + """ + Returns the SRID of ``self``. + + Returns: + An :class:`int` instance. + + MEOS Functions: + cbuffer_srid + """ + return cbuffer_srid(self._inner) + + def hash(self) -> int: + """ + Returns the hash of ``self``. + + Returns: + An :class:`int` instance. + + MEOS Functions: + cbuffer_hash + """ + return cbuffer_hash(self._inner) + + def __hash__(self) -> int: + """ + Returns the hash of ``self``. + + Returns: + An :class:`int` instance. + + MEOS Functions: + cbuffer_hash + """ + return cbuffer_hash(self._inner) + + # ------------------------- Transformations ------------------------------- + def round(self, max_decimals: int = 0) -> Cbuffer: + """ + Returns `self` rounded to the given number of decimal digits. + + Args: + max_decimals: Maximum number of decimal digits. + + Returns: + A new :class:`Cbuffer` instance. + + MEOS Functions: + cbuffer_round + """ + return Cbuffer(_inner=cbuffer_round(self._inner, max_decimals)) + + def set_srid(self, srid: int) -> Cbuffer: + """ + Returns a new :class:`Cbuffer` with the SRID set to ``srid``. + + Args: + srid: The SRID to set. + + Returns: + A new :class:`Cbuffer` instance. + + MEOS Functions: + cbuffer_set_srid + """ + new_inner = cbuffer_copy(self._inner) + cbuffer_set_srid(new_inner, srid) + return Cbuffer(_inner=new_inner) + + def transform(self, srid: int) -> Cbuffer: + """ + Returns a new :class:`Cbuffer` transformed to another SRID. + + Args: + srid: The desired SRID. + + Returns: + A new :class:`Cbuffer` instance. + + MEOS Functions: + cbuffer_transform + """ + return Cbuffer(_inner=cbuffer_transform(self._inner, srid)) + + # ------------------------- Distance Operations --------------------------- + def distance( + self, other: Union[Cbuffer, shp.BaseGeometry, STBox] + ) -> float: + """ + Returns the distance between ``self`` and ``other``. + + Args: + other: A :class:`Cbuffer`, geometry or + :class:`~pymeos.boxes.STBox` instance. + + Returns: + A :class:`float` instance. + + MEOS Functions: + distance_cbuffer_cbuffer, distance_cbuffer_geo, + distance_cbuffer_stbox + """ + from ...boxes import STBox + + if isinstance(other, Cbuffer): + return distance_cbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, shp.BaseGeometry): + return distance_cbuffer_geo( + self._inner, geometry_to_gserialized(other) + ) + elif isinstance(other, STBox): + return distance_cbuffer_stbox(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + + def nearest_approach_distance(self, other: STBox) -> float: + """ + Returns the nearest approach distance between ``self`` and ``other``. + + Args: + other: A :class:`~pymeos.boxes.STBox` instance. + + Returns: + A :class:`float` instance. + + MEOS Functions: + nad_cbuffer_stbox + """ + from ...boxes import STBox + + if isinstance(other, STBox): + return nad_cbuffer_stbox(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + + # ------------------------- Spatial Relationships ------------------------- + def contains(self, other: Cbuffer) -> bool: + """ + Returns whether ``self`` contains ``other``. + + Args: + other: A :class:`Cbuffer` instance. + + Returns: + ``True`` if ``self`` contains ``other``, ``False`` otherwise. + + MEOS Functions: + contains_cbuffer_cbuffer + """ + return contains_cbuffer_cbuffer(self._inner, other._inner) == 1 + + def covers(self, other: Cbuffer) -> bool: + """ + Returns whether ``self`` covers ``other``. + + Args: + other: A :class:`Cbuffer` instance. + + Returns: + ``True`` if ``self`` covers ``other``, ``False`` otherwise. + + MEOS Functions: + covers_cbuffer_cbuffer + """ + return covers_cbuffer_cbuffer(self._inner, other._inner) == 1 + + def is_disjoint(self, other: Cbuffer) -> bool: + """ + Returns whether ``self`` is disjoint from ``other``. + + Args: + other: A :class:`Cbuffer` instance. + + Returns: + ``True`` if ``self`` is disjoint from ``other``, ``False`` + otherwise. + + MEOS Functions: + disjoint_cbuffer_cbuffer + """ + return disjoint_cbuffer_cbuffer(self._inner, other._inner) == 1 + + def intersects(self, other: Cbuffer) -> bool: + """ + Returns whether ``self`` intersects ``other``. + + Args: + other: A :class:`Cbuffer` instance. + + Returns: + ``True`` if ``self`` intersects ``other``, ``False`` otherwise. + + MEOS Functions: + intersects_cbuffer_cbuffer + """ + return intersects_cbuffer_cbuffer(self._inner, other._inner) == 1 + + def touches(self, other: Cbuffer) -> bool: + """ + Returns whether ``self`` touches ``other``. + + Args: + other: A :class:`Cbuffer` instance. + + Returns: + ``True`` if ``self`` touches ``other``, ``False`` otherwise. + + MEOS Functions: + touches_cbuffer_cbuffer + """ + return touches_cbuffer_cbuffer(self._inner, other._inner) == 1 + + def is_within_distance(self, other: Cbuffer, distance: float) -> bool: + """ + Returns whether ``self`` is within ``distance`` of ``other``. + + Args: + other: A :class:`Cbuffer` instance. + distance: The distance to check. + + Returns: + ``True`` if ``self`` is within ``distance`` of ``other``, ``False`` + otherwise. + + MEOS Functions: + dwithin_cbuffer_cbuffer + """ + return dwithin_cbuffer_cbuffer(self._inner, other._inner, distance) == 1 + + # ------------------------- Comparisons ----------------------------------- + def __eq__(self, other): + """ + Returns whether ``self`` is equal to ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if ``self`` is equal to ``other``, ``False`` otherwise. + + MEOS Functions: + cbuffer_eq + """ + if isinstance(other, self.__class__): + return cbuffer_eq(self._inner, other._inner) + return False + + def __ne__(self, other): + """ + Returns whether ``self`` is not equal to ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if ``self`` is not equal to ``other``, ``False`` + otherwise. + + MEOS Functions: + cbuffer_ne + """ + if isinstance(other, self.__class__): + return cbuffer_ne(self._inner, other._inner) + return True + + def __lt__(self, other): + """ + Returns whether ``self`` is less than ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if ``self`` is less than ``other``, ``False`` otherwise. + + MEOS Functions: + cbuffer_lt + """ + if isinstance(other, self.__class__): + return cbuffer_lt(self._inner, other._inner) + raise TypeError(f"Operation not supported with type {other.__class__}") + + def __le__(self, other): + """ + Returns whether ``self`` is less than or equal to ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if ``self`` is less than or equal to ``other``, ``False`` + otherwise. + + MEOS Functions: + cbuffer_le + """ + if isinstance(other, self.__class__): + return cbuffer_le(self._inner, other._inner) + raise TypeError(f"Operation not supported with type {other.__class__}") + + def __gt__(self, other): + """ + Returns whether ``self`` is greater than ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if ``self`` is greater than ``other``, ``False`` + otherwise. + + MEOS Functions: + cbuffer_gt + """ + if isinstance(other, self.__class__): + return cbuffer_gt(self._inner, other._inner) + raise TypeError(f"Operation not supported with type {other.__class__}") + + def __ge__(self, other): + """ + Returns whether ``self`` is greater than or equal to ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if ``self`` is greater than or equal to ``other``, + ``False`` otherwise. + + MEOS Functions: + cbuffer_ge + """ + if isinstance(other, self.__class__): + return cbuffer_ge(self._inner, other._inner) + raise TypeError(f"Operation not supported with type {other.__class__}") + + def cmp(self, other: Cbuffer) -> int: + """ + Returns -1, 0, or 1 depending on whether ``self`` is less than, equal + to, or greater than ``other``. + + Args: + other: A :class:`Cbuffer` instance. + + Returns: + An :class:`int` instance. + + MEOS Functions: + cbuffer_cmp + """ + return cbuffer_cmp(self._inner, other._inner) + + def same(self, other: Cbuffer) -> bool: + """ + Returns whether ``self`` is spatially the same as ``other``. + + Args: + other: A :class:`Cbuffer` instance. + + Returns: + ``True`` if ``self`` is the same as ``other``, ``False`` otherwise. + + MEOS Functions: + cbuffer_same + """ + return cbuffer_same(self._inner, other._inner) + + def not_same(self, other: Cbuffer) -> bool: + """ + Returns whether ``self`` is spatially not the same as ``other``. + + Args: + other: A :class:`Cbuffer` instance. + + Returns: + ``True`` if ``self`` is not the same as ``other``, ``False`` + otherwise. + + MEOS Functions: + cbuffer_nsame + """ + return cbuffer_nsame(self._inner, other._inner) + + # ------------------------- Database Operations --------------------------- + @staticmethod + def read_from_cursor(value, _=None): + """ + Reads a :class:`Cbuffer` from a database cursor. Used when + automatically loading objects from the database. + Users should use the class constructor instead. + """ + if not value: + return None + return Cbuffer(string=value) diff --git a/pymeos/collections/cbuffer/cbufferset.py b/pymeos/collections/cbuffer/cbufferset.py new file mode 100644 index 00000000..d8d55c11 --- /dev/null +++ b/pymeos/collections/cbuffer/cbufferset.py @@ -0,0 +1,229 @@ +from __future__ import annotations + +from typing import List, Optional, Union, overload + +from pymeos_cffi import * + +from .cbuffer import Cbuffer +from ..base import Set + + +class CbufferSet(Set[Cbuffer]): + """ + Class for representing a set of :class:`Cbuffer` values. + + ``CbufferSet`` objects can be created with a single argument of type string + as in MobilityDB. + + >>> CbufferSet(string='{Cbuffer(Point(1 1), 1), Cbuffer(Point(2 2), 2)}') + + Another possibility is to create a ``CbufferSet`` object from a list of + :class:`Cbuffer` instances. + + >>> CbufferSet(elements=[Cbuffer(point=Point(1, 1), radius=1)]) + + """ + + __slots__ = ["_inner"] + + _mobilitydb_name = "cbufferset" + + _parse_function = cbufferset_in + _parse_value_function = lambda x: ( + cbuffer_in(x) if isinstance(x, str) else x._inner + ) + _make_function = cbufferset_make + + # ------------------------- Constructors ---------------------------------- + + # ------------------------- Output ---------------------------------------- + + def __str__(self, max_decimals: int = 15): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + cbufferset_out + """ + return cbufferset_out(self._inner, max_decimals) + + # ------------------------- Conversions ----------------------------------- + + def to_spanset(self): + raise NotImplementedError() + + def to_span(self): + raise NotImplementedError() + + # ------------------------- Accessors ------------------------------------- + + def start_element(self) -> Cbuffer: + """ + Returns the first element in ``self``. + + Returns: + A :class:`Cbuffer` instance + + MEOS Functions: + cbufferset_start_value + """ + return Cbuffer(_inner=cbufferset_start_value(self._inner)) + + def end_element(self) -> Cbuffer: + """ + Returns the last element in ``self``. + + Returns: + A :class:`Cbuffer` instance + + MEOS Functions: + cbufferset_end_value + """ + return Cbuffer(_inner=cbufferset_end_value(self._inner)) + + def element_n(self, n: int) -> Cbuffer: + """ + Returns the ``n``-th element in ``self``. + + Args: + n: The 0-based index of the element to return. + + Returns: + A :class:`Cbuffer` instance + + MEOS Functions: + cbufferset_value_n + """ + super().element_n(n) + return Cbuffer(_inner=cbufferset_value_n(self._inner, n + 1)[0]) + + def elements(self) -> List[Cbuffer]: + """ + Returns a list of all elements in ``self``. + + Returns: + A list of :class:`Cbuffer` instances + + MEOS Functions: + cbufferset_values + """ + elems = cbufferset_values(self._inner) + return [Cbuffer(_inner=elems[i]) for i in range(self.num_elements())] + + # ------------------------- Topological Operations ------------------------ + + def contains(self, content: Union[CbufferSet, Cbuffer]) -> bool: + """ + Returns whether ``self`` contains ``content``. + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_set_cbuffer, contains_set_set + """ + if isinstance(content, Cbuffer): + return contains_set_cbuffer(self._inner, content._inner) + else: + return super().contains(content) + + # ------------------------- Set Operations -------------------------------- + + @overload + def intersection(self, other: Cbuffer) -> Optional[CbufferSet]: ... + + @overload + def intersection(self, other: CbufferSet) -> Optional[CbufferSet]: ... + + def intersection(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: A :class:`CbufferSet` or :class:`Cbuffer` instance + + Returns: + An object of the same type as ``other`` or ``None`` if the + intersection is empty. + + MEOS Functions: + intersection_set_cbuffer, intersection_set_set + """ + if isinstance(other, Cbuffer): + result = intersection_set_cbuffer(self._inner, other._inner) + return CbufferSet(_inner=result) if result is not None else None + elif isinstance(other, CbufferSet): + result = intersection_set_set(self._inner, other._inner) + return CbufferSet(_inner=result) if result is not None else None + else: + return super().intersection(other) + + def minus(self, other: Union[CbufferSet, Cbuffer]) -> Optional[CbufferSet]: + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: A :class:`CbufferSet` or :class:`Cbuffer` instance + + Returns: + A :class:`CbufferSet` instance or ``None`` if the difference is + empty. + + MEOS Functions: + minus_set_cbuffer, minus_set_set + """ + if isinstance(other, Cbuffer): + result = minus_set_cbuffer(self._inner, other._inner) + return CbufferSet(_inner=result) if result is not None else None + elif isinstance(other, CbufferSet): + result = minus_set_set(self._inner, other._inner) + return CbufferSet(_inner=result) if result is not None else None + else: + return super().minus(other) + + def subtract_from(self, other: Cbuffer) -> Optional[CbufferSet]: + """ + Returns the difference of ``other`` and ``self``. + + Args: + other: A :class:`Cbuffer` instance + + Returns: + A :class:`CbufferSet` instance. + + MEOS Functions: + minus_cbuffer_set + + See Also: + :meth:`minus` + """ + result = minus_cbuffer_set(other._inner, self._inner) + return CbufferSet(_inner=result) if result is not None else None + + def union(self, other: Union[CbufferSet, Cbuffer]) -> CbufferSet: + """ + Returns the union of ``self`` and ``other``. + + Args: + other: A :class:`CbufferSet` or :class:`Cbuffer` instance + + Returns: + A :class:`CbufferSet` instance. + + MEOS Functions: + union_set_cbuffer, union_set_set + """ + if isinstance(other, Cbuffer): + result = union_set_cbuffer(self._inner, other._inner) + return CbufferSet(_inner=result) if result is not None else None + elif isinstance(other, CbufferSet): + result = union_set_set(self._inner, other._inner) + return CbufferSet(_inner=result) if result is not None else None + else: + return super().union(other) diff --git a/pymeos/collections/npoint/__init__.py b/pymeos/collections/npoint/__init__.py new file mode 100644 index 00000000..332aaa11 --- /dev/null +++ b/pymeos/collections/npoint/__init__.py @@ -0,0 +1,5 @@ +from .npoint import Npoint +from .npointset import NpointSet +from .nsegment import Nsegment + +__all__ = ["Npoint", "NpointSet", "Nsegment"] diff --git a/pymeos/collections/npoint/npoint.py b/pymeos/collections/npoint/npoint.py new file mode 100644 index 00000000..18410888 --- /dev/null +++ b/pymeos/collections/npoint/npoint.py @@ -0,0 +1,421 @@ +from __future__ import annotations + +from typing import Optional, Union, TYPE_CHECKING + +import shapely.geometry.base as shp +from pymeos_cffi import * + +if TYPE_CHECKING: + from ...boxes import STBox + from ...main import TNpoint + + +class Npoint: + """ + Class for representing a network point, i.e. a point defined by a route + identifier and a relative position along that route. + + ``Npoint`` objects can be created with a single argument of type string as + in MobilityDB. + + >>> Npoint('NPoint(1, 0.5)') + + Another possibility is to provide the route identifier and the relative + position with the corresponding parameters. + + >>> Npoint(route=1, position=0.5) + + """ + + __slots__ = ["_inner"] + + _mobilitydb_name = "npoint" + + # ------------------------- Constructors ---------------------------------- + def __init__( + self, + string: Optional[str] = None, + *, + route: Optional[int] = None, + position: Optional[float] = None, + _inner=None, + ): + assert (_inner is not None) or (string is not None) != ( + route is not None and position is not None + ), ( + "Either string must be not None or route and position must be" + " not None" + ) + if _inner is not None: + self._inner = _inner + elif string is not None: + self._inner = npoint_in(string) + else: + self._inner = npoint_make(route, position) + + @staticmethod + def from_wkb(wkb: bytes) -> Npoint: + """ + Returns a `Npoint` from its WKB representation. + + Args: + wkb: WKB representation + + Returns: + A new :class:`Npoint` instance + + MEOS Functions: + npoint_from_wkb + """ + result = npoint_from_wkb(wkb, len(wkb)) + return Npoint(_inner=result) + + @staticmethod + def from_hexwkb(hexwkb: str) -> Npoint: + """ + Returns a `Npoint` from its WKB representation in hex-encoded ASCII. + + Args: + hexwkb: WKB representation in hex-encoded ASCII + + Returns: + A new :class:`Npoint` instance + + MEOS Functions: + npoint_from_hexwkb + """ + result = npoint_from_hexwkb(hexwkb) + return Npoint(_inner=result) + + @staticmethod + def from_geometry(geom: shp.BaseGeometry) -> Npoint: + """ + Returns a `Npoint` from a `shp.BaseGeometry`. + + Args: + geom: A `shp.BaseGeometry` instance. + + Returns: + A new :class:`Npoint` instance. + + MEOS Functions: + geompoint_to_npoint + """ + gs = geometry_to_gserialized(geom) + return Npoint(_inner=geompoint_to_npoint(gs)) + + def __copy__(self) -> Npoint: + """ + Returns a copy of ``self``. + + Returns: + A :class:`Npoint` instance. + + MEOS Functions: + npoint_round + """ + inner_copy = npoint_round(self._inner, 100) + return Npoint(_inner=inner_copy) + + # ------------------------- Output ---------------------------------------- + def __str__(self, max_decimals: int = 15): + """ + Returns the string representation of ``self``. + + Returns: + A :class:`str` instance. + + MEOS Functions: + npoint_out + """ + return npoint_out(self._inner, max_decimals) + + def __repr__(self): + """ + Returns the string representation of ``self``. + + Returns: + A :class:`str` instance. + + MEOS Functions: + npoint_out + """ + return f"{self.__class__.__name__}" f"({self})" + + def as_wkb(self) -> bytes: + """ + Returns the WKB representation of ``self``. + + Returns: + A :class:`bytes` object with the WKB representation of ``self``. + + MEOS Functions: + npoint_as_wkb + """ + return npoint_as_wkb(self._inner, 4)[0] + + 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: + npoint_as_hexwkb + """ + return npoint_as_hexwkb(self._inner, -1)[0] + + def as_ewkt(self, max_decimals: int = 15) -> str: + """ + Returns the EWKT representation of ``self``. + + Args: + max_decimals: The number of decimal places to use for the + coordinates. + + Returns: + A :class:`str` instance. + + MEOS Functions: + npoint_as_ewkt + """ + return npoint_as_ewkt(self._inner, max_decimals) + + def as_text(self, max_decimals: int = 15) -> str: + """ + Returns the WKT representation of ``self``. + + Args: + max_decimals: The number of decimal places to use for the + coordinates. + + Returns: + A :class:`str` instance. + + MEOS Functions: + npoint_as_text + """ + return npoint_as_text(self._inner, max_decimals) + + # ------------------------- Conversions ----------------------------------- + def to_geometry(self, precision: int = 15) -> shp.BaseGeometry: + """ + Returns the geometry represented by ``self``. + + Args: + precision: The number of decimal places to use for the coordinates. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` instance. + + MEOS Functions: + npoint_to_geompoint + """ + return gserialized_to_shapely_geometry( + npoint_to_geompoint(self._inner), precision + ) + + def to_stbox(self) -> STBox: + """ + Returns the bounding box of ``self`` as an :class:`STBox`. + + Returns: + A new :class:`STBox` instance. + + MEOS Functions: + npoint_to_stbox + """ + from ...boxes import STBox + + return STBox(_inner=npoint_to_stbox(self._inner)) + + # ------------------------- Accessors ------------------------------------- + def route(self) -> int: + """ + Returns the route identifier of ``self``. + + Returns: + An :class:`int` with the route identifier of ``self``. + + MEOS Functions: + npoint_route + """ + return npoint_route(self._inner) + + def position(self) -> float: + """ + Returns the relative position of ``self`` along its route. + + Returns: + A :class:`float` with the relative position of ``self``. + + MEOS Functions: + npoint_position + """ + return npoint_position(self._inner) + + def srid(self) -> int: + """ + Returns the SRID of ``self``. + + Returns: + An :class:`int` with the SRID of ``self``. + + MEOS Functions: + npoint_srid + """ + return npoint_srid(self._inner) + + # ------------------------- Transformations ------------------------------- + def round(self, max_decimals: int = 0) -> Npoint: + """ + Returns a new :class:`Npoint` with the relative position of ``self`` + rounded to ``max_decimals`` decimal places. + + Args: + max_decimals: The number of decimal places. + + Returns: + A new :class:`Npoint` instance. + + MEOS Functions: + npoint_round + """ + return Npoint(_inner=npoint_round(self._inner, max_decimals)) + + # ------------------------- Comparisons ----------------------------------- + def __eq__(self, other): + """ + Returns whether ``self`` is equal to ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if ``self`` is equal to ``other``, ``False`` otherwise. + + MEOS Functions: + npoint_eq + """ + if isinstance(other, self.__class__): + return npoint_eq(self._inner, other._inner) + return False + + def __ne__(self, other): + """ + Returns whether ``self`` is not equal to ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if ``self`` is not equal to ``other``, ``False`` + otherwise. + + MEOS Functions: + npoint_ne + """ + if isinstance(other, self.__class__): + return npoint_ne(self._inner, other._inner) + return True + + def __lt__(self, other): + """ + Returns whether ``self`` is less than ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if ``self`` is less than ``other``, ``False`` otherwise. + + MEOS Functions: + npoint_lt + """ + if isinstance(other, self.__class__): + return npoint_lt(self._inner, other._inner) + raise TypeError(f"Operation not supported with type {other.__class__}") + + def __le__(self, other): + """ + Returns whether ``self`` is less than or equal to ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if ``self`` is less than or equal to ``other``, ``False`` + otherwise. + + MEOS Functions: + npoint_le + """ + if isinstance(other, self.__class__): + return npoint_le(self._inner, other._inner) + raise TypeError(f"Operation not supported with type {other.__class__}") + + def __gt__(self, other): + """ + Returns whether ``self`` is greater than ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if ``self`` is greater than ``other``, ``False`` + otherwise. + + MEOS Functions: + npoint_gt + """ + if isinstance(other, self.__class__): + return npoint_gt(self._inner, other._inner) + raise TypeError(f"Operation not supported with type {other.__class__}") + + def __ge__(self, other): + """ + Returns whether ``self`` is greater than or equal to ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if ``self`` is greater than or equal to ``other``, + ``False`` otherwise. + + MEOS Functions: + npoint_ge + """ + if isinstance(other, self.__class__): + return npoint_ge(self._inner, other._inner) + raise TypeError(f"Operation not supported with type {other.__class__}") + + def same(self, other: Npoint) -> bool: + """ + Returns whether ``self`` and ``other`` are spatially equal. + + Args: + other: The :class:`Npoint` to compare with ``self``. + + Returns: + ``True`` if ``self`` is spatially equal to ``other``, ``False`` + otherwise. + + MEOS Functions: + npoint_same + """ + return npoint_same(self._inner, other._inner) + + def __hash__(self) -> int: + """ + Returns the hash of ``self``. + + Returns: + An :class:`int` with the hash of ``self``. + + MEOS Functions: + npoint_hash + """ + return npoint_hash(self._inner) diff --git a/pymeos/collections/npoint/npointset.py b/pymeos/collections/npoint/npointset.py new file mode 100644 index 00000000..a99b7a41 --- /dev/null +++ b/pymeos/collections/npoint/npointset.py @@ -0,0 +1,139 @@ +from __future__ import annotations + +from typing import List, Optional, Union + +from pymeos_cffi import ( + npointset_in, + npointset_out, + npointset_make, + npointset_start_value, + npointset_end_value, + npointset_value_n, + npointset_values, + npointset_routes, + npoint_in, +) + +from ..base import Set +from .npoint import Npoint + + +def _npointset_make(values: List["CData"]) -> "CData": + return npointset_make(values, len(values)) + + +class NpointSet(Set[Npoint]): + """ + Class for representing a set of network points. + + ``NpointSet`` objects can be created with a single argument of type string + as in MobilityDB. + + >>> NpointSet(string='{NPoint(1, 0.5), NPoint(2, 0.3)}') + + Another possibility is to create a ``NpointSet`` object from a list of + network points. + + >>> NpointSet(elements=[Npoint(1, 0.5), Npoint(2, 0.3)]) + + """ + + __slots__ = ["_inner"] + + _mobilitydb_name = "npointset" + + _parse_function = npointset_in + _parse_value_function = lambda x: ( + npoint_in(x)._inner if isinstance(x, str) else x._inner + ) + _make_function = _npointset_make + + # ------------------------- Constructors ---------------------------------- + + # ------------------------- Output ---------------------------------------- + def __str__(self, max_decimals: int = 15): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + npointset_out + """ + return npointset_out(self._inner, max_decimals) + + # ------------------------- Conversions ----------------------------------- + def to_spanset(self): + raise NotImplementedError() + + def to_span(self): + raise NotImplementedError() + + # ------------------------- Accessors ------------------------------------- + def start_element(self) -> Npoint: + """ + Returns the first element in ``self``. + + Returns: + A :class:`Npoint` instance + + MEOS Functions: + npointset_start_value + """ + return Npoint(_inner=npointset_start_value(self._inner)) + + def end_element(self) -> Npoint: + """ + Returns the last element in ``self``. + + Returns: + A :class:`Npoint` instance + + MEOS Functions: + npointset_end_value + """ + return Npoint(_inner=npointset_end_value(self._inner)) + + def element_n(self, n: int) -> Npoint: + """ + Returns the ``n``-th element in ``self``. + + Args: + n: The 0-based index of the element to return. + + Returns: + A :class:`Npoint` instance + + MEOS Functions: + npointset_value_n + """ + super().element_n(n) + return Npoint(_inner=npointset_value_n(self._inner, n + 1)[0]) + + def elements(self) -> List[Npoint]: + """ + Returns the elements in ``self``. + + Returns: + A list of :class:`Npoint` instances + + MEOS Functions: + npointset_values + """ + elems = npointset_values(self._inner) + return [Npoint(_inner=elems[i]) for i in range(self.num_elements())] + + def routes(self) -> Set[int]: + """ + Returns the set of route identifiers of the elements in ``self``. + + Returns: + A new :class:`Set` instance with the route identifiers. + + MEOS Functions: + npointset_routes + """ + from ...factory import _CollectionFactory + + return _CollectionFactory.create_collection(npointset_routes(self._inner)) diff --git a/pymeos/collections/npoint/nsegment.py b/pymeos/collections/npoint/nsegment.py new file mode 100644 index 00000000..ac63e806 --- /dev/null +++ b/pymeos/collections/npoint/nsegment.py @@ -0,0 +1,155 @@ +from __future__ import annotations + +from typing import Optional, TYPE_CHECKING + +import shapely.geometry.base as shp +from pymeos_cffi import * + +if TYPE_CHECKING: + from ...boxes import STBox + + +class Nsegment: + """ + Class for representing a network segment, that is, a route identifier + together with a start and end position along that route. + + ``Nsegment`` objects can be created with a single string argument as in + MobilityDB. + + >>> Nsegment('NSegment(1, 0.2, 0.6)') + + Another possibility is to provide the ``rid``, ``pos1`` and ``pos2`` + arguments. + + >>> Nsegment(rid=1, pos1=0.2, pos2=0.6) + """ + + __slots__ = ["_inner"] + + _mobilitydb_name = "nsegment" + + # ------------------------- Constructors ---------------------------------- + def __init__( + self, + string: Optional[str] = None, + *, + rid: Optional[int] = None, + pos1: Optional[float] = None, + pos2: Optional[float] = None, + _inner=None, + ): + assert (_inner is not None) or (string is not None) != ( + rid is not None and pos1 is not None and pos2 is not None + ), ( + "Either string must be not None or rid, pos1 and pos2 must be" + " not None" + ) + if _inner is not None: + self._inner = _inner + elif string is not None: + self._inner = nsegment_in(string) + else: + self._inner = nsegment_make(rid, float(pos1), float(pos2)) + + def __copy__(self) -> Nsegment: + """ + Returns a copy of ``self``. + + MEOS Functions: + nsegment_round + """ + return Nsegment(_inner=nsegment_round(self._inner, 100)) + + # ------------------------- Output ---------------------------------------- + def __str__(self, max_decimals: int = 15) -> str: + """ + Returns the string representation of ``self``. + + MEOS Functions: + nsegment_out + """ + return nsegment_out(self._inner, max_decimals) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self})" + + # ------------------------- Accessors ------------------------------------- + def route(self) -> int: + """ + Returns the route identifier of ``self``. + + MEOS Functions: + nsegment_route + """ + return nsegment_route(self._inner) + + def start_position(self) -> float: + """ + Returns the start position of ``self``. + + MEOS Functions: + nsegment_start_position + """ + return nsegment_start_position(self._inner) + + def end_position(self) -> float: + """ + Returns the end position of ``self``. + + MEOS Functions: + nsegment_end_position + """ + return nsegment_end_position(self._inner) + + def srid(self) -> int: + """ + Returns the SRID of ``self``. + + MEOS Functions: + nsegment_srid + """ + return nsegment_srid(self._inner) + + # ------------------------- Conversions ----------------------------------- + def to_geometry(self, precision: int = 15) -> shp.BaseGeometry: + """ + Returns ``self`` as a `shapely` + :class:`~shapely.geometry.base.BaseGeometry`. + + MEOS Functions: + nsegment_to_geom + """ + return gserialized_to_shapely_geometry( + nsegment_to_geom(self._inner), precision + ) + + def to_stbox(self) -> STBox: + """ + Returns the bounding box of ``self``. + + MEOS Functions: + nsegment_to_stbox + """ + from ...boxes import STBox + + return STBox(_inner=nsegment_to_stbox(self._inner)) + + # ------------------------- Transformations ------------------------------- + def round(self, max_decimals: int = 0) -> Nsegment: + """ + Returns ``self`` with the positions rounded to a number of decimals. + + MEOS Functions: + nsegment_round + """ + return Nsegment(_inner=nsegment_round(self._inner, max_decimals)) + + # ------------------------- Comparisons ----------------------------------- + def __eq__(self, other) -> bool: + if isinstance(other, Nsegment): + return str(self) == str(other) + return NotImplemented + + def __hash__(self) -> int: + return hash(str(self)) diff --git a/pymeos/collections/pose/__init__.py b/pymeos/collections/pose/__init__.py new file mode 100644 index 00000000..a2f69bc4 --- /dev/null +++ b/pymeos/collections/pose/__init__.py @@ -0,0 +1,4 @@ +from .pose import Pose +from .poseset import PoseSet + +__all__ = ["Pose", "PoseSet"] diff --git a/pymeos/collections/pose/pose.py b/pymeos/collections/pose/pose.py new file mode 100644 index 00000000..62cdb0d0 --- /dev/null +++ b/pymeos/collections/pose/pose.py @@ -0,0 +1,551 @@ +from __future__ import annotations + +from typing import Optional, Union, TYPE_CHECKING + +import shapely.geometry.base as shp +from pymeos_cffi import * + +if TYPE_CHECKING: + from ...boxes import STBox + from ...collections import Time + + +class Pose: + """ + Class for representing a pose, i.e. a point with an orientation. + + ``Pose`` objects can be created with a single argument of type string as + in MobilityDB. + + >>> Pose('Pose(Point(1 1), 0.5)') + + Another possibility is to provide the spatial point and the orientation + with the corresponding parameters. For 2D poses the orientation is given + by a single angle ``theta``. + + >>> Pose(point='Point(1 1)', theta=0.5) + + For 3D poses the orientation is given by a unit quaternion + ``(W, X, Y, Z)``. + + >>> Pose(point='Point(1 1 1)', W=1.0, X=0.0, Y=0.0, Z=0.0) + + """ + + __slots__ = ["_inner"] + + _mobilitydb_name = "pose" + + # ------------------------- Constructors ---------------------------------- + def __init__( + self, + string: Optional[str] = None, + *, + point: Optional[Union[str, shp.BaseGeometry]] = None, + theta: Optional[float] = None, + W: Optional[float] = None, + X: Optional[float] = None, + Y: Optional[float] = None, + Z: Optional[float] = None, + srid: Optional[int] = 0, + geodetic: bool = False, + _inner=None, + ): + assert (_inner is not None) or (string is not None) != ( + point is not None + ), "Either string must be not None or point must be not None" + if _inner is not None: + self._inner = _inner + elif string is not None: + self._inner = pose_in(string) + else: + gs = geo_to_gserialized(point, geodetic) + if W is not None and X is not None and Y is not None and Z is not None: + self._inner = pose_make_point3d(gs, W, X, Y, Z) + else: + self._inner = pose_make_point2d(gs, float(theta or 0)) + if srid is not None: + pose_set_srid(self._inner, srid) + + def __copy__(self) -> Pose: + """ + Returns a copy of ``self``. + + Returns: + A :class:`Pose` instance. + + MEOS Functions: + pose_copy + """ + inner_copy = pose_copy(self._inner) + return Pose(_inner=inner_copy) + + @staticmethod + def from_wkb(wkb: bytes) -> Pose: + """ + Returns a `Pose` from its WKB representation. + + Args: + wkb: WKB representation + + Returns: + A new :class:`Pose` instance + + MEOS Functions: + pose_from_wkb + """ + result = pose_from_wkb(wkb) + return Pose(_inner=result) + + @staticmethod + def from_hexwkb(hexwkb: str) -> Pose: + """ + Returns a `Pose` from its WKB representation in hex-encoded ASCII. + + Args: + hexwkb: WKB representation in hex-encoded ASCII + + Returns: + A new :class:`Pose` instance + + MEOS Functions: + pose_from_hexwkb + """ + result = pose_from_hexwkb(hexwkb) + return Pose(_inner=result) + + # ------------------------- Output ---------------------------------------- + def __str__(self, max_decimals: int = 15): + """ + Returns the string representation of ``self``. + + Returns: + A :class:`str` instance. + + MEOS Functions: + pose_out + """ + return pose_out(self._inner, max_decimals) + + def __repr__(self): + """ + Returns a string representation of ``self``. + + Returns: + A :class:`str` instance. + + MEOS Functions: + pose_out + """ + return f"{self.__class__.__name__}" f"({self})" + + def as_wkt(self, max_decimals: int = 15) -> str: + """ + Returns the WKT representation of ``self``. + + Args: + max_decimals: The number of decimal places to use for the + coordinates. + + Returns: + A :class:`str` instance. + + MEOS Functions: + pose_as_text + """ + return pose_as_text(self._inner, max_decimals) + + def as_text(self, max_decimals: int = 15) -> str: + """ + Returns the WKT representation of ``self``. + + Args: + max_decimals: The number of decimal places to use for the + coordinates. + + Returns: + A :class:`str` instance. + + MEOS Functions: + pose_as_text + """ + return pose_as_text(self._inner, max_decimals) + + def as_ewkt(self, max_decimals: int = 15) -> str: + """ + Returns the EWKT representation of ``self``. + + Args: + max_decimals: The number of decimal places to use for the + coordinates. + + Returns: + A :class:`str` instance. + + MEOS Functions: + pose_as_ewkt + """ + return pose_as_ewkt(self._inner, max_decimals) + + def as_wkb(self) -> bytes: + """ + Returns the WKB representation of ``self``. + + Returns: + A :class:`bytes` object with the WKB representation of ``self``. + + MEOS Functions: + pose_as_wkb + """ + return pose_as_wkb(self._inner, 4)[0] + + 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: + pose_as_hexwkb + """ + return pose_as_hexwkb(self._inner, -1)[0] + + # ------------------------- Conversions ----------------------------------- + def to_geometry(self, precision: int = 15) -> shp.BaseGeometry: + """ + Returns the point of ``self`` as a `shapely` + :class:`~shapely.BaseGeometry` instance. + + Args: + precision: The precision of the geometry coordinates. + + Returns: + A new :class:`~shapely.BaseGeometry` instance. + + MEOS Functions: + pose_to_point + """ + return gserialized_to_shapely_geometry(pose_to_point(self._inner), precision) + + def to_stbox(self) -> STBox: + """ + Returns the bounding box of ``self`` as an :class:`STBox` instance. + + Returns: + A new :class:`STBox` instance. + + MEOS Functions: + pose_to_stbox + """ + from ...boxes import STBox + + return STBox(_inner=pose_to_stbox(self._inner)) + + # ------------------------- Accessors ------------------------------------- + def orientation(self) -> shp.BaseGeometry: + """ + Returns the orientation of ``self`` as a unit quaternion. + + Returns: + The orientation of ``self``. + + MEOS Functions: + pose_orientation + """ + return pose_orientation(self._inner) + + def rotation(self) -> float: + """ + Returns the rotation angle of ``self``. + + Returns: + A :class:`float` instance. + + MEOS Functions: + pose_rotation + """ + return pose_rotation(self._inner) + + def __hash__(self) -> int: + """ + Returns the hash of ``self``. + + Returns: + A new :class:`int` instance. + + MEOS Functions: + pose_hash + """ + return pose_hash(self._inner) + + # ------------------------- Spatial Reference System ---------------------- + def srid(self) -> int: + """ + Returns the SRID of ``self``. + + Returns: + An :class:`int` instance. + + MEOS Functions: + pose_srid + """ + return pose_srid(self._inner) + + def set_srid(self, srid: int) -> Pose: + """ + Returns a new :class:`Pose` with the SRID set to ``srid``. + + Args: + srid: The desired SRID. + + Returns: + A new :class:`Pose` instance. + + MEOS Functions: + pose_set_srid + """ + result = pose_copy(self._inner) + pose_set_srid(result, srid) + return Pose(_inner=result) + + # ------------------------- Transformations ------------------------------- + def round(self, max_decimals: int = 0) -> Pose: + """ + Returns a new :class:`Pose` with the coordinate values of ``self`` + rounded to ``max_decimals`` decimal places. + + Args: + max_decimals: The number of decimal places. + + Returns: + A new :class:`Pose` instance. + + MEOS Functions: + pose_round + """ + return Pose(_inner=pose_round(self._inner, max_decimals)) + + def transform(self, srid: int) -> Pose: + """ + Returns a new :class:`Pose` transformed to another SRID. + + Args: + srid: The desired SRID. + + Returns: + A new :class:`Pose` instance. + + MEOS Functions: + pose_transform + """ + return Pose(_inner=pose_transform(self._inner, srid)) + + def transform_pipeline( + self, pipeline: str, srid: int, is_forward: bool = True + ) -> Pose: + """ + Returns a new :class:`Pose` transformed using a transformation + pipeline. + + Args: + pipeline: The transformation pipeline. + srid: The desired SRID. + is_forward: Whether to apply the pipeline in the forward direction. + + Returns: + A new :class:`Pose` instance. + + MEOS Functions: + pose_transform_pipeline + """ + return Pose( + _inner=pose_transform_pipeline(self._inner, pipeline, srid, is_forward) + ) + + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[shp.BaseGeometry, Pose, STBox]) -> float: + """ + Returns the distance between ``self`` and ``other``. + + Args: + other: An object to check the distance to. + + Returns: + A :class:`float` instance. + + MEOS Functions: + distance_pose_geo, distance_pose_pose, distance_pose_stbox + """ + from ...boxes import STBox + + if isinstance(other, shp.BaseGeometry): + gs = geo_to_gserialized(other, False) + return distance_pose_geo(self._inner, gs) + elif isinstance(other, Pose): + return distance_pose_pose(self._inner, other._inner) + elif isinstance(other, STBox): + return distance_pose_stbox(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + + # ------------------------- Comparisons ----------------------------------- + def __eq__(self, other): + """ + Returns whether ``self`` is equal to ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if equal, ``False`` otherwise. + + MEOS Functions: + pose_eq + """ + if isinstance(other, self.__class__): + return pose_eq(self._inner, other._inner) + return False + + def __ne__(self, other): + """ + Returns whether ``self`` is not equal to ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if not equal, ``False`` otherwise. + + MEOS Functions: + pose_ne + """ + if isinstance(other, self.__class__): + return pose_ne(self._inner, other._inner) + return True + + def __lt__(self, other): + """ + Returns whether ``self`` is less than ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if less than, ``False`` otherwise. + + MEOS Functions: + pose_lt + """ + if isinstance(other, self.__class__): + return pose_lt(self._inner, other._inner) + raise TypeError(f"Operation not supported with type {other.__class__}") + + def __le__(self, other): + """ + Returns whether ``self`` is less than or equal to ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if less than or equal, ``False`` otherwise. + + MEOS Functions: + pose_le + """ + if isinstance(other, self.__class__): + return pose_le(self._inner, other._inner) + raise TypeError(f"Operation not supported with type {other.__class__}") + + def __gt__(self, other): + """ + Returns whether ``self`` is greater than ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if greater than, ``False`` otherwise. + + MEOS Functions: + pose_gt + """ + if isinstance(other, self.__class__): + return pose_gt(self._inner, other._inner) + raise TypeError(f"Operation not supported with type {other.__class__}") + + def __ge__(self, other): + """ + Returns whether ``self`` is greater than or equal to ``other``. + + Args: + other: The object to compare with ``self``. + + Returns: + ``True`` if greater than or equal, ``False`` otherwise. + + MEOS Functions: + pose_ge + """ + if isinstance(other, self.__class__): + return pose_ge(self._inner, other._inner) + raise TypeError(f"Operation not supported with type {other.__class__}") + + def cmp(self, other: Pose) -> int: + """ + Returns the comparison value between ``self`` and ``other``. + + Args: + other: The :class:`Pose` to compare with ``self``. + + Returns: + An :class:`int` instance: -1, 0 or 1. + + MEOS Functions: + pose_cmp + """ + return pose_cmp(self._inner, other._inner) + + def same(self, other: Pose) -> bool: + """ + Returns whether ``self`` and ``other`` are spatially the same. + + Args: + other: The :class:`Pose` to compare with ``self``. + + Returns: + ``True`` if the same, ``False`` otherwise. + + MEOS Functions: + pose_same + """ + return pose_same(self._inner, other._inner) + + def not_same(self, other: Pose) -> bool: + """ + Returns whether ``self`` and ``other`` are not spatially the same. + + Args: + other: The :class:`Pose` to compare with ``self``. + + Returns: + ``True`` if not the same, ``False`` otherwise. + + MEOS Functions: + pose_nsame + """ + return pose_nsame(self._inner, other._inner) + + # ------------------------- Database Operations --------------------------- + @staticmethod + def read_from_cursor(value, _=None): + """ + Reads a :class:`Pose` from a database cursor. Used when automatically + loading objects from the database. + Users should use the class constructor instead. + """ + if not value: + return None + return Pose(string=value) diff --git a/pymeos/collections/pose/poseset.py b/pymeos/collections/pose/poseset.py new file mode 100644 index 00000000..0cbc7851 --- /dev/null +++ b/pymeos/collections/pose/poseset.py @@ -0,0 +1,228 @@ +from __future__ import annotations + +from typing import Optional, overload, Union, List + +from pymeos_cffi import * + +from .pose import Pose +from ..base import Set + + +class PoseSet(Set[Pose]): + """ + Class for representing a set of pose values. + + ``PoseSet`` objects can be created with a single argument of type string + as in MobilityDB. + + >>> PoseSet(string='{Pose(Point(1 1), 0.5), Pose(Point(2 2), 0.3)}') + + Another possibility is to create a ``PoseSet`` object from a list of + :class:`Pose` instances. + + >>> PoseSet(elements=[Pose('Pose(Point(1 1), 0.5)')]) + + """ + + __slots__ = ["_inner"] + + _mobilitydb_name = "poseset" + + _parse_function = poseset_in + _parse_value_function = lambda pose: ( + pose._inner if isinstance(pose, Pose) else pose_in(pose) + ) + _make_function = lambda elements: poseset_make(elements, len(elements)) + + # ------------------------- Constructors ---------------------------------- + + # ------------------------- Output ---------------------------------------- + + def __str__(self, max_decimals: int = 15): + """ + Return the string representation of the content of ``self``. + + Returns: + A new :class:`str` instance + + MEOS Functions: + poseset_out + """ + return poseset_out(self._inner, max_decimals) + + # ------------------------- Conversions ----------------------------------- + + def to_spanset(self): + raise NotImplementedError() + + def to_span(self): + raise NotImplementedError() + + # ------------------------- Accessors ------------------------------------- + + def start_element(self) -> Pose: + """ + Returns the first element in ``self``. + + Returns: + A :class:`Pose` instance + + MEOS Functions: + poseset_start_value + """ + return Pose(_inner=poseset_start_value(self._inner)) + + def end_element(self) -> Pose: + """ + Returns the last element in ``self``. + + Returns: + A :class:`Pose` instance + + MEOS Functions: + poseset_end_value + """ + return Pose(_inner=poseset_end_value(self._inner)) + + def element_n(self, n: int) -> Pose: + """ + Returns the ``n``-th element in ``self``. + + Args: + n: The 0-based index of the element to return. + + Returns: + A :class:`Pose` instance + + MEOS Functions: + poseset_value_n + """ + super().element_n(n) + return Pose(_inner=poseset_value_n(self._inner, n + 1)[0]) + + def elements(self) -> List[Pose]: + """ + Returns the elements in ``self``. + + Returns: + A list of :class:`Pose` instances + + MEOS Functions: + poseset_values + """ + elems = poseset_values(self._inner) + return [Pose(_inner=elems[i]) for i in range(self.num_elements())] + + # ------------------------- Topological Operations ------------------------ + + def contains(self, content: Union[PoseSet, Pose]) -> bool: + """ + Returns whether ``self`` contains ``content``. + + Args: + content: object to compare with + + Returns: + True if contains, False otherwise + + MEOS Functions: + contains_set_set, contains_set_pose + """ + if isinstance(content, Pose): + return contains_set_pose(self._inner, content._inner) + else: + return super().contains(content) + + # ------------------------- Set Operations -------------------------------- + + @overload + def intersection(self, other: Pose) -> Optional[Pose]: ... + + @overload + def intersection(self, other: PoseSet) -> Optional[PoseSet]: ... + + def intersection(self, other): + """ + Returns the intersection of ``self`` and ``other``. + + Args: + other: A :class:`PoseSet` or :class:`Pose` instance + + Returns: + An object of the same type as ``other`` or ``None`` if the + intersection is empty. + + MEOS Functions: + intersection_set_pose, intersection_set_set + """ + if isinstance(other, Pose): + result = intersection_set_pose(self._inner, other._inner) + return Pose(_inner=result) if result is not None else None + elif isinstance(other, PoseSet): + result = intersection_set_set(self._inner, other._inner) + return PoseSet(_inner=result) if result is not None else None + else: + return super().intersection(other) + + def minus(self, other: Union[PoseSet, Pose]) -> Optional[PoseSet]: + """ + Returns the difference of ``self`` and ``other``. + + Args: + other: A :class:`PoseSet` or :class:`Pose` instance + + Returns: + A :class:`PoseSet` instance or ``None`` if the difference is empty. + + MEOS Functions: + minus_set_pose, minus_set_set + """ + if isinstance(other, Pose): + result = minus_set_pose(self._inner, other._inner) + return PoseSet(_inner=result) if result is not None else None + elif isinstance(other, PoseSet): + result = minus_set_set(self._inner, other._inner) + return PoseSet(_inner=result) if result is not None else None + else: + return super().minus(other) + + def subtract_from(self, other: Pose) -> Optional[Pose]: + """ + Returns the difference of ``other`` and ``self``. + + Args: + other: A :class:`Pose` instance + + Returns: + A :class:`Pose` instance. + + MEOS Functions: + minus_pose_set + + See Also: + :meth:`minus` + """ + result = minus_pose_set(other._inner, self._inner) + return Pose(_inner=result) if result is not None else None + + def union(self, other: Union[PoseSet, Pose]) -> PoseSet: + """ + Returns the union of ``self`` and ``other``. + + Args: + other: A :class:`PoseSet` or :class:`Pose` instance + + Returns: + A :class:`PoseSet` instance. + + MEOS Functions: + union_set_pose, union_set_set + """ + if isinstance(other, Pose): + result = union_set_pose(self._inner, other._inner) + return PoseSet(_inner=result) if result is not None else None + elif isinstance(other, PoseSet): + result = union_set_set(self._inner, other._inner) + return PoseSet(_inner=result) if result is not None else None + else: + return super().union(other) diff --git a/pymeos/factory.py b/pymeos/factory.py index 1bed983e..f49d9ca9 100644 --- a/pymeos/factory.py +++ b/pymeos/factory.py @@ -19,10 +19,25 @@ TGeogPointInst, TGeogPointSeq, TGeogPointSeqSet, + TNpointInst, + TNpointSeq, + TNpointSeqSet, + TCbufferInst, + TCbufferSeq, + TCbufferSeqSet, + TPoseInst, + TPoseSeq, + TPoseSeqSet, + TRgeometryInst, + TRgeometrySeq, + TRgeometrySeqSet, ) from .collections import ( GeometrySet, GeographySet, + NpointSet, + CbufferSet, + PoseSet, IntSet, IntSpan, IntSpanSet, @@ -38,6 +53,30 @@ TsTzSpanSet, ) +try: # the compiled binding (same source pymeos_cffi's own enums use) + from _meos_cffi import lib as _meos_lib +except Exception: # pragma: no cover - the binding is present at runtime + _meos_lib = None + + +def _meos_type(name: str): + """Return the MEOS type tag for ``name``. + + Robust to ``pymeos_cffi`` not yet wrapping a valid, compiled MEOS type + tag in its ``MeosType`` enum: prefer the exposed enum member, else read + the constant from the compiled binding (``_meos_cffi.lib`` — the very + source ``pymeos_cffi`` builds its enums from), else ``None`` so the + mapping is skipped and importing never fails. ``MeosType`` is an + ``IntEnum``, so an ``int`` key matches ``inner.temptype`` lookups + identically to an enum-keyed one. + """ + member = getattr(MeosType, name, None) + if member is not None: + return member + if _meos_lib is not None and hasattr(_meos_lib, name): + return int(getattr(_meos_lib, name)) + return None + class _TemporalFactory: """ @@ -134,3 +173,32 @@ def create_collection(inner): if hasattr(inner, attribute) ) return _CollectionFactory._mapper[collection_type](_inner=inner) + + +# Extended temporal type families (cbuffer, npoint, pose, rgeo) are full +# user-facing temporal types and are wired exactly like every other type. +# Registered post-hoc through `_meos_type` so a build whose `pymeos_cffi` +# does not yet wrap these (valid, compiled) tags in its `MeosType` enum +# still imports cleanly and activates them automatically. +for _tag, _inst, _seq, _seqset in ( + ("T_TNPOINT", TNpointInst, TNpointSeq, TNpointSeqSet), + ("T_TCBUFFER", TCbufferInst, TCbufferSeq, TCbufferSeqSet), + ("T_TPOSE", TPoseInst, TPoseSeq, TPoseSeqSet), + ("T_TRGEOMETRY", TRgeometryInst, TRgeometrySeq, TRgeometrySeqSet), +): + _k = _meos_type(_tag) + if _k is not None: + _TemporalFactory._mapper[(_k, MeosTemporalSubtype.INSTANT)] = _inst + _TemporalFactory._mapper[(_k, MeosTemporalSubtype.SEQUENCE)] = _seq + _TemporalFactory._mapper[(_k, MeosTemporalSubtype.SEQUENCE_SET)] = ( + _seqset + ) + +for _tag, _cls in ( + ("T_NPOINTSET", NpointSet), + ("T_CBUFFERSET", CbufferSet), + ("T_POSESET", PoseSet), +): + _k = _meos_type(_tag) + if _k is not None: + _CollectionFactory._mapper[_k] = _cls diff --git a/pymeos/main/__init__.py b/pymeos/main/__init__.py index b6517465..b18d0795 100644 --- a/pymeos/main/__init__.py +++ b/pymeos/main/__init__.py @@ -17,6 +17,15 @@ TGeogPointSeqSet, ) from .ttext import TText, TTextInst, TTextSeq, TTextSeqSet +from .tnpoint import TNpoint, TNpointInst, TNpointSeq, TNpointSeqSet +from .tcbuffer import TCbuffer, TCbufferInst, TCbufferSeq, TCbufferSeqSet +from .tpose import TPose, TPoseInst, TPoseSeq, TPoseSeqSet +from .trgeometry import ( + TRgeometry, + TRgeometryInst, + TRgeometrySeq, + TRgeometrySeqSet, +) __all__ = [ "TBool", @@ -48,4 +57,20 @@ "TGeogPointInst", "TGeogPointSeq", "TGeogPointSeqSet", + "TNpoint", + "TNpointInst", + "TNpointSeq", + "TNpointSeqSet", + "TCbuffer", + "TCbufferInst", + "TCbufferSeq", + "TCbufferSeqSet", + "TPose", + "TPoseInst", + "TPoseSeq", + "TPoseSeqSet", + "TRgeometry", + "TRgeometryInst", + "TRgeometrySeq", + "TRgeometrySeqSet", ] diff --git a/pymeos/main/tcbuffer.py b/pymeos/main/tcbuffer.py new file mode 100644 index 00000000..35e1ee2b --- /dev/null +++ b/pymeos/main/tcbuffer.py @@ -0,0 +1,1094 @@ +from __future__ import annotations + +from abc import ABC +from typing import Optional, List, Union, TYPE_CHECKING, Set, TypeVar + +import shapely.geometry.base as shpb +from pymeos_cffi import * + +from .tfloat import TFloat +from .tpoint import TPoint +from ..collections import * +from ..collections.cbuffer import Cbuffer, CbufferSet +from ..mixins import TTemporallyComparable +from ..temporal import Temporal, TInstant, TSequence, TSequenceSet, TInterpolation + +if TYPE_CHECKING: + from .tbool import TBool + from ..boxes import STBox + +Self = TypeVar("Self", bound="TCbuffer") + + +class TCbuffer( + Temporal[Cbuffer, "TCbuffer", "TCbufferInst", "TCbufferSeq", "TCbufferSeqSet"], + TTemporallyComparable, + ABC, +): + """ + Abstract class for temporal circular buffers. + """ + + _mobilitydb_name = "tcbuffer" + + BaseClass = Cbuffer + + _parse_function = tcbuffer_in + + def __init__(self, _inner) -> None: + super().__init__() + + # ------------------------- Constructors ---------------------------------- + @staticmethod + def from_point_radius(point: TPoint, radius: TFloat) -> TCbuffer: + """ + Create a temporal circular buffer from a temporal point and a temporal + float representing the radius. + + Args: + point: A :class:`TPoint` with the center. + radius: A :class:`TFloat` with the radius. + + Returns: + A new :class:`TCbuffer` object. + + MEOS Functions: + tcbuffer_make + """ + result = tcbuffer_make(point._inner, radius._inner) + return Temporal._factory(result) + + # ------------------------- Output ---------------------------------------- + def __str__(self): + """ + Returns the string representation of `self`. + + Returns: + A :class:`str` with the string representation of `self`. + + MEOS Functions: + tspatial_as_text + """ + return tspatial_as_text(self._inner, 15) + + def as_wkt(self, precision: int = 15) -> str: + """ + Returns the temporal circular buffer as a WKT string. + + Args: + precision: The precision of the returned geometry. + + Returns: + A new :class:`str` representing the temporal circular buffer. + + MEOS Functions: + tspatial_as_text + """ + return tspatial_as_text(self._inner, precision) + + def as_ewkt(self, precision: int = 15) -> str: + """ + Returns the temporal circular buffer as an EWKT string. + + Args: + precision: The precision of the returned geometry. + + Returns: + A new :class:`str` representing the temporal circular buffer. + + MEOS Functions: + tspatial_as_ewkt + """ + return tspatial_as_ewkt(self._inner, precision) + + # ------------------------- Conversions ----------------------------------- + def to_tfloat(self) -> TFloat: + """ + Returns the temporal float of the radii of `self`. + + Returns: + A new :class:`TFloat` object. + + MEOS Functions: + tcbuffer_to_tfloat + """ + result = tcbuffer_to_tfloat(self._inner) + return Temporal._factory(result) + + def to_tgeompoint(self) -> TPoint: + """ + Returns the temporal geometry point of the centers of `self`. + + Returns: + A new :class:`TGeomPoint` object. + + MEOS Functions: + tcbuffer_to_tgeompoint + """ + result = tcbuffer_to_tgeompoint(self._inner) + return Temporal._factory(result) + + # ------------------------- Accessors ------------------------------------- + def bounding_box(self) -> STBox: + """ + Returns the bounding box of `self`. + + Returns: + An :class:`~pymeos.boxes.STBox` representing the bounding box. + + MEOS Functions: + tspatial_to_stbox + """ + from ..boxes import STBox + + return STBox(_inner=tspatial_to_stbox(self._inner)) + + def radius(self) -> TFloat: + """ + Returns the radius of `self` as a temporal float. + + Returns: + A new :class:`TFloat` with the radius. + + MEOS Functions: + tcbuffer_radius + """ + result = tcbuffer_radius(self._inner) + return Temporal._factory(result) + + def points(self) -> CbufferSet: + """ + Returns the set of points of `self`. + + Returns: + A :class:`CbufferSet` with the points. + + MEOS Functions: + tcbuffer_points + """ + return CbufferSet(_inner=tcbuffer_points(self._inner)) + + def traversed_area(self) -> shpb.BaseGeometry: + """ + Returns the traversed area of `self` as a `shapely` + :class:`~shapely.geometry.base.BaseGeometry`. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` representing the + traversed area. + + MEOS Functions: + tcbuffer_trav_area + """ + return gserialized_to_shapely_geometry(tcbuffer_trav_area(self._inner), 15) + + # ------------------------- Spatial Reference System ---------------------- + def srid(self) -> int: + """ + Returns the SRID of `self`. + + Returns: + An :class:`int` representing the SRID. + + MEOS Functions: + tspatial_srid + """ + return tspatial_srid(self._inner) + + def set_srid(self: Self, srid: int) -> Self: + """ + Returns a new :class:`TCbuffer` with the given SRID. + + Args: + srid: The desired SRID. + + Returns: + A new :class:`TCbuffer` instance. + + MEOS Functions: + tspatial_set_srid + """ + return self.__class__(_inner=tspatial_set_srid(self._inner, srid)) + + # ------------------------- Transformations ------------------------------- + def round(self, max_decimals: int = 0) -> TCbuffer: + """ + Round the coordinate values to a number of decimal places. + + Returns: + A new :class:`TCbuffer` object. + + MEOS Functions: + temporal_round + """ + result = temporal_round(self._inner, max_decimals) + return Temporal._factory(result) + + def transform(self: Self, srid: int) -> Self: + """ + Returns a new :class:`TCbuffer` transformed to another SRID. + + Args: + srid: The desired SRID. + + Returns: + A new :class:`TCbuffer` instance. + + MEOS Functions: + tspatial_transform + """ + result = tspatial_transform(self._inner, srid) + return Temporal._factory(result) + + # ------------------------- Value Accessors ------------------------------- + # MEOS exposes NO typed value accessor or value-based constructor for the + # temporal circular buffer: there is no tcbuffer_start_value/end_value/ + # value_set/value_at_timestamp, no tcbufferinst_make, and no + # tcbuffer_from_base_*; the only generic accessor returns an opaque Datum + # that cannot be rebuilt into a Cbuffer without Datum-hiding internals. + # These overrides keep the class concrete and fail loudly instead of + # making the whole type uninstantiable. from_mfjson is being added + # upstream by MobilityDB#1051 (feat/tcbuffer-mfjson); the others remain + # unimplemented in MEOS and are a separate upstream feature. + def start_value(self) -> Cbuffer: + """Not exposed by MEOS for the temporal circular buffer.""" + raise NotImplementedError( + "MEOS exposes no typed start_value for the temporal circular " + "buffer (no tcbuffer_start_value); pending upstream." + ) + + def end_value(self) -> Cbuffer: + """Not exposed by MEOS for the temporal circular buffer.""" + raise NotImplementedError( + "MEOS exposes no typed end_value for the temporal circular " + "buffer (no tcbuffer_end_value); pending upstream." + ) + + def value_set(self) -> Set[Cbuffer]: + """Not exposed by MEOS for the temporal circular buffer.""" + raise NotImplementedError( + "MEOS exposes no typed value_set for the temporal circular " + "buffer (no tcbuffer_values); pending upstream." + ) + + def value_at_timestamp(self, timestamp: datetime) -> Cbuffer: + """Not exposed by MEOS for the temporal circular buffer.""" + raise NotImplementedError( + "MEOS exposes no typed value_at_timestamp for the temporal " + "circular buffer (no tcbuffer_value_at_timestamp); pending " + "upstream." + ) + + @staticmethod + def from_base_time(value: Cbuffer, base: Time) -> TCbuffer: + """Not exposed by MEOS for the temporal circular buffer.""" + raise NotImplementedError( + "MEOS exposes no value-based constructor for the temporal " + "circular buffer (no tcbufferinst_make / tcbuffer_from_base_*); " + "pending upstream." + ) + + @classmethod + def from_mfjson(cls, mfjson: str) -> TCbuffer: + """MF-JSON input is being added upstream (MobilityDB#1051).""" + raise NotImplementedError( + "MEOS MF-JSON input for the temporal circular buffer is being " + "added upstream by MobilityDB#1051 (feat/tcbuffer-mfjson)." + ) + + # ------------------------- Ever and Always Comparisons ------------------- + def always_equal(self, value: Union[Cbuffer, TCbuffer]) -> bool: + """ + Returns whether the values of `self` are always equal to `value`. + + Args: + value: :class:`Cbuffer` or :class:`TCbuffer` to compare. + + Returns: + `True` if the values of `self` are always equal to `value`, + `False` otherwise. + + MEOS Functions: + always_eq_tcbuffer_cbuffer, always_eq_tcbuffer_tcbuffer + """ + if isinstance(value, Cbuffer): + return always_eq_tcbuffer_cbuffer(self._inner, value._inner) > 0 + elif isinstance(value, TCbuffer): + return always_eq_tcbuffer_tcbuffer(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def always_not_equal(self, value: Union[Cbuffer, TCbuffer]) -> bool: + """ + Returns whether the values of `self` are always not equal to `value`. + + Args: + value: :class:`Cbuffer` or :class:`TCbuffer` to compare. + + Returns: + `True` if the values of `self` are always not equal to `value`, + `False` otherwise. + + MEOS Functions: + always_ne_tcbuffer_cbuffer, always_ne_tcbuffer_tcbuffer + """ + if isinstance(value, Cbuffer): + return always_ne_tcbuffer_cbuffer(self._inner, value._inner) > 0 + elif isinstance(value, TCbuffer): + return always_ne_tcbuffer_tcbuffer(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def ever_equal(self, value: Union[Cbuffer, TCbuffer]) -> bool: + """ + Returns whether the values of `self` are ever equal to `value`. + + Args: + value: :class:`Cbuffer` or :class:`TCbuffer` to compare. + + Returns: + `True` if the values of `self` are ever equal to `value`, `False` + otherwise. + + MEOS Functions: + ever_eq_tcbuffer_cbuffer, ever_eq_tcbuffer_tcbuffer + """ + if isinstance(value, Cbuffer): + return ever_eq_tcbuffer_cbuffer(self._inner, value._inner) > 0 + elif isinstance(value, TCbuffer): + return ever_eq_tcbuffer_tcbuffer(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def ever_not_equal(self, value: Union[Cbuffer, TCbuffer]) -> bool: + """ + Returns whether the values of `self` are ever not equal to `value`. + + Args: + value: :class:`Cbuffer` or :class:`TCbuffer` to compare. + + Returns: + `True` if the values of `self` are ever not equal to `value`, + `False` otherwise. + + MEOS Functions: + ever_ne_tcbuffer_cbuffer, ever_ne_tcbuffer_tcbuffer + """ + if isinstance(value, Cbuffer): + return ever_ne_tcbuffer_cbuffer(self._inner, value._inner) > 0 + elif isinstance(value, TCbuffer): + return ever_ne_tcbuffer_tcbuffer(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def never_equal(self, value: Union[Cbuffer, TCbuffer]) -> bool: + """ + Returns whether the values of `self` are never equal to `value`. + + Args: + value: :class:`Cbuffer` or :class:`TCbuffer` to compare. + + Returns: + `True` if the values of `self` are never equal to `value`, `False` + otherwise. + + MEOS Functions: + ever_eq_tcbuffer_cbuffer, ever_eq_tcbuffer_tcbuffer + """ + return not self.ever_equal(value) + + def never_not_equal(self, value: Union[Cbuffer, TCbuffer]) -> bool: + """ + Returns whether the values of `self` are never not equal to `value`. + + Args: + value: :class:`Cbuffer` or :class:`TCbuffer` to compare. + + Returns: + `True` if the values of `self` are never not equal to `value`, + `False` otherwise. + + MEOS Functions: + ever_ne_tcbuffer_cbuffer, ever_ne_tcbuffer_tcbuffer + """ + return not self.ever_not_equal(value) + + # ------------------------- Temporal Comparisons -------------------------- + def temporal_equal(self, other: Union[Cbuffer, TCbuffer]) -> TBool: + """ + Returns the temporal equality relation between `self` and `other`. + + Args: + other: A :class:`Cbuffer` or temporal object to compare to `self`. + + Returns: + A :class:`TBool` with the result of the temporal equality relation. + + MEOS Functions: + teq_tcbuffer_cbuffer, teq_temporal_temporal + """ + if isinstance(other, Cbuffer): + result = teq_tcbuffer_cbuffer(self._inner, other._inner) + else: + return super().temporal_equal(other) + return Temporal._factory(result) + + def temporal_not_equal(self, other: Union[Cbuffer, TCbuffer]) -> TBool: + """ + Returns the temporal not equal relation between `self` and `other`. + + Args: + other: A :class:`Cbuffer` or temporal object to compare to `self`. + + Returns: + A :class:`TBool` with the result of the temporal not equal + relation. + + MEOS Functions: + tne_tcbuffer_cbuffer, tne_temporal_temporal + """ + if isinstance(other, Cbuffer): + result = tne_tcbuffer_cbuffer(self._inner, other._inner) + else: + return super().temporal_not_equal(other) + return Temporal._factory(result) + + # ------------------------- Restrictions ---------------------------------- + def at(self, other: Union[Cbuffer, shpb.BaseGeometry, STBox, Time]) -> TCbuffer: + """ + Returns a new temporal circular buffer with the values of `self` + restricted to `other`. + + Args: + other: An object to restrict the values of `self` to. + + Returns: + A new :class:`TCbuffer` with the values of `self` restricted to + `other`. + + MEOS Functions: + tcbuffer_at_cbuffer, tcbuffer_at_geom, tcbuffer_at_stbox, + temporal_at_timestamp, temporal_at_tstzset, temporal_at_tstzspan, + temporal_at_tstzspanset + """ + from ..boxes import STBox + + if isinstance(other, Cbuffer): + result = tcbuffer_at_cbuffer(self._inner, other._inner) + elif isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, False) + result = tcbuffer_at_geom(self._inner, gs) + elif isinstance(other, STBox): + result = tcbuffer_at_stbox(self._inner, other._inner, True) + else: + return super().at(other) + return Temporal._factory(result) + + def minus(self, other: Union[Cbuffer, shpb.BaseGeometry, STBox, Time]) -> TCbuffer: + """ + Returns a new temporal circular buffer with the values of `self` + restricted to the complement of `other`. + + Args: + other: An object to restrict the values of `self` to the + complement of. + + Returns: + A new :class:`TCbuffer` with the values of `self` restricted to + the complement of `other`. + + MEOS Functions: + tcbuffer_minus_cbuffer, tcbuffer_minus_geom, tcbuffer_minus_stbox, + temporal_minus_timestamp, temporal_minus_tstzset, + temporal_minus_tstzspan, temporal_minus_tstzspanset + """ + from ..boxes import STBox + + if isinstance(other, Cbuffer): + result = tcbuffer_minus_cbuffer(self._inner, other._inner) + elif isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, False) + result = tcbuffer_minus_geom(self._inner, gs) + elif isinstance(other, STBox): + result = tcbuffer_minus_stbox(self._inner, other._inner, True) + else: + return super().minus(other) + return Temporal._factory(result) + + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer]) -> TFloat: + """ + Returns the temporal distance between `self` and `other`. + + Args: + other: An object to check the distance to. + + Returns: + A new :class:`TFloat` with the temporal distance. + + MEOS Functions: + tdistance_tcbuffer_geo, tdistance_tcbuffer_cbuffer, + tdistance_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, False) + result = tdistance_tcbuffer_geo(self._inner, gs) + elif isinstance(other, Cbuffer): + result = tdistance_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = tdistance_tcbuffer_tcbuffer(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[shpb.BaseGeometry, Cbuffer, STBox, TCbuffer] + ) -> float: + """ + Returns the nearest approach distance between `self` and `other`. + + Args: + other: An object to check the nearest approach distance to. + + Returns: + A :class:`float` with the nearest approach distance. + + MEOS Functions: + nad_tcbuffer_geo, nad_tcbuffer_cbuffer, nad_tcbuffer_stbox, + nad_tcbuffer_tcbuffer + """ + from ..boxes import STBox + + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, False) + return nad_tcbuffer_geo(self._inner, gs) + elif isinstance(other, Cbuffer): + return nad_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, STBox): + return nad_tcbuffer_stbox(self._inner, other._inner) + elif isinstance(other, TCbuffer): + return nad_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + + def nearest_approach_instant( + self, other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer] + ) -> TCbufferInst: + """ + Returns the nearest approach instant between `self` and `other`. + + Args: + other: An object to check the nearest approach instant to. + + Returns: + A new :class:`TCbufferInst` with the nearest approach instant. + + MEOS Functions: + nai_tcbuffer_geo, nai_tcbuffer_cbuffer, nai_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, False) + result = nai_tcbuffer_geo(self._inner, gs) + elif isinstance(other, Cbuffer): + result = nai_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = nai_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return Temporal._factory(result) + + def shortest_line( + self, other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer] + ) -> shpb.BaseGeometry: + """ + Returns the shortest line between `self` and `other`. + + Args: + other: An object to check the shortest line to. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` with the + shortest line. + + MEOS Functions: + shortestline_tcbuffer_geo, shortestline_tcbuffer_cbuffer, + shortestline_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, False) + result = shortestline_tcbuffer_geo(self._inner, gs) + elif isinstance(other, Cbuffer): + result = shortestline_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = shortestline_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return gserialized_to_shapely_geometry(result, 10) + + # ------------------------- Ever Spatial Relationships -------------------- + def is_ever_contains(self, other: Union[shpb.BaseGeometry, Cbuffer]) -> bool: + """ + Returns whether `self` ever contains `other`. + + MEOS Functions: + econtains_tcbuffer_geo, econtains_tcbuffer_cbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = econtains_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False) + ) + elif isinstance(other, Cbuffer): + result = econtains_tcbuffer_cbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def is_ever_covers( + self, other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer] + ) -> bool: + """ + Returns whether `self` ever covers `other`. + + MEOS Functions: + ecovers_tcbuffer_geo, ecovers_tcbuffer_cbuffer, + ecovers_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = ecovers_tcbuffer_geo(self._inner, geo_to_gserialized(other, False)) + elif isinstance(other, Cbuffer): + result = ecovers_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = ecovers_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def is_ever_disjoint(self, other: Union[shpb.BaseGeometry, Cbuffer]) -> bool: + """ + Returns whether `self` is ever disjoint from `other`. + + MEOS Functions: + edisjoint_tcbuffer_geo, edisjoint_tcbuffer_cbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = edisjoint_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False) + ) + elif isinstance(other, Cbuffer): + result = edisjoint_tcbuffer_cbuffer(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[shpb.BaseGeometry, Cbuffer, TCbuffer], + distance: float, + ) -> bool: + """ + Returns whether `self` is ever within `distance` of `other`. + + MEOS Functions: + edwithin_tcbuffer_geo, edwithin_tcbuffer_cbuffer, + edwithin_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = edwithin_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False), distance + ) + elif isinstance(other, Cbuffer): + result = edwithin_tcbuffer_cbuffer(self._inner, other._inner, distance) + elif isinstance(other, TCbuffer): + result = edwithin_tcbuffer_tcbuffer(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[shpb.BaseGeometry, Cbuffer, TCbuffer] + ) -> bool: + """ + Returns whether `self` ever intersects `other`. + + MEOS Functions: + eintersects_tcbuffer_geo, eintersects_tcbuffer_cbuffer, + eintersects_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = eintersects_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False) + ) + elif isinstance(other, Cbuffer): + result = eintersects_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = eintersects_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def ever_touches(self, other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer]) -> bool: + """ + Returns whether `self` ever touches `other`. + + MEOS Functions: + etouches_tcbuffer_geo, etouches_tcbuffer_cbuffer, + etouches_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = etouches_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False) + ) + elif isinstance(other, Cbuffer): + result = etouches_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = etouches_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + # ------------------------- Always Spatial Relationships ------------------ + def is_always_contains(self, other: Union[shpb.BaseGeometry, Cbuffer]) -> bool: + """ + Returns whether `self` always contains `other`. + + MEOS Functions: + acontains_tcbuffer_geo, acontains_tcbuffer_cbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = acontains_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False) + ) + elif isinstance(other, Cbuffer): + result = acontains_tcbuffer_cbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def is_always_covers(self, other: Union[shpb.BaseGeometry, Cbuffer]) -> bool: + """ + Returns whether `self` always covers `other`. + + MEOS Functions: + acovers_tcbuffer_geo, acovers_tcbuffer_cbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = acovers_tcbuffer_geo(self._inner, geo_to_gserialized(other, False)) + elif isinstance(other, Cbuffer): + result = acovers_tcbuffer_cbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def is_always_disjoint( + self, other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer] + ) -> bool: + """ + Returns whether `self` is always disjoint from `other`. + + MEOS Functions: + adisjoint_tcbuffer_geo, adisjoint_tcbuffer_cbuffer, + adisjoint_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = adisjoint_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False) + ) + elif isinstance(other, Cbuffer): + result = adisjoint_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = adisjoint_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def is_always_within_distance( + self, + other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer], + distance: float, + ) -> bool: + """ + Returns whether `self` is always within `distance` of `other`. + + MEOS Functions: + adwithin_tcbuffer_geo, adwithin_tcbuffer_cbuffer, + adwithin_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = adwithin_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False), distance + ) + elif isinstance(other, Cbuffer): + result = adwithin_tcbuffer_cbuffer(self._inner, other._inner, distance) + elif isinstance(other, TCbuffer): + result = adwithin_tcbuffer_tcbuffer(self._inner, other._inner, distance) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def always_intersects( + self, other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer] + ) -> bool: + """ + Returns whether `self` always intersects `other`. + + MEOS Functions: + aintersects_tcbuffer_geo, aintersects_tcbuffer_cbuffer, + aintersects_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = aintersects_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False) + ) + elif isinstance(other, Cbuffer): + result = aintersects_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = aintersects_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def always_touches( + self, other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer] + ) -> bool: + """ + Returns whether `self` always touches `other`. + + MEOS Functions: + atouches_tcbuffer_geo, atouches_tcbuffer_cbuffer, + atouches_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = atouches_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False) + ) + elif isinstance(other, Cbuffer): + result = atouches_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = atouches_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + # ------------------------- Temporal Spatial Relationships ---------------- + def contains(self, other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer]) -> TBool: + """ + Returns a temporal boolean of whether `self` contains `other`. + + MEOS Functions: + tcontains_tcbuffer_geo, tcontains_tcbuffer_cbuffer, + tcontains_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = tcontains_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False) + ) + elif isinstance(other, Cbuffer): + result = tcontains_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = tcontains_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return Temporal._factory(result) + + def covers(self, other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer]) -> TBool: + """ + Returns a temporal boolean of whether `self` covers `other`. + + MEOS Functions: + tcovers_tcbuffer_geo, tcovers_tcbuffer_cbuffer, + tcovers_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = tcovers_tcbuffer_geo(self._inner, geo_to_gserialized(other, False)) + elif isinstance(other, Cbuffer): + result = tcovers_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = tcovers_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return Temporal._factory(result) + + def disjoint(self, other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer]) -> TBool: + """ + Returns a temporal boolean of whether `self` is disjoint from `other`. + + MEOS Functions: + tdisjoint_tcbuffer_geo, tdisjoint_tcbuffer_cbuffer, + tdisjoint_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = tdisjoint_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False) + ) + elif isinstance(other, Cbuffer): + result = tdisjoint_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = tdisjoint_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return Temporal._factory(result) + + def within_distance( + self, + other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer], + distance: float, + ) -> TBool: + """ + Returns a temporal boolean of whether `self` is within `distance` of + `other`. + + MEOS Functions: + tdwithin_tcbuffer_geo, tdwithin_tcbuffer_cbuffer, + tdwithin_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = tdwithin_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False), distance + ) + elif isinstance(other, Cbuffer): + result = tdwithin_tcbuffer_cbuffer(self._inner, other._inner, distance) + elif isinstance(other, TCbuffer): + result = tdwithin_tcbuffer_tcbuffer(self._inner, other._inner, distance) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return Temporal._factory(result) + + def intersects(self, other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer]) -> TBool: + """ + Returns a temporal boolean of whether `self` intersects `other`. + + MEOS Functions: + tintersects_tcbuffer_geo, tintersects_tcbuffer_cbuffer, + tintersects_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = tintersects_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False) + ) + elif isinstance(other, Cbuffer): + result = tintersects_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = tintersects_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return Temporal._factory(result) + + def touches(self, other: Union[shpb.BaseGeometry, Cbuffer, TCbuffer]) -> TBool: + """ + Returns a temporal boolean of whether `self` touches `other`. + + MEOS Functions: + ttouches_tcbuffer_geo, ttouches_tcbuffer_cbuffer, + ttouches_tcbuffer_tcbuffer + """ + if isinstance(other, shpb.BaseGeometry): + result = ttouches_tcbuffer_geo( + self._inner, geo_to_gserialized(other, False) + ) + elif isinstance(other, Cbuffer): + result = ttouches_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = ttouches_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return Temporal._factory(result) + + # ------------------------- Database Operations --------------------------- + @staticmethod + def read_from_cursor(value, _=None): + """ + Reads a :class:`TCbuffer` 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 TCbufferInst(string=value) + elif value[0] == "[" or value[0] == "(": + return TCbufferSeq(string=value) + elif value[0] == "{": + if value[1] == "[" or value[1] == "(": + return TCbufferSeqSet(string=value) + else: + return TCbufferSeq(string=value) + raise Exception("ERROR: Could not parse temporal circular buffer value") + + +class TCbufferInst( + TInstant[Cbuffer, "TCbuffer", "TCbufferInst", "TCbufferSeq", "TCbufferSeqSet"], + TCbuffer, +): + """ + Class for representing temporal circular buffers at a single instant. + """ + + _make_function = lambda *args: None + _cast_function = lambda x: None + + def __init__( + self, + string: Optional[str] = None, + *, + _inner=None, + ) -> None: + super().__init__(string=string, _inner=_inner) + + +class TCbufferSeq( + TSequence[Cbuffer, "TCbuffer", "TCbufferInst", "TCbufferSeq", "TCbufferSeqSet"], + TCbuffer, +): + """ + Class for representing temporal circular buffers over a tstzspan of time. + """ + + ComponentClass = TCbufferInst + + def __init__( + self, + string: Optional[str] = None, + *, + instant_list: Optional[List[Union[str, TCbufferInst]]] = None, + lower_inc: bool = True, + upper_inc: bool = 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, + interpolation=interpolation, + normalize=normalize, + _inner=_inner, + ) + + +class TCbufferSeqSet( + TSequenceSet[Cbuffer, "TCbuffer", "TCbufferInst", "TCbufferSeq", "TCbufferSeqSet"], + TCbuffer, +): + """ + Class for representing temporal circular buffers over a tstzspan of time + with gaps. + """ + + ComponentClass = TCbufferSeq + + def __init__( + self, + string: Optional[str] = None, + *, + sequence_list: Optional[List[Union[str, TCbufferSeq]]] = None, + normalize: bool = True, + _inner=None, + ): + super().__init__( + string=string, + sequence_list=sequence_list, + normalize=normalize, + _inner=_inner, + ) diff --git a/pymeos/main/tnpoint.py b/pymeos/main/tnpoint.py new file mode 100644 index 00000000..f04a5b33 --- /dev/null +++ b/pymeos/main/tnpoint.py @@ -0,0 +1,700 @@ +from __future__ import annotations + +from abc import ABC +from typing import Optional, Union, List, TYPE_CHECKING, TypeVar + +import shapely.geometry.base as shp +from pymeos_cffi import * + +from ..collections import * +from ..collections.npoint import Npoint, NpointSet, Nsegment +from ..mixins import TTemporallyComparable +from ..temporal import TInterpolation, Temporal, TInstant, TSequence, TSequenceSet + +if TYPE_CHECKING: + from .tbool import TBool + from .tfloat import TFloat + from .tpoint import TGeomPoint + from ..boxes import STBox + + +Self = TypeVar("Self", bound="TNpoint") + + +class TNpoint( + Temporal[Npoint, "TNpoint", "TNpointInst", "TNpointSeq", "TNpointSeqSet"], + TTemporallyComparable, + ABC, +): + """ + Abstract class for temporal network points. + """ + + _mobilitydb_name = "tnpoint" + + BaseClass = Npoint + + _parse_function = tnpoint_in + + def __init__(self, _inner) -> None: + super().__init__() + + # ------------------------- Constructors ---------------------------------- + + # ------------------------- Output ---------------------------------------- + def __str__(self, max_decimals: int = 15) -> str: + """ + Returns the string representation of `self`. + + Returns: + A string with the string representation of `self`. + + MEOS Functions: + tnpoint_out + """ + return tnpoint_out(self._inner, max_decimals) + + def as_wkt(self, max_decimals: int = 15) -> str: + """ + Returns the Well-Known Text representation of `self`. + + Returns: + A string with the Well-Known Text representation of `self`. + + MEOS Functions: + tnpoint_out + """ + return tnpoint_out(self._inner, max_decimals) + + # ------------------------- Conversions ----------------------------------- + def to_tgeompoint(self) -> TGeomPoint: + """ + Returns a temporal geometric point equivalent to `self`. + + Returns: + A new :class:`TGeomPoint` instance. + + MEOS Functions: + tnpoint_to_tgeompoint + """ + result = tnpoint_to_tgeompoint(self._inner) + return Temporal._factory(result) + + @staticmethod + def from_tgeompoint(temporal: TGeomPoint) -> TNpoint: + """ + Returns a temporal network point equivalent to a temporal geometric + point. + + Args: + temporal: A :class:`TGeomPoint` instance. + + Returns: + A new :class:`TNpoint` instance. + + MEOS Functions: + tgeompoint_to_tnpoint + """ + result = tgeompoint_to_tnpoint(temporal._inner) + return Temporal._factory(result) + + # ------------------------- Accessors ------------------------------------- + def bounding_box(self) -> STBox: + """ + Returns the bounding box of `self`. + + Returns: + A new :class:`STBox` instance. + + MEOS Functions: + tspatial_to_stbox + """ + from ..boxes import STBox + + return STBox(_inner=tspatial_to_stbox(self._inner)) + + def route(self) -> int: + """ + Returns the route identifier of `self`. Only defined when `self` stays + on a single route. + + Returns: + An :class:`int` with the route identifier. + + MEOS Functions: + tnpoint_route + """ + return tnpoint_route(self._inner) + + def routes(self) -> Set[int]: + """ + Returns the set of route identifiers of `self`. + + Returns: + A :class:`Set` instance with the route identifiers. + + MEOS Functions: + tnpoint_routes + """ + from ..factory import _CollectionFactory + + return _CollectionFactory.create_collection(tnpoint_routes(self._inner)) + + def length(self) -> float: + """ + Returns the length of the trajectory of `self`. + + Returns: + A :class:`float` with the length. + + MEOS Functions: + tnpoint_length + """ + return tnpoint_length(self._inner) + + def cumulative_length(self) -> TFloat: + """ + Returns the cumulative length of the trajectory of `self`. + + Returns: + A new :class:`TFloat` instance. + + MEOS Functions: + tnpoint_cumulative_length + """ + result = tnpoint_cumulative_length(self._inner) + return Temporal._factory(result) + + def speed(self) -> TFloat: + """ + Returns the speed of `self`. + + Returns: + A new :class:`TFloat` instance. + + MEOS Functions: + tnpoint_speed + """ + result = tnpoint_speed(self._inner) + return Temporal._factory(result) + + def trajectory(self, precision: int = 15) -> shp.BaseGeometry: + """ + Returns the trajectory of `self`. + + Args: + precision: The number of decimal places to use for the coordinates. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` instance. + + MEOS Functions: + tnpoint_trajectory + """ + return gserialized_to_shapely_geometry( + tnpoint_trajectory(self._inner), precision + ) + + def time_weighted_centroid(self, precision: int = 15) -> shp.BaseGeometry: + """ + Returns the time weighted centroid of `self`. + + Args: + precision: The number of decimal places to use for the coordinates. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` instance. + + MEOS Functions: + tnpoint_twcentroid + """ + return gserialized_to_shapely_geometry( + tnpoint_twcentroid(self._inner), precision + ) + + def positions(self) -> List[Nsegment]: + """ + Returns the network segments covered by `self`. + + Returns: + A :class:`list` of :class:`Nsegment` with the positions. + + MEOS Functions: + tnpoint_positions + """ + nss, count = tnpoint_positions(self._inner) + return [Nsegment(_inner=nss[i]) for i in range(count)] + + # ------------------------- Spatial Reference System ---------------------- + def srid(self) -> int: + """ + Returns the SRID of `self`. + + Returns: + An :class:`int` with the SRID of `self`. + + MEOS Functions: + tspatial_srid + """ + return tspatial_srid(self._inner) + + def set_srid(self: Self, srid: int) -> Self: + """ + Returns a copy of `self` with the SRID set to `srid`. + + Args: + srid: The desired SRID. + + Returns: + A new :class:`TNpoint` instance. + + MEOS Functions: + tspatial_set_srid + """ + return self.__class__(_inner=tspatial_set_srid(self._inner, srid)) + + # ------------------------- Ever and Always Comparisons ------------------- + def always_equal(self, value: Union[Npoint, TNpoint]) -> bool: + """ + Returns whether the values of `self` are always equal to `value`. + + Args: + value: :class:`Npoint` or :class:`TNpoint` to compare. + + Returns: + `True` if the values of `self` are always equal to `value`, + `False` otherwise. + + MEOS Functions: + always_eq_tnpoint_npoint, always_eq_tnpoint_tnpoint + """ + if isinstance(value, Npoint): + return always_eq_tnpoint_npoint(self._inner, value._inner) > 0 + elif isinstance(value, TNpoint): + return always_eq_tnpoint_tnpoint(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def always_not_equal(self, value: Union[Npoint, TNpoint]) -> bool: + """ + Returns whether the values of `self` are always not equal to `value`. + + Args: + value: :class:`Npoint` or :class:`TNpoint` to compare. + + Returns: + `True` if the values of `self` are always not equal to `value`, + `False` otherwise. + + MEOS Functions: + always_ne_tnpoint_npoint, always_ne_tnpoint_tnpoint + """ + if isinstance(value, Npoint): + return always_ne_tnpoint_npoint(self._inner, value._inner) > 0 + elif isinstance(value, TNpoint): + return always_ne_tnpoint_tnpoint(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def ever_equal(self, value: Union[Npoint, TNpoint]) -> bool: + """ + Returns whether the values of `self` are ever equal to `value`. + + Args: + value: :class:`Npoint` or :class:`TNpoint` to compare. + + Returns: + `True` if the values of `self` are ever equal to `value`, `False` + otherwise. + + MEOS Functions: + ever_eq_tnpoint_npoint, ever_eq_tnpoint_tnpoint + """ + if isinstance(value, Npoint): + return ever_eq_tnpoint_npoint(self._inner, value._inner) > 0 + elif isinstance(value, TNpoint): + return ever_eq_tnpoint_tnpoint(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def ever_not_equal(self, value: Union[Npoint, TNpoint]) -> bool: + """ + Returns whether the values of `self` are ever not equal to `value`. + + Args: + value: :class:`Npoint` or :class:`TNpoint` to compare. + + Returns: + `True` if the values of `self` are ever not equal to `value`, + `False` otherwise. + + MEOS Functions: + ever_ne_tnpoint_npoint, ever_ne_tnpoint_tnpoint + """ + if isinstance(value, Npoint): + return ever_ne_tnpoint_npoint(self._inner, value._inner) > 0 + elif isinstance(value, TNpoint): + return ever_ne_tnpoint_tnpoint(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def never_equal(self, value: Union[Npoint, TNpoint]) -> bool: + """ + Returns whether the values of `self` are never equal to `value`. + + Args: + value: :class:`Npoint` or :class:`TNpoint` to compare. + + Returns: + `True` if the values of `self` are never equal to `value`, `False` + otherwise. + + MEOS Functions: + ever_eq_tnpoint_npoint, ever_eq_tnpoint_tnpoint + """ + return not self.ever_equal(value) + + def never_not_equal(self, value: Union[Npoint, TNpoint]) -> bool: + """ + Returns whether the values of `self` are never not equal to `value`. + + Args: + value: :class:`Npoint` or :class:`TNpoint` to compare. + + Returns: + `True` if the values of `self` are never not equal to `value`, + `False` otherwise. + + MEOS Functions: + ever_ne_tnpoint_npoint, ever_ne_tnpoint_tnpoint + """ + return not self.ever_not_equal(value) + + # ------------------------- Temporal Comparisons -------------------------- + def temporal_equal(self, other: Union[Npoint, TNpoint]) -> TBool: + """ + Returns the temporal equality relation between `self` and `other`. + + Args: + other: An :class:`Npoint` or temporal object to compare to `self`. + + Returns: + A :class:`TBool` with the result of the temporal equality relation. + + MEOS Functions: + teq_tnpoint_npoint, teq_temporal_temporal + """ + if isinstance(other, Npoint): + result = teq_tnpoint_npoint(self._inner, other._inner) + else: + return super().temporal_equal(other) + return Temporal._factory(result) + + def temporal_not_equal(self, other: Union[Npoint, TNpoint]) -> TBool: + """ + Returns the temporal not equal relation between `self` and `other`. + + Args: + other: An :class:`Npoint` or temporal object to compare to `self`. + + Returns: + A :class:`TBool` with the result of the temporal not equal + relation. + + MEOS Functions: + tne_tnpoint_npoint, tne_temporal_temporal + """ + if isinstance(other, Npoint): + result = tne_tnpoint_npoint(self._inner, other._inner) + else: + return super().temporal_not_equal(other) + return Temporal._factory(result) + + # ------------------------- Distance Operations --------------------------- + def distance( + self, other: Union[shp.BaseGeometry, Npoint, TNpoint] + ) -> TFloat: + """ + Returns the temporal distance between `self` and `other`. + + Args: + other: An object to check the distance to. + + Returns: + A new :class:`TFloat` instance. + + MEOS Functions: + tdistance_tnpoint_point, tdistance_tnpoint_npoint, + tdistance_tnpoint_tnpoint + """ + if isinstance(other, Npoint): + result = tdistance_tnpoint_npoint(self._inner, other._inner) + elif isinstance(other, shp.BaseGeometry): + gs = geometry_to_gserialized(other) + result = tdistance_tnpoint_point(self._inner, gs) + elif isinstance(other, TNpoint): + result = tdistance_tnpoint_tnpoint(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[shp.BaseGeometry, Npoint, STBox, TNpoint] + ) -> float: + """ + Returns the nearest approach distance between `self` and `other`. + + Args: + other: An object to check the nearest approach distance to. + + Returns: + A :class:`float` with the nearest approach distance. + + MEOS Functions: + nad_tnpoint_geo, nad_tnpoint_npoint, nad_tnpoint_stbox, + nad_tnpoint_tnpoint + """ + from ..boxes import STBox + + if isinstance(other, Npoint): + return nad_tnpoint_npoint(self._inner, other._inner) + elif isinstance(other, shp.BaseGeometry): + gs = geometry_to_gserialized(other) + return nad_tnpoint_geo(self._inner, gs) + elif isinstance(other, STBox): + return nad_tnpoint_stbox(self._inner, other._inner) + elif isinstance(other, TNpoint): + return nad_tnpoint_tnpoint(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + + def nearest_approach_instant( + self, other: Union[shp.BaseGeometry, Npoint, TNpoint] + ) -> TNpointInst: + """ + Returns the nearest approach instant between `self` and `other`. + + Args: + other: An object to check the nearest approach instant to. + + Returns: + A new :class:`TNpointInst` instance. + + MEOS Functions: + nai_tnpoint_geo, nai_tnpoint_npoint, nai_tnpoint_tnpoint + """ + if isinstance(other, Npoint): + result = nai_tnpoint_npoint(self._inner, other._inner) + elif isinstance(other, shp.BaseGeometry): + gs = geometry_to_gserialized(other) + result = nai_tnpoint_geo(self._inner, gs) + elif isinstance(other, TNpoint): + result = nai_tnpoint_tnpoint(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return Temporal._factory(result) + + def shortest_line( + self, other: Union[shp.BaseGeometry, Npoint, TNpoint], precision: int = 15 + ) -> shp.BaseGeometry: + """ + Returns the shortest line between `self` and `other`. + + Args: + other: An object to check the shortest line to. + precision: The number of decimal places to use for the coordinates. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` instance. + + MEOS Functions: + shortestline_tnpoint_geo, shortestline_tnpoint_npoint, + shortestline_tnpoint_tnpoint + """ + if isinstance(other, Npoint): + result = shortestline_tnpoint_npoint(self._inner, other._inner) + elif isinstance(other, shp.BaseGeometry): + gs = geometry_to_gserialized(other) + result = shortestline_tnpoint_geo(self._inner, gs) + elif isinstance(other, TNpoint): + result = shortestline_tnpoint_tnpoint(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return gserialized_to_shapely_geometry(result, precision) + + # ------------------------- Restrictions ---------------------------------- + def at( + self, other: Union[shp.BaseGeometry, Npoint, NpointSet, STBox, Time] + ) -> TNpoint: + """ + Returns a new temporal network point with the values of `self` + restricted to `other`. + + Args: + other: An object to restrict the values of `self` to. + + Returns: + A new :class:`TNpoint` instance. + + MEOS Functions: + tnpoint_at_geom, tnpoint_at_npoint, tnpoint_at_npointset, + tnpoint_at_stbox, temporal_at_timestamp, temporal_at_tstzset, + temporal_at_tstzspan, temporal_at_tstzspanset + """ + from ..boxes import STBox + + if isinstance(other, Npoint): + result = tnpoint_at_npoint(self._inner, other._inner) + elif isinstance(other, NpointSet): + result = tnpoint_at_npointset(self._inner, other._inner) + elif isinstance(other, shp.BaseGeometry): + gs = geometry_to_gserialized(other) + result = tnpoint_at_geom(self._inner, gs) + elif isinstance(other, STBox): + result = tnpoint_at_stbox(self._inner, other._inner, True) + else: + return super().at(other) + return Temporal._factory(result) + + def minus( + self, other: Union[shp.BaseGeometry, Npoint, NpointSet, STBox, Time] + ) -> TNpoint: + """ + Returns a new temporal network point with the values of `self` + restricted to the complement of `other`. + + Args: + other: An object to restrict the values of `self` to the + complement of. + + Returns: + A new :class:`TNpoint` instance. + + MEOS Functions: + tnpoint_minus_geom, tnpoint_minus_npoint, tnpoint_minus_npointset, + tnpoint_minus_stbox, temporal_minus_timestamp, + temporal_minus_tstzset, temporal_minus_tstzspan, + temporal_minus_tstzspanset + """ + from ..boxes import STBox + + if isinstance(other, Npoint): + result = tnpoint_minus_npoint(self._inner, other._inner) + elif isinstance(other, NpointSet): + result = tnpoint_minus_npointset(self._inner, other._inner) + elif isinstance(other, shp.BaseGeometry): + gs = geometry_to_gserialized(other) + result = tnpoint_minus_geom(self._inner, gs) + elif isinstance(other, STBox): + result = tnpoint_minus_stbox(self._inner, other._inner, True) + else: + return super().minus(other) + return Temporal._factory(result) + + # ------------------------- Database Operations --------------------------- + @staticmethod + def read_from_cursor(value, _=None): + """ + Reads a :class:`TNpoint` 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.startswith("Interp=Stepwise;"): + value1 = value.replace("Interp=Stepwise;", "") + if value1[0] == "{": + return TNpointSeqSet(string=value) + else: + return TNpointSeq(string=value) + elif value[0] != "{" and value[0] != "[" and value[0] != "(": + return TNpointInst(string=value) + elif value[0] == "[" or value[0] == "(": + return TNpointSeq(string=value) + elif value[0] == "{": + if value[1] == "[" or value[1] == "(": + return TNpointSeqSet(string=value) + else: + return TNpointSeq(string=value) + raise Exception("ERROR: Could not parse temporal network point value") + + +class TNpointInst( + TInstant[Npoint, "TNpoint", "TNpointInst", "TNpointSeq", "TNpointSeqSet"], + TNpoint, +): + """ + Class for representing temporal network points at a single instant. + """ + + _make_function = tnpointinst_make + _cast_function = lambda x: x._inner + + def __init__( + self, + string: Optional[str] = None, + *, + value: Optional[Union[str, Npoint]] = None, + timestamp: Optional[Union[str, datetime]] = None, + _inner=None, + ) -> None: + super().__init__(string=string, value=value, timestamp=timestamp, _inner=_inner) + + +class TNpointSeq( + TSequence[Npoint, "TNpoint", "TNpointInst", "TNpointSeq", "TNpointSeqSet"], + TNpoint, +): + """ + Class for representing temporal network points over a tstzspan of time. + """ + + ComponentClass = TNpointInst + + def __init__( + self, + string: Optional[str] = None, + *, + instant_list: Optional[List[Union[str, TNpointInst]]] = None, + lower_inc: bool = True, + upper_inc: bool = 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, + interpolation=interpolation, + normalize=normalize, + _inner=_inner, + ) + + +class TNpointSeqSet( + TSequenceSet[Npoint, "TNpoint", "TNpointInst", "TNpointSeq", "TNpointSeqSet"], + TNpoint, +): + """ + Class for representing temporal network points over a tstzspan of time + with gaps. + """ + + ComponentClass = TNpointSeq + + def __init__( + self, + string: Optional[str] = None, + *, + sequence_list: Optional[List[Union[str, TNpointSeq]]] = None, + normalize: bool = True, + _inner=None, + ): + super().__init__( + string=string, + sequence_list=sequence_list, + normalize=normalize, + _inner=_inner, + ) diff --git a/pymeos/main/tpose.py b/pymeos/main/tpose.py new file mode 100644 index 00000000..022678d7 --- /dev/null +++ b/pymeos/main/tpose.py @@ -0,0 +1,736 @@ +from __future__ import annotations + +from abc import ABC +from typing import Optional, List, Union, TYPE_CHECKING, Set, Type, TypeVar + +import shapely.geometry.base as shpb +from pymeos_cffi import * + +from .tbool import TBool +from .tfloat import TFloat +from .tpoint import TPoint +from ..collections import * +from ..collections.pose import Pose, PoseSet +from ..mixins import TTemporallyComparable +from ..temporal import Temporal, TInstant, TSequence, TSequenceSet, TInterpolation + +if TYPE_CHECKING: + from ..boxes import STBox + +Self = TypeVar("Self", bound="TPose") + + +class TPose( + Temporal[Pose, "TPose", "TPoseInst", "TPoseSeq", "TPoseSeqSet"], + TTemporallyComparable, + ABC, +): + """ + Abstract class for temporal poses. + """ + + _mobilitydb_name = "tpose" + + BaseClass = Pose + + _parse_function = tpose_in + + def __init__(self, _inner) -> None: + super().__init__() + + # ------------------------- Constructors ---------------------------------- + @staticmethod + def from_tpoint_tfloat(tpoint: TPoint, tradius: TFloat) -> TPose: + """ + Create a temporal pose from a temporal point and a temporal float + representing the orientation. + + Args: + tpoint: A :class:`TPoint` with the position. + tradius: A :class:`TFloat` with the orientation. + + Returns: + A new :class:`TPose` object. + + MEOS Functions: + tpose_make + """ + result = tpose_make(tpoint._inner, tradius._inner) + return Temporal._factory(result) + + # ------------------------- Output ---------------------------------------- + def __str__(self): + """ + Returns the string representation of `self`. + + Returns: + A :class:`str` with the string representation of `self`. + + MEOS Functions: + tspatial_as_text + """ + return tspatial_as_text(self._inner, 15) + + def as_wkt(self, precision: int = 15) -> str: + """ + Returns the temporal pose as a WKT string. + + Args: + precision: The precision of the returned geometry. + + Returns: + A new :class:`str` representing the temporal pose. + + MEOS Functions: + tspatial_as_text + """ + return tspatial_as_text(self._inner, precision) + + def as_ewkt(self, precision: int = 15) -> str: + """ + Returns the temporal pose as an EWKT string. + + Args: + precision: The precision of the returned geometry. + + Returns: + A new :class:`str` representing the temporal pose. + + MEOS Functions: + tspatial_as_ewkt + """ + return tspatial_as_ewkt(self._inner, precision) + + # ------------------------- Conversions ----------------------------------- + def to_tpoint(self) -> TPoint: + """ + Returns the temporal point of the positions of `self`. + + Returns: + A new :class:`TPoint` object. + + MEOS Functions: + tpose_to_tpoint + """ + result = tpose_to_tpoint(self._inner) + return Temporal._factory(result) + + # ------------------------- Accessors ------------------------------------- + def bounding_box(self) -> STBox: + """ + Returns the bounding box of `self`. + + Returns: + An :class:`~pymeos.boxes.STBox` representing the bounding box. + + MEOS Functions: + tspatial_to_stbox + """ + from ..boxes import STBox + + return STBox(_inner=tspatial_to_stbox(self._inner)) + + def values(self) -> List[Pose]: + """ + Returns the values of `self`. + + Returns: + A :class:`list` of :class:`Pose` with the values. + + MEOS Functions: + tpose_values + """ + values, count = tpose_values(self._inner) + return [Pose(_inner=values[i]) for i in range(count)] + + def start_value(self) -> Pose: + """ + Returns the start value of `self`. + + Returns: + A :class:`Pose` with the start value. + + MEOS Functions: + tpose_start_value + """ + return Pose(_inner=tpose_start_value(self._inner)) + + def end_value(self) -> Pose: + """ + Returns the end value of `self`. + + Returns: + A :class:`Pose` with the end value. + + MEOS Functions: + tpose_end_value + """ + return Pose(_inner=tpose_end_value(self._inner)) + + def value_set(self) -> Set[Pose]: + """ + Returns the set of values of `self`. + + Returns: + A :class:`set` of :class:`Pose` with the values. + + MEOS Functions: + tpose_values + """ + values, count = tpose_values(self._inner) + return {Pose(_inner=values[i]) for i in range(count)} + + def value_at_timestamp(self, timestamp: datetime) -> Pose: + """ + Returns the value of `self` at the given timestamp. + + Args: + timestamp: A :class:`datetime` representing the timestamp. + + Returns: + A :class:`Pose` with the value. + + MEOS Functions: + tpose_value_at_timestamptz + """ + return Pose( + _inner=tpose_value_at_timestamptz( + self._inner, datetime_to_timestamptz(timestamp), True + )[0] + ) + + def value_n(self, n: int) -> Pose: + """ + Returns the ``n``-th value of `self`. + + Args: + n: The 0-based index of the value to return. + + Returns: + A :class:`Pose` with the value. + + MEOS Functions: + tpose_value_n + """ + return Pose(_inner=tpose_value_n(self._inner, n + 1)[0]) + + def points(self) -> PoseSet: + """ + Returns the set of points of `self`. + + Returns: + A :class:`PoseSet` with the points. + + MEOS Functions: + tpose_points + """ + return PoseSet(_inner=tpose_points(self._inner)) + + def trajectory(self, precision: int = 15) -> shpb.BaseGeometry: + """ + Returns the trajectory of `self` as a `shapely` + :class:`~shapely.geometry.base.BaseGeometry`. + + Args: + precision: The precision of the returned geometry. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` representing the + trajectory. + + MEOS Functions: + tpose_trajectory + """ + return gserialized_to_shapely_geometry( + tpose_trajectory(self._inner), precision + ) + + def rotation(self) -> TFloat: + """ + Returns the rotation of `self` as a temporal float. + + Returns: + A new :class:`TFloat` with the rotation. + + MEOS Functions: + tpose_rotation + """ + result = tpose_rotation(self._inner) + return Temporal._factory(result) + + # ------------------------- Spatial Reference System ---------------------- + def srid(self) -> int: + """ + Returns the SRID of `self`. + + Returns: + An :class:`int` representing the SRID. + + MEOS Functions: + tspatial_srid + """ + return tspatial_srid(self._inner) + + def set_srid(self: Self, srid: int) -> Self: + """ + Returns a new :class:`TPose` with the given SRID. + + Args: + srid: The desired SRID. + + Returns: + A new :class:`TPose` instance. + + MEOS Functions: + tspatial_set_srid + """ + return self.__class__(_inner=tspatial_set_srid(self._inner, srid)) + + # ------------------------- Transformations ------------------------------- + def round(self, max_decimals: int = 0) -> TPose: + """ + Round the coordinate values to a number of decimal places. + + Returns: + A new :class:`TPose` object. + + MEOS Functions: + temporal_round + """ + result = temporal_round(self._inner, max_decimals) + return Temporal._factory(result) + + def transform(self: Self, srid: int) -> Self: + """ + Returns a new :class:`TPose` transformed to another SRID. + + Args: + srid: The desired SRID. + + Returns: + A new :class:`TPose` instance. + + MEOS Functions: + tspatial_transform + """ + result = tspatial_transform(self._inner, srid) + return Temporal._factory(result) + + # ------------------------- Ever and Always Comparisons ------------------- + def always_equal(self, value: Union[Pose, TPose]) -> bool: + """ + Returns whether the values of `self` are always equal to `value`. + + Args: + value: :class:`Pose` or :class:`TPose` to compare. + + Returns: + `True` if the values of `self` are always equal to `value`, + `False` otherwise. + + MEOS Functions: + always_eq_tpose_pose, always_eq_tpose_tpose + """ + if isinstance(value, Pose): + return always_eq_tpose_pose(self._inner, value._inner) > 0 + elif isinstance(value, TPose): + return always_eq_tpose_tpose(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def always_not_equal(self, value: Union[Pose, TPose]) -> bool: + """ + Returns whether the values of `self` are always not equal to `value`. + + Args: + value: :class:`Pose` or :class:`TPose` to compare. + + Returns: + `True` if the values of `self` are always not equal to `value`, + `False` otherwise. + + MEOS Functions: + always_ne_tpose_pose, always_ne_tpose_tpose + """ + if isinstance(value, Pose): + return always_ne_tpose_pose(self._inner, value._inner) > 0 + elif isinstance(value, TPose): + return always_ne_tpose_tpose(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def ever_equal(self, value: Union[Pose, TPose]) -> bool: + """ + Returns whether the values of `self` are ever equal to `value`. + + Args: + value: :class:`Pose` or :class:`TPose` to compare. + + Returns: + `True` if the values of `self` are ever equal to `value`, `False` + otherwise. + + MEOS Functions: + ever_eq_tpose_pose, ever_eq_tpose_tpose + """ + if isinstance(value, Pose): + return ever_eq_tpose_pose(self._inner, value._inner) > 0 + elif isinstance(value, TPose): + return ever_eq_tpose_tpose(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def ever_not_equal(self, value: Union[Pose, TPose]) -> bool: + """ + Returns whether the values of `self` are ever not equal to `value`. + + Args: + value: :class:`Pose` or :class:`TPose` to compare. + + Returns: + `True` if the values of `self` are ever not equal to `value`, + `False` otherwise. + + MEOS Functions: + ever_ne_tpose_pose, ever_ne_tpose_tpose + """ + if isinstance(value, Pose): + return ever_ne_tpose_pose(self._inner, value._inner) > 0 + elif isinstance(value, TPose): + return ever_ne_tpose_tpose(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def never_equal(self, value: Union[Pose, TPose]) -> bool: + """ + Returns whether the values of `self` are never equal to `value`. + + Args: + value: :class:`Pose` or :class:`TPose` to compare. + + Returns: + `True` if the values of `self` are never equal to `value`, `False` + otherwise. + + MEOS Functions: + ever_eq_tpose_pose, ever_eq_tpose_tpose + """ + return not self.ever_equal(value) + + def never_not_equal(self, value: Union[Pose, TPose]) -> bool: + """ + Returns whether the values of `self` are never not equal to `value`. + + Args: + value: :class:`Pose` or :class:`TPose` to compare. + + Returns: + `True` if the values of `self` are never not equal to `value`, + `False` otherwise. + + MEOS Functions: + ever_ne_tpose_pose, ever_ne_tpose_tpose + """ + return not self.ever_not_equal(value) + + # ------------------------- Temporal Comparisons -------------------------- + def temporal_equal(self, other: Union[Pose, TPose]) -> TBool: + """ + Returns the temporal equality relation between `self` and `other`. + + Args: + other: A :class:`Pose` or temporal object to compare to `self`. + + Returns: + A :class:`TBool` with the result of the temporal equality relation. + + MEOS Functions: + teq_tpose_pose, teq_temporal_temporal + """ + if isinstance(other, Pose): + result = teq_tpose_pose(self._inner, other._inner) + else: + return super().temporal_equal(other) + return Temporal._factory(result) + + def temporal_not_equal(self, other: Union[Pose, TPose]) -> TBool: + """ + Returns the temporal not equal relation between `self` and `other`. + + Args: + other: A :class:`Pose` or temporal object to compare to `self`. + + Returns: + A :class:`TBool` with the result of the temporal not equal + relation. + + MEOS Functions: + tne_tpose_pose, tne_temporal_temporal + """ + if isinstance(other, Pose): + result = tne_tpose_pose(self._inner, other._inner) + else: + return super().temporal_not_equal(other) + return Temporal._factory(result) + + # ------------------------- Restrictions ---------------------------------- + def at(self, other: Union[Pose, shpb.BaseGeometry, STBox, Time]) -> TPose: + """ + Returns a new temporal pose with the values of `self` restricted to + `other`. + + Args: + other: An object to restrict the values of `self` to. + + Returns: + A new :class:`TPose` with the values of `self` restricted to + `other`. + + MEOS Functions: + tpose_at_pose, tpose_at_geom, tpose_at_stbox, + temporal_at_timestamp, temporal_at_tstzset, temporal_at_tstzspan, + temporal_at_tstzspanset + """ + from ..boxes import STBox + + if isinstance(other, Pose): + result = tpose_at_pose(self._inner, other._inner) + elif isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, False) + result = tpose_at_geom(self._inner, gs) + elif isinstance(other, STBox): + result = tpose_at_stbox(self._inner, other._inner, True) + else: + return super().at(other) + return Temporal._factory(result) + + def minus(self, other: Union[Pose, shpb.BaseGeometry, STBox, Time]) -> TPose: + """ + Returns a new temporal pose with the values of `self` restricted to + the complement of `other`. + + Args: + other: An object to restrict the values of `self` to the + complement of. + + Returns: + A new :class:`TPose` with the values of `self` restricted to the + complement of `other`. + + MEOS Functions: + tpose_minus_pose, tpose_minus_geom, tpose_minus_stbox, + temporal_minus_timestamp, temporal_minus_tstzset, + temporal_minus_tstzspan, temporal_minus_tstzspanset + """ + from ..boxes import STBox + + if isinstance(other, Pose): + result = tpose_minus_pose(self._inner, other._inner) + elif isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, False) + result = tpose_minus_geom(self._inner, gs) + elif isinstance(other, STBox): + result = tpose_minus_stbox(self._inner, other._inner, True) + else: + return super().minus(other) + return Temporal._factory(result) + + # ------------------------- Distance Operations --------------------------- + def distance(self, other: Union[shpb.BaseGeometry, Pose, TPose]) -> TFloat: + """ + Returns the temporal distance between `self` and `other`. + + Args: + other: An object to check the distance to. + + Returns: + A new :class:`TFloat` with the temporal distance. + + MEOS Functions: + tdistance_tpose_point, tdistance_tpose_pose, tdistance_tpose_tpose + """ + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, False) + result = tdistance_tpose_point(self._inner, gs) + elif isinstance(other, Pose): + result = tdistance_tpose_pose(self._inner, other._inner) + elif isinstance(other, TPose): + result = tdistance_tpose_tpose(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[shpb.BaseGeometry, Pose, STBox, TPose] + ) -> float: + """ + Returns the nearest approach distance between `self` and `other`. + + Args: + other: An object to check the nearest approach distance to. + + Returns: + A :class:`float` with the nearest approach distance. + + MEOS Functions: + nad_tpose_geo, nad_tpose_pose, nad_tpose_stbox, nad_tpose_tpose + """ + from ..boxes import STBox + + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, False) + return nad_tpose_geo(self._inner, gs) + elif isinstance(other, Pose): + return nad_tpose_pose(self._inner, other._inner) + elif isinstance(other, STBox): + return nad_tpose_stbox(self._inner, other._inner) + elif isinstance(other, TPose): + return nad_tpose_tpose(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + + def nearest_approach_instant( + self, other: Union[shpb.BaseGeometry, Pose, TPose] + ) -> TPoseInst: + """ + Returns the nearest approach instant between `self` and `other`. + + Args: + other: An object to check the nearest approach instant to. + + Returns: + A new :class:`TPoseInst` with the nearest approach instant. + + MEOS Functions: + nai_tpose_geo, nai_tpose_pose, nai_tpose_tpose + """ + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, False) + result = nai_tpose_geo(self._inner, gs) + elif isinstance(other, Pose): + result = nai_tpose_pose(self._inner, other._inner) + elif isinstance(other, TPose): + result = nai_tpose_tpose(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return Temporal._factory(result) + + def shortest_line( + self, other: Union[shpb.BaseGeometry, Pose, TPose] + ) -> shpb.BaseGeometry: + """ + Returns the shortest line between `self` and `other`. + + Args: + other: An object to check the shortest line to. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` with the + shortest line. + + MEOS Functions: + shortestline_tpose_geo, shortestline_tpose_pose, + shortestline_tpose_tpose + """ + if isinstance(other, shpb.BaseGeometry): + gs = geo_to_gserialized(other, False) + result = shortestline_tpose_geo(self._inner, gs) + elif isinstance(other, Pose): + result = shortestline_tpose_pose(self._inner, other._inner) + elif isinstance(other, TPose): + result = shortestline_tpose_tpose(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return gserialized_to_shapely_geometry(result, 10) + + # ------------------------- Database Operations --------------------------- + @staticmethod + def read_from_cursor(value, _=None): + """ + Reads a :class:`TPose` 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 TPoseInst(string=value) + elif value[0] == "[" or value[0] == "(": + return TPoseSeq(string=value) + elif value[0] == "{": + if value[1] == "[" or value[1] == "(": + return TPoseSeqSet(string=value) + else: + return TPoseSeq(string=value) + raise Exception("ERROR: Could not parse temporal pose value") + + +class TPoseInst(TInstant[Pose, "TPose", "TPoseInst", "TPoseSeq", "TPoseSeqSet"], TPose): + """ + Class for representing temporal poses at a single instant. + """ + + _make_function = lambda *args: None + _cast_function = lambda x: None + + def __init__( + self, + string: Optional[str] = None, + *, + _inner=None, + ) -> None: + super().__init__(string=string, _inner=_inner) + + +class TPoseSeq(TSequence[Pose, "TPose", "TPoseInst", "TPoseSeq", "TPoseSeqSet"], TPose): + """ + Class for representing temporal poses over a tstzspan of time. + """ + + ComponentClass = TPoseInst + + def __init__( + self, + string: Optional[str] = None, + *, + instant_list: Optional[List[Union[str, TPoseInst]]] = None, + lower_inc: bool = True, + upper_inc: bool = 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, + interpolation=interpolation, + normalize=normalize, + _inner=_inner, + ) + + +class TPoseSeqSet( + TSequenceSet[Pose, "TPose", "TPoseInst", "TPoseSeq", "TPoseSeqSet"], TPose +): + """ + Class for representing temporal poses over a tstzspan of time with gaps. + """ + + ComponentClass = TPoseSeq + + def __init__( + self, + string: Optional[str] = None, + *, + sequence_list: Optional[List[Union[str, TPoseSeq]]] = None, + normalize: bool = True, + _inner=None, + ): + super().__init__( + string=string, + sequence_list=sequence_list, + normalize=normalize, + _inner=_inner, + ) diff --git a/pymeos/main/trgeometry.py b/pymeos/main/trgeometry.py new file mode 100644 index 00000000..290d46c3 --- /dev/null +++ b/pymeos/main/trgeometry.py @@ -0,0 +1,1076 @@ +from __future__ import annotations + +from abc import ABC +from typing import Optional, List, Union, TYPE_CHECKING, Set, TypeVar + +import shapely.geometry.base as shpb +from pymeos_cffi import * + +from .tbool import TBool +from .tfloat import TFloat +from .tpoint import TPoint +from ..collections import * +from ..collections.pose import Pose +from ..mixins import TTemporallyComparable +from ..temporal import Temporal, TInstant, TSequence, TSequenceSet, TInterpolation + +if TYPE_CHECKING: + from ..boxes import STBox + from .tpose import TPose + +Self = TypeVar("Self", bound="TRgeometry") + + +class TRgeometry( + Temporal[ + shpb.BaseGeometry, + "TRgeometry", + "TRgeometryInst", + "TRgeometrySeq", + "TRgeometrySeqSet", + ], + TTemporallyComparable, + ABC, +): + """ + Abstract class for temporal rigid geometries. + """ + + _mobilitydb_name = "trgeometry" + + BaseClass = shpb.BaseGeometry + + def __init__(self, _inner) -> None: + super().__init__() + + # ------------------------- Constructors ---------------------------------- + @staticmethod + def from_geometry_tpose(geometry: shpb.BaseGeometry, tpose: TPose) -> TRgeometry: + """ + Create a temporal rigid geometry from a reference geometry and a + temporal pose. + + Args: + geometry: A :class:`~shapely.geometry.base.BaseGeometry` with the + reference geometry. + tpose: A :class:`TPose` with the temporal pose. + + Returns: + A new :class:`TRgeometry` object. + + MEOS Functions: + geo_tpose_to_trgeo + """ + gs = geometry_to_gserialized(geometry) + result = geo_tpose_to_trgeo(gs, tpose._inner) + return Temporal._factory(result) + + # ------------------------- Output ---------------------------------------- + def __str__(self): + """ + Returns the string representation of `self`. + + Returns: + A :class:`str` with the string representation of `self`. + + MEOS Functions: + trgeo_out + """ + return trgeo_out(self._inner) + + def as_wkt(self, precision: int = 15) -> str: + """ + Returns the temporal rigid geometry as a WKT string. + + Args: + precision: The precision of the returned geometry. + + Returns: + A new :class:`str` representing the temporal rigid geometry. + + MEOS Functions: + tspatial_as_text + """ + return tspatial_as_text(self._inner, precision) + + def as_ewkt(self, precision: int = 15) -> str: + """ + Returns the temporal rigid geometry as an EWKT string. + + Args: + precision: The precision of the returned geometry. + + Returns: + A new :class:`str` representing the temporal rigid geometry. + + MEOS Functions: + tspatial_as_ewkt + """ + return tspatial_as_ewkt(self._inner, precision) + + # ------------------------- Conversions ----------------------------------- + def to_tpose(self) -> TPose: + """ + Returns the temporal pose of `self`. + + Returns: + A new :class:`TPose` object. + + MEOS Functions: + trgeo_to_tpose + """ + result = trgeo_to_tpose(self._inner) + return Temporal._factory(result) + + def to_tpoint(self) -> TPoint: + """ + Returns the temporal point of the positions of `self`. + + Returns: + A new :class:`TPoint` object. + + MEOS Functions: + trgeo_to_tpoint + """ + result = trgeo_to_tpoint(self._inner) + return Temporal._factory(result) + + def to_instant(self) -> "TRgeometryInst": + """ + Returns `self` as a :class:`TRgeometryInst`. + + MEOS Functions: + trgeo_to_tinstant + """ + return Temporal._factory(trgeo_to_tinstant(self._inner)) + + # ------------------------- Accessors ------------------------------------- + def bounding_box(self) -> STBox: + """ + Returns the bounding box of `self`. + + Returns: + An :class:`~pymeos.boxes.STBox` representing the bounding box. + + MEOS Functions: + tspatial_to_stbox + """ + from ..boxes import STBox + + return STBox(_inner=tspatial_to_stbox(self._inner)) + + def geometry(self, precision: int = 15) -> shpb.BaseGeometry: + """ + Returns the reference geometry of `self` as a `shapely` + :class:`~shapely.geometry.base.BaseGeometry`. + + Args: + precision: The precision of the returned geometry. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` with the + reference geometry. + + MEOS Functions: + trgeo_geom + """ + return gserialized_to_shapely_geometry(trgeo_geom(self._inner), precision) + + def values(self, precision: int = 15) -> List[shpb.BaseGeometry]: + """ + Returns the values of `self`. + + Returns: + A :class:`list` of :class:`~shapely.geometry.base.BaseGeometry` + with the values. + + MEOS Functions: + trgeo_value_n + """ + return [i.value(precision=precision) for i in self.instants()] + + def start_value(self, precision: int = 15) -> shpb.BaseGeometry: + """ + Returns the start value of `self`. + + Returns: + A :class:`~shapely.geometry.base.BaseGeometry` with the start + value. + + MEOS Functions: + trgeo_start_value + """ + return gserialized_to_shapely_geometry( + trgeo_start_value(self._inner), precision + ) + + def end_value(self, precision: int = 15) -> shpb.BaseGeometry: + """ + Returns the end value of `self`. + + Returns: + A :class:`~shapely.geometry.base.BaseGeometry` with the end value. + + MEOS Functions: + trgeo_end_value + """ + return gserialized_to_shapely_geometry(trgeo_end_value(self._inner), precision) + + def value_set(self, precision: int = 15) -> Set[shpb.BaseGeometry]: + """ + Returns the set of values of `self`. + + Returns: + A :class:`set` of :class:`~shapely.geometry.base.BaseGeometry` + with the values. + + MEOS Functions: + trgeo_value_n + """ + return {i.value(precision=precision) for i in self.instants()} + + def value_at_timestamp( + self, timestamp: datetime, precision: int = 15 + ) -> shpb.BaseGeometry: + """ + Returns the value of `self` at the given timestamp. + + Args: + timestamp: A :class:`datetime` representing the timestamp. + precision: An :class:`int` representing the precision of the + coordinates. + + Returns: + A :class:`~shapely.geometry.base.BaseGeometry` with the value. + + MEOS Functions: + tgeo_value_at_timestamptz + """ + return gserialized_to_shapely_geometry( + tgeo_value_at_timestamptz( + self._inner, datetime_to_timestamptz(timestamp), True + )[0], + precision, + ) + + def value_n(self, n: int, precision: int = 15) -> shpb.BaseGeometry: + """ + Returns the ``n``-th value of `self`. + + Args: + n: The 0-based index of the value to return. + precision: An :class:`int` representing the precision of the + coordinates. + + Returns: + A :class:`~shapely.geometry.base.BaseGeometry` with the value. + + MEOS Functions: + trgeo_value_n + """ + return gserialized_to_shapely_geometry( + trgeo_value_n(self._inner, n + 1)[0], precision + ) + + def start_instant(self: Self) -> TRgeometryInst: + """ + Returns the first instant of `self`. + + Returns: + A new :class:`TRgeometryInst` object. + + MEOS Functions: + trgeo_start_instant + """ + return Temporal._factory(trgeo_start_instant(self._inner)) + + def end_instant(self: Self) -> TRgeometryInst: + """ + Returns the last instant of `self`. + + Returns: + A new :class:`TRgeometryInst` object. + + MEOS Functions: + trgeo_end_instant + """ + return Temporal._factory(trgeo_end_instant(self._inner)) + + def instant_n(self, n: int) -> TRgeometryInst: + """ + Returns the ``n``-th instant of `self`. + + Args: + n: The 0-based index of the instant to return. + + Returns: + A new :class:`TRgeometryInst` object. + + MEOS Functions: + trgeo_instant_n + """ + return Temporal._factory(trgeo_instant_n(self._inner, n + 1)) + + def instants(self) -> List[TRgeometryInst]: + """ + Returns the instants of `self`. + + Returns: + A :class:`list` of :class:`TRgeometryInst` objects. + + MEOS Functions: + trgeo_instants + """ + ins, count = trgeo_instants(self._inner) + return [Temporal._factory(ins[i]) for i in range(count)] + + def points(self) -> GeometrySet: + """ + Returns the set of points of `self`. + + Returns: + A :class:`GeometrySet` with the points. + + MEOS Functions: + trgeo_points + """ + from ..factory import _CollectionFactory + + return _CollectionFactory.create_collection(trgeo_points(self._inner)) + + def rotation(self) -> TFloat: + """ + Returns the rotation of `self` as a temporal float. + + Returns: + A new :class:`TFloat` with the rotation. + + MEOS Functions: + trgeo_rotation + """ + result = trgeo_rotation(self._inner) + return Temporal._factory(result) + + def traversed_area( + self, unary_union: bool = True, precision: int = 15 + ) -> shpb.BaseGeometry: + """ + Returns the traversed area of `self` as a `shapely` + :class:`~shapely.geometry.base.BaseGeometry`. + + Args: + unary_union: Whether to apply a unary union to the result. + precision: The precision of the returned geometry. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` with the + traversed area. + + MEOS Functions: + trgeo_traversed_area + """ + return gserialized_to_shapely_geometry( + trgeo_traversed_area(self._inner, unary_union), precision + ) + + # ------------------------- Spatial Reference System ---------------------- + def srid(self) -> int: + """ + Returns the SRID of `self`. + + Returns: + An :class:`int` representing the SRID. + + MEOS Functions: + tspatial_srid + """ + return tspatial_srid(self._inner) + + def set_srid(self: Self, srid: int) -> Self: + """ + Returns a new :class:`TRgeometry` with the given SRID. + + Args: + srid: The desired SRID. + + Returns: + A new :class:`TRgeometry` instance. + + MEOS Functions: + tspatial_set_srid + """ + return self.__class__(_inner=tspatial_set_srid(self._inner, srid)) + + # ------------------------- Transformations ------------------------------- + def round(self, max_decimals: int = 0) -> TRgeometry: + """ + Round the coordinate values to a number of decimal places. + + Returns: + A new :class:`TRgeometry` object. + + MEOS Functions: + trgeo_round + """ + result = trgeo_round(self._inner, max_decimals) + return Temporal._factory(result) + + def transform(self: Self, srid: int) -> Self: + """ + Returns a new :class:`TRgeometry` transformed to another SRID. + + Args: + srid: The desired SRID. + + Returns: + A new :class:`TRgeometry` instance. + + MEOS Functions: + tspatial_transform + """ + result = tspatial_transform(self._inner, srid) + return Temporal._factory(result) + + def set_interpolation(self: Self, interpolation: TInterpolation) -> Self: + """ + Returns a new :class:`TRgeometry` with the given interpolation. + + Args: + interpolation: The desired interpolation. + + Returns: + A new :class:`TRgeometry` instance. + + MEOS Functions: + trgeo_set_interp + """ + result = trgeo_set_interp(self._inner, interpolation.value) + return Temporal._factory(result) + + def append_instant( + self: Self, + instant: TRgeometryInst, + max_dist: Optional[float] = 0.0, + max_time: Optional[timedelta] = None, + ) -> TRgeometry: + """ + Returns a new :class:`TRgeometry` with `instant` appended. + + Args: + instant: The :class:`TRgeometryInst` to append. + max_dist: The maximum distance between consecutive instants. + max_time: The maximum time between consecutive instants. + + Returns: + A new :class:`TRgeometry` object. + + MEOS Functions: + trgeo_append_tinstant + """ + interp = self.interpolation() + mt = timedelta_to_interval(max_time) if max_time is not None else None + result = trgeo_append_tinstant( + self._inner, instant._inner, interp.value, max_dist, mt, False + ) + return Temporal._factory(result) + + def append_sequence(self: Self, sequence: TRgeometrySeq) -> TRgeometry: + """ + Returns a new :class:`TRgeometry` with `sequence` appended. + + Args: + sequence: The :class:`TRgeometrySeq` to append. + + Returns: + A new :class:`TRgeometry` object. + + MEOS Functions: + trgeo_append_tsequence + """ + result = trgeo_append_tsequence(self._inner, sequence._inner, False) + return Temporal._factory(result) + + # ------------------------- Ever and Always Comparisons ------------------- + def always_equal(self, value: Union[shpb.BaseGeometry, TRgeometry]) -> bool: + """ + Returns whether the values of `self` are always equal to `value`. + + Args: + value: :class:`~shapely.geometry.base.BaseGeometry` or + :class:`TRgeometry` to compare. + + Returns: + `True` if the values of `self` are always equal to `value`, + `False` otherwise. + + MEOS Functions: + always_eq_trgeo_geo, always_eq_trgeo_trgeo + """ + if isinstance(value, shpb.BaseGeometry): + gs = geometry_to_gserialized(value) + return always_eq_trgeo_geo(self._inner, gs) > 0 + elif isinstance(value, TRgeometry): + return always_eq_trgeo_trgeo(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def always_not_equal(self, value: Union[shpb.BaseGeometry, TRgeometry]) -> bool: + """ + Returns whether the values of `self` are always not equal to `value`. + + Args: + value: :class:`~shapely.geometry.base.BaseGeometry` or + :class:`TRgeometry` to compare. + + Returns: + `True` if the values of `self` are always not equal to `value`, + `False` otherwise. + + MEOS Functions: + always_ne_trgeo_geo, always_ne_trgeo_trgeo + """ + if isinstance(value, shpb.BaseGeometry): + gs = geometry_to_gserialized(value) + return always_ne_trgeo_geo(self._inner, gs) > 0 + elif isinstance(value, TRgeometry): + return always_ne_trgeo_trgeo(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def ever_equal(self, value: Union[shpb.BaseGeometry, TRgeometry]) -> bool: + """ + Returns whether the values of `self` are ever equal to `value`. + + Args: + value: :class:`~shapely.geometry.base.BaseGeometry` or + :class:`TRgeometry` to compare. + + Returns: + `True` if the values of `self` are ever equal to `value`, `False` + otherwise. + + MEOS Functions: + ever_eq_trgeo_geo, ever_eq_trgeo_trgeo + """ + if isinstance(value, shpb.BaseGeometry): + gs = geometry_to_gserialized(value) + return ever_eq_trgeo_geo(self._inner, gs) > 0 + elif isinstance(value, TRgeometry): + return ever_eq_trgeo_trgeo(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def ever_not_equal(self, value: Union[shpb.BaseGeometry, TRgeometry]) -> bool: + """ + Returns whether the values of `self` are ever not equal to `value`. + + Args: + value: :class:`~shapely.geometry.base.BaseGeometry` or + :class:`TRgeometry` to compare. + + Returns: + `True` if the values of `self` are ever not equal to `value`, + `False` otherwise. + + MEOS Functions: + ever_ne_trgeo_geo, ever_ne_trgeo_trgeo + """ + if isinstance(value, shpb.BaseGeometry): + gs = geometry_to_gserialized(value) + return ever_ne_trgeo_geo(self._inner, gs) > 0 + elif isinstance(value, TRgeometry): + return ever_ne_trgeo_trgeo(self._inner, value._inner) > 0 + else: + raise TypeError(f"Operation not supported with type {value.__class__}") + + def never_equal(self, value: Union[shpb.BaseGeometry, TRgeometry]) -> bool: + """ + Returns whether the values of `self` are never equal to `value`. + + Args: + value: :class:`~shapely.geometry.base.BaseGeometry` or + :class:`TRgeometry` to compare. + + Returns: + `True` if the values of `self` are never equal to `value`, `False` + otherwise. + + MEOS Functions: + ever_eq_trgeo_geo, ever_eq_trgeo_trgeo + """ + return not self.ever_equal(value) + + def never_not_equal(self, value: Union[shpb.BaseGeometry, TRgeometry]) -> bool: + """ + Returns whether the values of `self` are never not equal to `value`. + + Args: + value: :class:`~shapely.geometry.base.BaseGeometry` or + :class:`TRgeometry` to compare. + + Returns: + `True` if the values of `self` are never not equal to `value`, + `False` otherwise. + + MEOS Functions: + ever_ne_trgeo_geo, ever_ne_trgeo_trgeo + """ + return not self.ever_not_equal(value) + + # ------------------------- Temporal Comparisons -------------------------- + def temporal_equal(self, other: Union[shpb.BaseGeometry, TRgeometry]) -> TBool: + """ + Returns the temporal equality relation between `self` and `other`. + + Args: + other: A :class:`~shapely.geometry.base.BaseGeometry` or temporal + object to compare to `self`. + + Returns: + A :class:`TBool` with the result of the temporal equality relation. + + MEOS Functions: + teq_trgeo_geo, teq_temporal_temporal + """ + if isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other) + result = teq_trgeo_geo(self._inner, gs) + else: + return super().temporal_equal(other) + return Temporal._factory(result) + + def temporal_not_equal(self, other: Union[shpb.BaseGeometry, TRgeometry]) -> TBool: + """ + Returns the temporal not equal relation between `self` and `other`. + + Args: + other: A :class:`~shapely.geometry.base.BaseGeometry` or temporal + object to compare to `self`. + + Returns: + A :class:`TBool` with the result of the temporal not equal + relation. + + MEOS Functions: + tne_trgeo_geo, tne_temporal_temporal + """ + if isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other) + result = tne_trgeo_geo(self._inner, gs) + else: + return super().temporal_not_equal(other) + return Temporal._factory(result) + + # ------------------------- Restrictions ---------------------------------- + def at(self, other: Time) -> TRgeometry: + """ + Returns a new temporal rigid geometry 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 :class:`TRgeometry` with the values of `self` restricted to + `other`. + + MEOS Functions: + trgeo_restrict_timestamptz, trgeo_restrict_tstzset, + trgeo_restrict_tstzspan, trgeo_restrict_tstzspanset + """ + if isinstance(other, datetime): + result = trgeo_restrict_timestamptz( + self._inner, datetime_to_timestamptz(other), True + ) + elif isinstance(other, TsTzSet): + result = trgeo_restrict_tstzset(self._inner, other._inner, True) + elif isinstance(other, TsTzSpan): + result = trgeo_restrict_tstzspan(self._inner, other._inner, True) + elif isinstance(other, TsTzSpanSet): + result = trgeo_restrict_tstzspanset(self._inner, other._inner, True) + else: + return super().at(other) + return Temporal._factory(result) + + def minus(self, other: Time) -> TRgeometry: + """ + Returns a new temporal rigid geometry with the values of `self` + restricted to the complement of the time `other`. + + Args: + other: A time object to restrict the values of `self` to the + complement of. + + Returns: + A new :class:`TRgeometry` with the values of `self` restricted to + the complement of `other`. + + MEOS Functions: + trgeo_restrict_timestamptz, trgeo_restrict_tstzset, + trgeo_restrict_tstzspan, trgeo_restrict_tstzspanset + """ + if isinstance(other, datetime): + result = trgeo_restrict_timestamptz( + self._inner, datetime_to_timestamptz(other), False + ) + elif isinstance(other, TsTzSet): + result = trgeo_restrict_tstzset(self._inner, other._inner, False) + elif isinstance(other, TsTzSpan): + result = trgeo_restrict_tstzspan(self._inner, other._inner, False) + elif isinstance(other, TsTzSpanSet): + result = trgeo_restrict_tstzspanset(self._inner, other._inner, False) + else: + return super().minus(other) + return Temporal._factory(result) + + # ------------------------- Splitting ------------------------------------- + def segments(self) -> List["TRgeometrySeq"]: + """ + Returns the temporal segments of `self`. + + MEOS Functions: + trgeo_segments + """ + seqs, count = trgeo_segments(self._inner) + return [Temporal._factory(seqs[i]) for i in range(count)] + + # ------------------------- Modifications --------------------------------- + def delete(self, other: Time, connect: bool = True) -> TRgeometry: + """ + Returns a new temporal rigid geometry equal to `self` with the + elements at `other` removed. + + Args: + other: A time object to remove from `self`. + connect: Whether to connect the resulting segments. + + MEOS Functions: + trgeo_delete_timestamptz, trgeo_delete_tstzset, + trgeo_delete_tstzspan, trgeo_delete_tstzspanset + """ + if isinstance(other, datetime): + result = trgeo_delete_timestamptz( + self._inner, datetime_to_timestamptz(other), connect + ) + elif isinstance(other, TsTzSet): + result = trgeo_delete_tstzset(self._inner, other._inner, connect) + elif isinstance(other, TsTzSpan): + result = trgeo_delete_tstzspan(self._inner, other._inner, connect) + elif isinstance(other, TsTzSpanSet): + result = trgeo_delete_tstzspanset( + self._inner, other._inner, connect + ) + else: + return super().delete(other, connect) + return Temporal._factory(result) + + def before(self, timestamp: datetime, strict: bool = False) -> TRgeometry: + """ + Returns a new temporal rigid geometry with the values of `self` + before `timestamp`. + + Args: + timestamp: A :class:`datetime` to restrict before. + strict: Whether the bound is strict. + + MEOS Functions: + trgeo_before_timestamptz + """ + result = trgeo_before_timestamptz( + self._inner, datetime_to_timestamptz(timestamp), strict + ) + return Temporal._factory(result) + + def after(self, timestamp: datetime, strict: bool = False) -> TRgeometry: + """ + Returns a new temporal rigid geometry with the values of `self` + after `timestamp`. + + Args: + timestamp: A :class:`datetime` to restrict after. + strict: Whether the bound is strict. + + MEOS Functions: + trgeo_after_timestamptz + """ + result = trgeo_after_timestamptz( + self._inner, datetime_to_timestamptz(timestamp), strict + ) + return Temporal._factory(result) + + # ------------------------- Distance Operations --------------------------- + def distance( + self, other: Union[shpb.BaseGeometry, TPoint, TRgeometry] + ) -> TFloat: + """ + Returns the temporal distance between `self` and `other`. + + Args: + other: An object to check the distance to. + + Returns: + A new :class:`TFloat` with the temporal distance. + + MEOS Functions: + tdistance_trgeo_geo, tdistance_trgeo_tpoint, tdistance_trgeo_trgeo + """ + if isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other) + result = tdistance_trgeo_geo(self._inner, gs) + elif isinstance(other, TPoint): + result = tdistance_trgeo_tpoint(self._inner, other._inner) + elif isinstance(other, TRgeometry): + result = tdistance_trgeo_trgeo(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[shpb.BaseGeometry, STBox, TPoint, TRgeometry] + ) -> float: + """ + Returns the nearest approach distance between `self` and `other`. + + Args: + other: An object to check the nearest approach distance to. + + Returns: + A :class:`float` with the nearest approach distance. + + MEOS Functions: + nad_trgeo_geo, nad_trgeo_stbox, nad_trgeo_tpoint, nad_trgeo_trgeo + """ + from ..boxes import STBox + + if isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other) + return nad_trgeo_geo(self._inner, gs) + elif isinstance(other, STBox): + return nad_trgeo_stbox(self._inner, other._inner) + elif isinstance(other, TPoint): + return nad_trgeo_tpoint(self._inner, other._inner) + elif isinstance(other, TRgeometry): + return nad_trgeo_trgeo(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + + def nearest_approach_instant( + self, other: Union[shpb.BaseGeometry, TPoint, TRgeometry] + ) -> TRgeometryInst: + """ + Returns the nearest approach instant between `self` and `other`. + + Args: + other: An object to check the nearest approach instant to. + + Returns: + A new :class:`TRgeometryInst` with the nearest approach instant. + + MEOS Functions: + nai_trgeo_geo, nai_trgeo_tpoint, nai_trgeo_trgeo + """ + if isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other) + result = nai_trgeo_geo(self._inner, gs) + elif isinstance(other, TPoint): + result = nai_trgeo_tpoint(self._inner, other._inner) + elif isinstance(other, TRgeometry): + result = nai_trgeo_trgeo(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return Temporal._factory(result) + + def shortest_line( + self, other: Union[shpb.BaseGeometry, TPoint, TRgeometry] + ) -> shpb.BaseGeometry: + """ + Returns the shortest line between `self` and `other`. + + Args: + other: An object to check the shortest line to. + + Returns: + A new :class:`~shapely.geometry.base.BaseGeometry` with the + shortest line. + + MEOS Functions: + shortestline_trgeo_geo, shortestline_trgeo_tpoint, + shortestline_trgeo_trgeo + """ + if isinstance(other, shpb.BaseGeometry): + gs = geometry_to_gserialized(other) + result = shortestline_trgeo_geo(self._inner, gs) + elif isinstance(other, TPoint): + result = shortestline_trgeo_tpoint(self._inner, other._inner) + elif isinstance(other, TRgeometry): + result = shortestline_trgeo_trgeo(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return gserialized_to_shapely_geometry(result, 10) + + # ------------------------- Database Operations --------------------------- + @staticmethod + def read_from_cursor(value, _=None): + """ + Reads a :class:`TRgeometry` 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 TRgeometryInst(string=value) + elif value[0] == "[" or value[0] == "(": + return TRgeometrySeq(string=value) + elif value[0] == "{": + if value[1] == "[" or value[1] == "(": + return TRgeometrySeqSet(string=value) + else: + return TRgeometrySeq(string=value) + raise Exception("ERROR: Could not parse temporal rigid geometry value") + + +class TRgeometryInst( + TInstant[ + shpb.BaseGeometry, + "TRgeometry", + "TRgeometryInst", + "TRgeometrySeq", + "TRgeometrySeqSet", + ], + TRgeometry, +): + """ + Class for representing temporal rigid geometries at a single instant. + """ + + _make_function = lambda *args: None + _cast_function = lambda x: None + + def __init__( + self, + string: Optional[str] = None, + *, + geometry: Optional[shpb.BaseGeometry] = None, + pose: Optional[Pose] = None, + timestamp: Optional[Union[str, datetime]] = None, + _inner=None, + ) -> None: + super().__init__(string=string, _inner=_inner) + if self._inner is None: + gs = geometry_to_gserialized(geometry) + ts = ( + datetime_to_timestamptz(timestamp) + if isinstance(timestamp, datetime) + else pg_timestamptz_in(timestamp, -1) + ) + self._inner = trgeoinst_make(gs, pose._inner, ts) + + +class TRgeometrySeq( + TSequence[ + shpb.BaseGeometry, + "TRgeometry", + "TRgeometryInst", + "TRgeometrySeq", + "TRgeometrySeqSet", + ], + TRgeometry, +): + """ + Class for representing temporal rigid geometries over a tstzspan of time. + """ + + ComponentClass = TRgeometryInst + + def __init__( + self, + string: Optional[str] = None, + *, + instant_list: Optional[List[Union[str, TRgeometryInst]]] = None, + lower_inc: bool = True, + upper_inc: bool = 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, + interpolation=interpolation, + normalize=normalize, + _inner=_inner, + ) + + +class TRgeometrySeqSet( + TSequenceSet[ + shpb.BaseGeometry, + "TRgeometry", + "TRgeometryInst", + "TRgeometrySeq", + "TRgeometrySeqSet", + ], + TRgeometry, +): + """ + Class for representing temporal rigid geometries over a tstzspan of time + with gaps. + """ + + ComponentClass = TRgeometrySeq + + def __init__( + self, + string: Optional[str] = None, + *, + sequence_list: Optional[List[Union[str, TRgeometrySeq]]] = None, + normalize: bool = True, + _inner=None, + ): + super().__init__( + string=string, + sequence_list=sequence_list, + normalize=normalize, + _inner=_inner, + ) + + # ------------------------- Accessors ------------------------------------- + def start_sequence(self) -> TRgeometrySeq: + """ + Returns the first sequence of `self`. + + MEOS Functions: + trgeo_start_sequence + """ + return self.ComponentClass(_inner=trgeo_start_sequence(self._inner)) + + def end_sequence(self) -> TRgeometrySeq: + """ + Returns the last sequence of `self`. + + MEOS Functions: + trgeo_end_sequence + """ + return self.ComponentClass(_inner=trgeo_end_sequence(self._inner)) + + def sequence_n(self, n: int) -> TRgeometrySeq: + """ + Returns the ``n``-th sequence of `self` (0-based). + + MEOS Functions: + trgeo_sequence_n + """ + return self.ComponentClass(_inner=trgeo_sequence_n(self._inner, n + 1)) + + def sequences(self) -> List[TRgeometrySeq]: + """ + Returns the list of sequences of `self`. + + MEOS Functions: + trgeo_sequences + """ + seqs, count = trgeo_sequences(self._inner) + return [self.ComponentClass(_inner=seqs[i]) for i in range(count)] diff --git a/tests/main/tcbuffer_test.py b/tests/main/tcbuffer_test.py new file mode 100644 index 00000000..a7bd0c4f --- /dev/null +++ b/tests/main/tcbuffer_test.py @@ -0,0 +1,176 @@ +from copy import copy + +import pytest +from shapely import Point + +from pymeos import ( + TBool, + TCbuffer, + TCbufferInst, + TCbufferSeq, + TCbufferSeqSet, + TInterpolation, + Cbuffer, +) +from tests.conftest import TestPyMEOS + + +class TestTCbuffer(TestPyMEOS): + pass + + +class TestTCbufferConstructors(TestTCbuffer): + tci = TCbufferInst("Cbuffer(Point(1 1), 0.5)@2019-09-01") + tcds = TCbufferSeq( + "{Cbuffer(Point(1 1), 0.5)@2019-09-01," + " Cbuffer(Point(2 2), 0.3)@2019-09-02}" + ) + tcs = TCbufferSeq( + "[Cbuffer(Point(1 1), 0.5)@2019-09-01," + " Cbuffer(Point(2 2), 0.3)@2019-09-02]" + ) + tcss = TCbufferSeqSet( + "{[Cbuffer(Point(1 1), 0.5)@2019-09-01," + " Cbuffer(Point(2 2), 0.3)@2019-09-02]," + "[Cbuffer(Point(1 1), 0.5)@2019-09-03," + " Cbuffer(Point(1 1), 0.5)@2019-09-05]}" + ) + + @pytest.mark.parametrize( + "temporal, expected_type", + [ + (tci, TCbufferInst), + (tcds, TCbufferSeq), + (tcs, TCbufferSeq), + (tcss, TCbufferSeqSet), + ], + ids=["Instant", "Discrete Sequence", "Sequence", "SequenceSet"], + ) + def test_string_constructor(self, temporal, expected_type): + assert isinstance(temporal, expected_type) + + @pytest.mark.parametrize( + "temporal", + [tci, tcds, tcs, tcss], + ids=["Instant", "Discrete Sequence", "Sequence", "SequenceSet"], + ) + def test_from_as_hexwkb_constructor(self, temporal): + assert temporal == temporal.__class__.from_hexwkb(temporal.as_hexwkb()) + + @pytest.mark.parametrize( + "temporal", + [tci, tcds, tcs, tcss], + ids=["Instant", "Discrete Sequence", "Sequence", "SequenceSet"], + ) + def test_copy_constructor(self, temporal): + other = copy(temporal) + assert temporal == other + assert temporal is not other + + +class TestTCbufferOutputs(TestTCbuffer): + tci = TCbufferInst("Cbuffer(Point(1 1), 0.5)@2019-09-01") + tcs = TCbufferSeq( + "[Cbuffer(Point(1 1), 0.5)@2019-09-01," + " Cbuffer(Point(2 2), 0.3)@2019-09-02]" + ) + + def test_repr(self): + assert isinstance(repr(self.tci), str) + + def test_as_wkt(self): + assert isinstance(self.tcs.as_wkt(), str) + + +class TestTCbufferAccessors(TestTCbuffer): + tcs = TCbufferSeq( + "[Cbuffer(Point(1 1), 0.5)@2019-09-01," + " Cbuffer(Point(2 2), 0.3)@2019-09-02]" + ) + + def test_interpolation(self): + assert self.tcs.interpolation() == TInterpolation.LINEAR + + def test_srid(self): + assert isinstance(self.tcs.srid(), int) + + def test_to_tfloat(self): + assert self.tcs.to_tfloat() is not None + + def test_to_tgeompoint(self): + assert self.tcs.to_tgeompoint() is not None + + +class TestTCbufferEverAlways(TestTCbuffer): + tcs = TCbufferSeq( + "[Cbuffer(Point(1 1), 0.5)@2019-09-01," + " Cbuffer(Point(2 2), 0.3)@2019-09-02]" + ) + + def test_ever_equal(self): + assert isinstance( + self.tcs.ever_equal(Cbuffer("Cbuffer(Point(1 1), 0.5)")), bool + ) + + def test_never_not_equal(self): + assert isinstance( + self.tcs.never_not_equal(Cbuffer("Cbuffer(Point(1 1), 0.5)")), bool + ) + + +class TestTCbufferDistance(TestTCbuffer): + tcs = TCbufferSeq( + "[Cbuffer(Point(1 1), 0.5)@2019-09-01," + " Cbuffer(Point(2 2), 0.3)@2019-09-02]" + ) + + def test_nearest_approach_distance(self): + assert isinstance( + self.tcs.nearest_approach_distance( + Cbuffer("Cbuffer(Point(0 0), 0.0)") + ), + float, + ) + + +class TestTCbufferSpatialRelationships(TestTCbuffer): + tcs = TCbufferSeq( + "[Cbuffer(Point(1 1), 0.5)@2019-09-01," + " Cbuffer(Point(2 2), 0.3)@2019-09-02]" + ) + other = Cbuffer("Cbuffer(Point(1 1), 0.5)") + + def test_ever_relationships(self): + assert isinstance(self.tcs.ever_intersects(self.other), bool) + assert isinstance(self.tcs.is_ever_disjoint(self.other), bool) + assert isinstance(self.tcs.ever_intersects(Point(1, 1)), bool) + assert isinstance( + self.tcs.is_ever_within_distance(self.other, 1.0), bool + ) + + def test_always_relationships(self): + assert isinstance(self.tcs.always_intersects(self.other), bool) + assert isinstance(self.tcs.is_always_disjoint(self.other), bool) + + def test_temporal_relationships(self): + r = self.tcs.intersects(self.other) + assert r is None or isinstance(r, TBool) + r = self.tcs.within_distance(self.other, 1.0) + assert r is None or isinstance(r, TBool) + r = self.tcs.disjoint(Point(1, 1)) + assert r is None or isinstance(r, TBool) + + +class TestCbuffer(TestPyMEOS): + c = Cbuffer("Cbuffer(Point(1 1), 0.5)") + + def test_string_constructor(self): + assert isinstance(self.c, Cbuffer) + + def test_from_as_hexwkb(self): + assert self.c == Cbuffer.from_hexwkb(self.c.as_hexwkb()) + + def test_copy(self): + other = copy(self.c) + assert self.c == other + assert self.c is not other diff --git a/tests/main/tnpoint_test.py b/tests/main/tnpoint_test.py new file mode 100644 index 00000000..e0006a3a --- /dev/null +++ b/tests/main/tnpoint_test.py @@ -0,0 +1,261 @@ +from copy import copy +from datetime import datetime, timezone + +import pytest + +from pymeos import ( + TFloat, + TNpoint, + TNpointInst, + TNpointSeq, + TNpointSeqSet, + TInterpolation, + Npoint, + NpointSet, + Nsegment, +) +from tests.conftest import TestPyMEOS + + +class TestTNpoint(TestPyMEOS): + pass + + +class TestTNpointConstructors(TestTNpoint): + tpi = TNpointInst("NPoint(1, 0.5)@2019-09-01") + tpds = TNpointSeq("{NPoint(1, 0.5)@2019-09-01, NPoint(1, 0.7)@2019-09-02}") + tps = TNpointSeq("[NPoint(1, 0.5)@2019-09-01, NPoint(1, 0.7)@2019-09-02]") + tpss = TNpointSeqSet( + "{[NPoint(1, 0.5)@2019-09-01, NPoint(1, 0.7)@2019-09-02]," + "[NPoint(1, 0.5)@2019-09-03, NPoint(1, 0.5)@2019-09-05]}" + ) + + @pytest.mark.parametrize( + "source, type, interpolation", + [ + (tpi, TNpointInst, TInterpolation.NONE), + (tpds, TNpointSeq, TInterpolation.DISCRETE), + (tps, TNpointSeq, TInterpolation.LINEAR), + (tpss, TNpointSeqSet, TInterpolation.LINEAR), + ], + ids=["Instant", "Discrete Sequence", "Sequence", "SequenceSet"], + ) + def test_string_constructor(self, source, type, interpolation): + assert isinstance(source, type) + assert source.interpolation() == interpolation + + @pytest.mark.parametrize( + "temporal", + [tpi, tpds, tps, tpss], + 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()) + + @pytest.mark.parametrize( + "temporal", + [tpi, tpds, tps, tpss], + ids=["Instant", "Discrete Sequence", "Sequence", "SequenceSet"], + ) + def test_copy_constructor(self, temporal): + other = copy(temporal) + assert temporal == other + assert temporal is not other + + def test_instant_value_timestamp_constructor(self): + tpi = TNpointInst(value=Npoint(1, 0.5), timestamp="2019-09-01") + assert isinstance(tpi, TNpointInst) + assert tpi.route() == 1 + + +class TestTNpointOutputs(TestTNpoint): + tpi = TNpointInst("NPoint(1, 0.5)@2019-09-01") + tps = TNpointSeq("[NPoint(1, 0.5)@2019-09-01, NPoint(1, 0.7)@2019-09-02]") + + @pytest.mark.parametrize( + "temporal", + [tpi, tps], + ids=["Instant", "Sequence"], + ) + def test_str_round_trip(self, temporal): + assert isinstance(str(temporal), str) + assert temporal == temporal.__class__(str(temporal)) + + @pytest.mark.parametrize( + "temporal", + [tpi, tps], + ids=["Instant", "Sequence"], + ) + def test_as_wkt(self, temporal): + assert isinstance(temporal.as_wkt(), str) + + @pytest.mark.parametrize( + "temporal", + [tpi, tps], + ids=["Instant", "Sequence"], + ) + def test_repr_round_trip(self, temporal): + assert temporal == temporal.__class__(repr(temporal)) + + +class TestTNpointAccessors(TestTNpoint): + tpi = TNpointInst("NPoint(1, 0.5)@2019-09-01") + tps = TNpointSeq("[NPoint(1, 0.5)@2019-09-01, NPoint(1, 0.7)@2019-09-02]") + + def test_route(self): + assert self.tpi.route() == 1 + assert self.tps.route() == 1 + + def test_srid(self): + assert isinstance(self.tps.srid(), int) + + def test_length(self): + assert isinstance(self.tps.length(), float) + + def test_cumulative_length(self): + assert isinstance(self.tps.cumulative_length(), TFloat) + + def test_speed(self): + assert isinstance(self.tps.speed(), TFloat) + + def test_bounding_box(self): + from pymeos import STBox + + assert isinstance(self.tps.bounding_box(), STBox) + + def test_to_tgeompoint(self): + from pymeos import TGeomPoint + + assert isinstance(self.tps.to_tgeompoint(), TGeomPoint) + + +class TestTNpointComparisons(TestTNpoint): + tps = TNpointSeq("[NPoint(1, 0.5)@2019-09-01, NPoint(1, 0.7)@2019-09-02]") + other = TNpointSeq("[NPoint(1, 0.5)@2019-09-01, NPoint(1, 0.7)@2019-09-02]") + + def test_eq(self): + assert self.tps == self.other + + def test_ever_equal(self): + assert self.tps.ever_equal(Npoint(1, 0.5)) + + def test_never_not_equal(self): + assert isinstance(self.tps.never_not_equal(Npoint(1, 0.5)), bool) + + def test_temporal_equal(self): + from pymeos import TBool + + assert isinstance(self.tps.temporal_equal(Npoint(1, 0.5)), TBool) + + def test_distance(self): + assert isinstance(self.tps.distance(self.other), TFloat) + + def test_nearest_approach_distance(self): + assert isinstance( + self.tps.nearest_approach_distance(self.other), float + ) + + def test_nearest_approach_instant(self): + assert isinstance( + self.tps.nearest_approach_instant(self.other), TNpointInst + ) + + +class TestTNpointRestrictions(TestTNpoint): + tps = TNpointSeq("[NPoint(1, 0.5)@2019-09-01, NPoint(1, 0.7)@2019-09-02]") + + def test_at_timestamp(self): + result = self.tps.at( + datetime(2019, 9, 1, tzinfo=timezone.utc) + ) + assert isinstance(result, TNpoint) + + def test_minus_timestamp(self): + result = self.tps.minus( + datetime(2019, 9, 1, tzinfo=timezone.utc) + ) + assert isinstance(result, TNpoint) + + +class TestNpoint(TestTNpoint): + np = Npoint("NPoint(1, 0.5)") + + def test_string_constructor(self): + assert isinstance(self.np, Npoint) + + def test_route_position(self): + assert self.np.route() == 1 + assert self.np.position() == 0.5 + + def test_make_constructor(self): + assert Npoint(route=1, position=0.5) == self.np + + def test_from_as_wkb(self): + assert self.np == Npoint.from_wkb(self.np.as_wkb()) + assert self.np == Npoint.from_hexwkb(self.np.as_hexwkb()) + + def test_str_round_trip(self): + assert self.np == Npoint(str(self.np)) + + def test_hash(self): + assert isinstance(hash(self.np), int) + + def test_to_stbox(self): + from pymeos import STBox + + assert isinstance(self.np.to_stbox(), STBox) + + +class TestNpointSet(TestTNpoint): + nps = NpointSet("{NPoint(1, 0.5), NPoint(2, 0.3)}") + + def test_string_constructor(self): + assert isinstance(self.nps, NpointSet) + + def test_elements_constructor(self): + other = NpointSet(elements=[Npoint(1, 0.5), Npoint(2, 0.3)]) + assert other == self.nps + + def test_start_end_element(self): + assert isinstance(self.nps.start_element(), Npoint) + assert isinstance(self.nps.end_element(), Npoint) + + def test_element_n(self): + assert isinstance(self.nps.element_n(0), Npoint) + + def test_elements(self): + elems = self.nps.elements() + assert all(isinstance(e, Npoint) for e in elems) + + def test_str_round_trip(self): + assert self.nps == NpointSet(str(self.nps)) + + +class TestTNpointPositions(TestTNpoint): + tps = TNpointSeq("[NPoint(1, 0.5)@2019-09-01, NPoint(1, 0.7)@2019-09-02]") + + def test_positions(self): + pos = self.tps.positions() + assert all(isinstance(p, Nsegment) for p in pos) + + +class TestNsegment(TestPyMEOS): + ns = Nsegment("NSegment(1, 0.2, 0.6)") + + def test_constructors(self): + assert isinstance(self.ns, Nsegment) + assert isinstance(Nsegment(rid=1, pos1=0.2, pos2=0.6), Nsegment) + + def test_accessors(self): + assert isinstance(self.ns.route(), int) + assert isinstance(self.ns.start_position(), float) + assert isinstance(self.ns.end_position(), float) + assert isinstance(self.ns.srid(), int) + + def test_copy_eq(self): + other = copy(self.ns) + assert self.ns == other + + def test_round(self): + assert isinstance(self.ns.round(2), Nsegment) diff --git a/tests/main/tpose_test.py b/tests/main/tpose_test.py new file mode 100644 index 00000000..c69bb3f9 --- /dev/null +++ b/tests/main/tpose_test.py @@ -0,0 +1,184 @@ +from copy import copy +from datetime import datetime, timezone + +import pytest + +from pymeos import ( + TPose, + TPoseInst, + TPoseSeq, + TPoseSeqSet, + TInterpolation, + Pose, +) +from tests.conftest import TestPyMEOS + + +class TestTPose(TestPyMEOS): + pass + + +class TestTPoseConstructors(TestTPose): + tpi = TPoseInst("Pose(Point(1 1), 0.5)@2019-09-01") + tpds = TPoseSeq( + "{Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02}" + ) + tps = TPoseSeq( + "[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]" + ) + tpss = TPoseSeqSet( + "{[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]," + "[Pose(Point(1 1), 0.5)@2019-09-03, Pose(Point(1 1), 0.5)@2019-09-05]}" + ) + + @pytest.mark.parametrize( + "temporal, expected_type", + [ + (tpi, TPoseInst), + (tpds, TPoseSeq), + (tps, TPoseSeq), + (tpss, TPoseSeqSet), + ], + ids=["Instant", "Discrete Sequence", "Sequence", "SequenceSet"], + ) + def test_string_constructor(self, temporal, expected_type): + assert isinstance(temporal, expected_type) + + @pytest.mark.parametrize( + "temporal", + [tpi, tpds, tps, tpss], + ids=["Instant", "Discrete Sequence", "Sequence", "SequenceSet"], + ) + def test_from_as_hexwkb_constructor(self, temporal): + assert temporal == temporal.__class__.from_hexwkb(temporal.as_hexwkb()) + + @pytest.mark.parametrize( + "temporal", + [tpi, tpds, tps, tpss], + ids=["Instant", "Discrete Sequence", "Sequence", "SequenceSet"], + ) + def test_copy_constructor(self, temporal): + other = copy(temporal) + assert temporal == other + assert temporal is not other + + +class TestTPoseOutputs(TestTPose): + tpi = TPoseInst("Pose(Point(1 1), 0.5)@2019-09-01") + tps = TPoseSeq( + "[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]" + ) + + def test_str(self): + assert str(self.tpi) == "POSE(POINT(1 1), 0.5)@2019-09-01 00:00:00+00" + + def test_repr(self): + assert isinstance(repr(self.tpi), str) + + def test_as_wkt(self): + assert isinstance(self.tps.as_wkt(), str) + + +class TestTPoseAccessors(TestTPose): + tpi = TPoseInst("Pose(Point(1 1), 0.5)@2019-09-01") + tps = TPoseSeq( + "[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]" + ) + + def test_interpolation(self): + assert self.tps.interpolation() == TInterpolation.LINEAR + + def test_start_value(self): + assert isinstance(self.tps.start_value(), Pose) + + def test_end_value(self): + assert isinstance(self.tps.end_value(), Pose) + + def test_values(self): + values = self.tps.values() + assert all(isinstance(v, Pose) for v in values) + + def test_value_at_timestamp(self): + value = self.tps.value_at_timestamp( + datetime(2019, 9, 1, tzinfo=timezone.utc) + ) + assert isinstance(value, Pose) + + def test_srid(self): + assert isinstance(self.tpi.srid(), int) + + def test_to_tpoint(self): + assert self.tps.to_tpoint() is not None + + +class TestTPoseEverAlways(TestTPose): + tps = TPoseSeq( + "[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]" + ) + + def test_ever_equal(self): + assert self.tps.ever_equal(Pose("Pose(Point(1 1), 0.5)")) + + def test_never_not_equal(self): + assert isinstance( + self.tps.never_not_equal(Pose("Pose(Point(1 1), 0.5)")), bool + ) + + +class TestTPoseRestrictions(TestTPose): + tps = TPoseSeq( + "[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]" + ) + + def test_at_pose(self): + result = self.tps.at(Pose("Pose(Point(1 1), 0.5)")) + assert result is None or isinstance(result, TPose) + + def test_minus_pose(self): + result = self.tps.minus(Pose("Pose(Point(1 1), 0.5)")) + assert result is None or isinstance(result, TPose) + + +class TestTPoseDistance(TestTPose): + tps = TPoseSeq( + "[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]" + ) + + def test_nearest_approach_distance(self): + assert isinstance( + self.tps.nearest_approach_distance(Pose("Pose(Point(0 0), 0.0)")), + float, + ) + + +class TestPose(TestPyMEOS): + p = Pose("Pose(Point(1 1), 0.5)") + + def test_string_constructor(self): + assert isinstance(self.p, Pose) + + def test_str(self): + assert str(self.p) == "POSE(POINT(1 1), 0.5)" + + def test_from_as_hexwkb(self): + assert self.p == Pose.from_hexwkb(self.p.as_hexwkb()) + + def test_copy(self): + other = copy(self.p) + assert self.p == other + assert self.p is not other + + def test_rotation(self): + assert isinstance(self.p.rotation(), float) + + def test_srid(self): + assert isinstance(self.p.srid(), int) + + def test_round(self): + assert isinstance(self.p.round(2), Pose) + + def test_comparisons(self): + other = Pose("Pose(Point(2 2), 0.3)") + assert self.p != other + assert self.p == copy(self.p) + assert isinstance(self.p.cmp(other), int) diff --git a/tests/main/trgeometry_test.py b/tests/main/trgeometry_test.py new file mode 100644 index 00000000..910e9f4d --- /dev/null +++ b/tests/main/trgeometry_test.py @@ -0,0 +1,198 @@ +from copy import copy +from datetime import datetime, timezone + +import pytest +from shapely import Polygon + +from pymeos import ( + TRgeometry, + TRgeometryInst, + TRgeometrySeq, + TRgeometrySeqSet, + TInterpolation, + TPoseInst, + TPoseSeq, + Pose, +) +from tests.conftest import TestPyMEOS + + +class TestTRgeometry(TestPyMEOS): + pass + + +class TestTRgeometryConstructors(TestTRgeometry): + geometry = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + tpi = TPoseInst("Pose(Point(1 1), 0.5)@2019-09-01") + tps = TPoseSeq( + "[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]" + ) + tri = TRgeometry.from_geometry_tpose(geometry, tpi) + trs = TRgeometry.from_geometry_tpose(geometry, tps) + + @pytest.mark.parametrize( + "temporal, expected_type", + [ + (tri, TRgeometryInst), + (trs, TRgeometrySeq), + ], + ids=["Instant", "Sequence"], + ) + def test_from_geometry_tpose_constructor(self, temporal, expected_type): + assert isinstance(temporal, expected_type) + + def test_instant_constructor(self): + inst = TRgeometryInst( + geometry=self.geometry, + pose=Pose("Pose(Point(1 1), 0.5)"), + timestamp="2019-09-01", + ) + assert isinstance(inst, TRgeometryInst) + + @pytest.mark.parametrize( + "temporal", + [tri, trs], + ids=["Instant", "Sequence"], + ) + def test_from_as_hexwkb_constructor(self, temporal): + assert temporal == temporal.__class__.from_hexwkb(temporal.as_hexwkb()) + + @pytest.mark.parametrize( + "temporal", + [tri, trs], + ids=["Instant", "Sequence"], + ) + def test_copy_constructor(self, temporal): + other = copy(temporal) + assert temporal == other + assert temporal is not other + + +class TestTRgeometryOutputs(TestTRgeometry): + geometry = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + tpi = TPoseInst("Pose(Point(1 1), 0.5)@2019-09-01") + tps = TPoseSeq( + "[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]" + ) + tri = TRgeometry.from_geometry_tpose(geometry, tpi) + trs = TRgeometry.from_geometry_tpose(geometry, tps) + + def test_str(self): + assert isinstance(str(self.tri), str) + + def test_repr(self): + assert isinstance(repr(self.tri), str) + + def test_as_wkt(self): + assert isinstance(self.trs.as_wkt(), str) + + def test_as_ewkt(self): + assert isinstance(self.trs.as_ewkt(), str) + + +class TestTRgeometryAccessors(TestTRgeometry): + geometry = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + tps = TPoseSeq( + "[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]" + ) + trs = TRgeometry.from_geometry_tpose(geometry, tps) + + def test_interpolation(self): + assert self.trs.interpolation() == TInterpolation.LINEAR + + def test_start_value(self): + assert self.trs.start_value() is not None + + def test_end_value(self): + assert self.trs.end_value() is not None + + def test_values(self): + assert self.trs.values() is not None + + def test_geometry(self): + assert self.trs.geometry() is not None + + def test_value_at_timestamp(self): + value = self.trs.value_at_timestamp( + datetime(2019, 9, 1, tzinfo=timezone.utc) + ) + assert value is not None + + def test_srid(self): + assert isinstance(self.trs.srid(), int) + + def test_to_tpose(self): + assert self.trs.to_tpose() is not None + + def test_to_tpoint(self): + assert self.trs.to_tpoint() is not None + + def test_rotation(self): + assert self.trs.rotation() is not None + + +class TestTRgeometryEverAlways(TestTRgeometry): + geometry = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + tps = TPoseSeq( + "[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]" + ) + trs = TRgeometry.from_geometry_tpose(geometry, tps) + + def test_never_not_equal(self): + assert isinstance(self.trs.never_not_equal(self.geometry), bool) + + +class TestTRgeometryRestrictions(TestTRgeometry): + geometry = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + tps = TPoseSeq( + "[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]" + ) + trs = TRgeometry.from_geometry_tpose(geometry, tps) + + def test_at_timestamp(self): + result = self.trs.at(datetime(2019, 9, 1, tzinfo=timezone.utc)) + assert result is None or isinstance(result, TRgeometry) + + def test_minus_timestamp(self): + result = self.trs.minus(datetime(2019, 9, 1, tzinfo=timezone.utc)) + assert result is None or isinstance(result, TRgeometry) + + +class TestTRgeometryDistance(TestTRgeometry): + geometry = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + tps = TPoseSeq( + "[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]" + ) + trs = TRgeometry.from_geometry_tpose(geometry, tps) + + def test_nearest_approach_distance(self): + assert isinstance( + self.trs.nearest_approach_distance( + Polygon([(5, 5), (6, 5), (6, 6), (5, 6)]) + ), + float, + ) + + +class TestTRgeometryGapMethods(TestTRgeometry): + geometry = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + tps = TPoseSeq( + "[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]" + ) + trs = TRgeometry.from_geometry_tpose(geometry, tps) + + def test_segments(self): + assert isinstance(self.trs.segments(), list) + + def test_to_instant(self): + assert self.trs.to_instant() is not None + + def test_delete(self): + r = self.trs.delete(datetime(2019, 9, 1, tzinfo=timezone.utc)) + assert r is None or isinstance(r, TRgeometry) + + def test_before_after(self): + b = self.trs.before(datetime(2019, 9, 2, tzinfo=timezone.utc)) + a = self.trs.after(datetime(2019, 9, 1, tzinfo=timezone.utc)) + assert b is None or isinstance(b, TRgeometry) + assert a is None or isinstance(a, TRgeometry)