From 006ed6d710c5c2320db8cc08be5e5ce2cd37d02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Zim=C3=A1nyi?= Date: Sat, 31 Oct 2026 22:21:25 +0100 Subject: [PATCH 1/2] Read and write every PyMEOS class in each format its SQL type has Every class has the readers and writers of the portable functions, each calling the MEOS function the SQL function of the same name reaches, as the catalog's typeEncodings states them: as_text and from_text for every class; as_wkb and as_hexwkb writing plain WKB, as the SQL asBinary and asHexWKB do, in the byte order an endian argument names ("NDR", "XDR", or "" for the machine's own) through wkb_variant_from_endian; and, for a type carrying an SRID, as_ewkt, as_ewkb and as_hexewkb with from_ewkt, from_ewkb and from_hexewkb. str is as_text for every class: each class's text writer is its as_text and its __str__ returns as_text(). The spatial temporal classes, TPoint, TCbuffer, TPose, TNpoint and TRgeometry, take the E forms from the mixin TSpatialIO (tspatial_as_ewkt, tspatial_as_ewkb, tspatial_as_hexewkb, temporal_from_wkb, temporal_from_hexwkb), TRgeometry keeping its trgeometry_as_ewkt. The spatial sets, GeoSet, PoseSet, CbufferSet and NpointSet, write spatialset_as_ewkb and spatialset_as_hexewkb and read set_from_wkb and set_from_hexwkb; STBox writes stbox_as_ewkb and stbox_as_hexewkb, its text carrying its SRID; Pose, Cbuffer, Npoint and Nsegment read and write through their own *_as_ewkb, *_as_hexewkb, *_from_wkb and *_from_hexwkb, Nsegment gaining its binary and E forms. Nsegment's text is nsegment_as_text and TNpoint's tspatial_as_text, the writers the SQL asText of nsegment and tnpoint reach. tests/io_forms_test.py checks, over a value of each of 29 classes, the text str and as_text write, its reading back through from_text, the byte-order marker of NDR and XDR WKB and their reading back, the EWKT of the 14 values carrying an SRID and the EWKB and hex EWKB round trips of those and STBox. Witness: for TGeomPointSeq('SRID=4326;[Point(1 1)@2001-01-01, Point(2 2)@2001-01-02]'), against PyMEOS master cfed5e3 as_hexwkb() writes 012E004EE6100000..., the extended form carrying SRID 4326 under the plain name; against this branch as_hexwkb() writes 012E000E02..., plain WKB reading back with SRID 0, as_hexewkb() writes 012E004EE6100000..., reading back with SRID 4326, and as_hexwkb("XDR") starts 00. Measured: 4841 tests, 117 of them in tests/io_forms_test.py; ruff check and ruff format report no finding. Why: a binding's object layer holds every value as its MEOS type, and its input and output are the portable functions with the MEOS function the SQL function reaches, so a value a binding writes reads back in PostgreSQL and every other binding, and the plain and E forms mean the same everywhere. --- pymeos/boxes/stbox.py | 108 ++++++++++++- pymeos/boxes/tbox.py | 44 ++++- pymeos/collections/base/set.py | 36 ++++- pymeos/collections/base/span.py | 36 ++++- pymeos/collections/base/spanset.py | 36 ++++- pymeos/collections/cbuffer/cbuffer.py | 116 +++++++++++++- pymeos/collections/cbuffer/cbufferset.py | 84 ++++++++++ pymeos/collections/geo/geoset.py | 89 ++++++++++ pymeos/collections/npoint/npoint.py | 116 +++++++++++++- pymeos/collections/npoint/npointset.py | 84 ++++++++++ pymeos/collections/npoint/nsegment.py | 187 +++++++++++++++++++++- pymeos/collections/number/floatset.py | 10 +- pymeos/collections/number/floatspan.py | 10 +- pymeos/collections/number/floatspanset.py | 10 +- pymeos/collections/number/intset.py | 10 +- pymeos/collections/number/intspan.py | 10 +- pymeos/collections/number/intspanset.py | 10 +- pymeos/collections/pose/pose.py | 116 +++++++++++++- pymeos/collections/pose/poseset.py | 84 ++++++++++ pymeos/collections/text/textset.py | 10 +- pymeos/collections/time/dateset.py | 10 +- pymeos/collections/time/datespan.py | 10 +- pymeos/collections/time/datespanset.py | 10 +- pymeos/collections/time/tstzset.py | 10 +- pymeos/collections/time/tstzspan.py | 10 +- pymeos/collections/time/tstzspanset.py | 10 +- pymeos/main/tbool.py | 8 +- pymeos/main/tcbuffer.py | 13 +- pymeos/main/tfloat.py | 8 +- pymeos/main/tint.py | 8 +- pymeos/main/tnpoint.py | 19 ++- pymeos/main/tpoint.py | 14 +- pymeos/main/tpose.py | 13 +- pymeos/main/trgeometry.py | 13 +- pymeos/main/ttext.py | 6 + pymeos/mixins/__init__.py | 2 + pymeos/mixins/spatial_io.py | 129 +++++++++++++++ pymeos/temporal/temporal.py | 55 +++++-- tests/io_forms_test.py | 150 +++++++++++++++++ 39 files changed, 1594 insertions(+), 110 deletions(-) create mode 100644 pymeos/mixins/spatial_io.py create mode 100644 tests/io_forms_test.py diff --git a/pymeos/boxes/stbox.py b/pymeos/boxes/stbox.py index 1a4dd2bc..334d5dd4 100644 --- a/pymeos/boxes/stbox.py +++ b/pymeos/boxes/stbox.py @@ -288,8 +288,94 @@ def from_expanding_bounding_box( raise TypeError(f"Operation not supported with type {value.__class__}") return STBox(_inner=stbox_expand_space(box, expansion)) + @staticmethod + def from_text(text: str) -> STBox: + """ + Returns a :class:`STBox` from its text, the inverse of :meth:`as_text`. + + Args: + text: The text representation. + + Returns: + A new :class:`STBox` instance. + + MEOS Functions: + stbox_in + """ + return STBox(string=text) + + @staticmethod + def from_ewkb(ewkb: bytes) -> STBox: + """ + Returns a :class:`STBox` from its EWKB, the inverse of :meth:`as_ewkb`. + + Args: + ewkb: The EWKB representation. + + Returns: + A new :class:`STBox` instance. + + MEOS Functions: + stbox_from_wkb + """ + return STBox(_inner=stbox_from_wkb(ewkb)) + + @staticmethod + def from_hexewkb(hexewkb: str) -> STBox: + """ + Returns a :class:`STBox` from its HEXEWKB, the inverse of :meth:`as_hexewkb`. + + Args: + hexewkb: The HEXEWKB representation. + + Returns: + A new :class:`STBox` instance. + + MEOS Functions: + stbox_from_hexwkb + """ + return STBox(_inner=stbox_from_hexwkb(hexewkb)) + + def as_ewkb(self, endian: str = "") -> bytes: + """ + Returns the EWKB representation of ``self``, carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`bytes` instance. + + MEOS Functions: + stbox_as_ewkb, wkb_variant_from_endian + """ + return stbox_as_ewkb(self._inner, wkb_variant_from_endian(endian)) + + def as_hexewkb(self, endian: str = "") -> str: + """ + Returns the HEXEWKB representation of ``self``, carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`str` instance. + + MEOS Functions: + stbox_as_hexewkb, wkb_variant_from_endian + """ + return stbox_as_hexewkb(self._inner, wkb_variant_from_endian(endian))[0] + # ------------------------- Output ---------------------------------------- - def __str__(self, max_decimals: int = 15): + def __str__(self) -> str: + """ + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self, max_decimals: int = 15) -> str: """ Returns a string representation of ``self``. @@ -313,30 +399,38 @@ def __repr__(self): """ return f"{self.__class__.__name__}({self})" - def as_wkb(self) -> bytes: + def as_wkb(self, endian: str = "") -> bytes: """ Returns the WKB representation of ``self``. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`str` object with the WKB representation of ``self``. MEOS Functions: - stbox_as_wkb + stbox_as_wkb, wkb_variant_from_endian """ - return stbox_as_wkb(self._inner, 4) + return stbox_as_wkb(self._inner, wkb_variant_from_endian(endian)) - def as_hexwkb(self) -> str: + def as_hexwkb(self, endian: str = "") -> str: """ Returns the WKB representation of ``self`` in hex-encoded ASCII. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. MEOS Functions: - stbox_as_hexwkb + stbox_as_hexwkb, wkb_variant_from_endian """ - return stbox_as_hexwkb(self._inner, -1)[0] + return stbox_as_hexwkb(self._inner, wkb_variant_from_endian(endian))[0] # ------------------------- Conversions ---------------------------------- def to_geometry(self, precision: int = 15) -> shp.BaseGeometry: diff --git a/pymeos/boxes/tbox.py b/pymeos/boxes/tbox.py index 734b462f..d10d971e 100644 --- a/pymeos/boxes/tbox.py +++ b/pymeos/boxes/tbox.py @@ -237,8 +237,30 @@ def from_tnumber(temporal: TNumber) -> TBox: """ return TBox(_inner=tnumber_to_tbox(temporal._inner)) + @staticmethod + def from_text(text: str) -> TBox: + """ + Returns a :class:`TBox` from its text, the inverse of :meth:`as_text`. + + Args: + text: The text representation. + + Returns: + A new :class:`TBox` instance. + + MEOS Functions: + tbox_in + """ + return TBox(string=text) + # ------------------------- Output ---------------------------------------- - def __str__(self, max_decimals: int = 15): + def __str__(self) -> str: + """ + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self, max_decimals: int = 15) -> str: """ Returns a string representation of ``self``. @@ -262,30 +284,38 @@ def __repr__(self): """ return f"{self.__class__.__name__}({self})" - def as_wkb(self) -> bytes: + def as_wkb(self, endian: str = "") -> bytes: """ Returns the WKB representation of ``self``. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`str` object with the WKB representation of ``self``. MEOS Functions: - tbox_as_wkb + tbox_as_wkb, wkb_variant_from_endian """ - return tbox_as_wkb(self._inner, 4) + return tbox_as_wkb(self._inner, wkb_variant_from_endian(endian)) - def as_hexwkb(self) -> str: + def as_hexwkb(self, endian: str = "") -> str: """ Returns the WKB representation of ``self`` in hex-encoded ASCII. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. MEOS Functions: - tbox_as_hexwkb + tbox_as_hexwkb, wkb_variant_from_endian """ - return tbox_as_hexwkb(self._inner, -1)[0] + return tbox_as_hexwkb(self._inner, wkb_variant_from_endian(endian))[0] # ------------------------- Conversions ---------------------------------- def to_floatspan(self) -> FloatSpan: diff --git a/pymeos/collections/base/set.py b/pymeos/collections/base/set.py index 50391327..5568f536 100644 --- a/pymeos/collections/base/set.py +++ b/pymeos/collections/base/set.py @@ -96,6 +96,22 @@ def from_hexwkb(cls: type[Self], hexwkb: str) -> Self: return _CollectionFactory.create_collection(set_from_hexwkb(hexwkb)) + @classmethod + def from_text(cls: type[Self], text: str) -> Self: + """ + Returns a :class:`Set` from its text, the inverse of :meth:`as_text`. + + Args: + text: The text representation. + + Returns: + A new :class:`Set` instance. + + MEOS Functions: + _in + """ + return cls(string=text) + # ------------------------- Output ---------------------------------------- @abstractmethod def __str__(self) -> str: @@ -119,27 +135,35 @@ def __repr__(self) -> str: """ return f"{self.__class__.__name__}({self})" - def as_wkb(self) -> bytes: + def as_wkb(self, endian: str = "") -> bytes: """ Returns the WKB representation of ``self``. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`str` object with the WKB representation of ``self``. MEOS Functions: - set_as_wkb + set_as_wkb, wkb_variant_from_endian """ - return set_as_wkb(self._inner, 4) + return set_as_wkb(self._inner, wkb_variant_from_endian(endian)) - def as_hexwkb(self) -> str: + def as_hexwkb(self, endian: str = "") -> str: """ Returns the WKB representation of ``self`` in hex-encoded ASCII. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. MEOS Functions: - set_as_hexwkb + set_as_hexwkb, wkb_variant_from_endian """ - return set_as_hexwkb(self._inner, -1)[0] + return set_as_hexwkb(self._inner, wkb_variant_from_endian(endian))[0] # ------------------------- Conversions ----------------------------------- @overload diff --git a/pymeos/collections/base/span.py b/pymeos/collections/base/span.py index 23f1cbb4..55ffdc00 100644 --- a/pymeos/collections/base/span.py +++ b/pymeos/collections/base/span.py @@ -96,6 +96,22 @@ def from_hexwkb(cls: type[Self], hexwkb: str) -> Self: result = span_from_hexwkb(hexwkb) return cls(_inner=result) + @classmethod + def from_text(cls: type[Self], text: str) -> Self: + """ + Returns a :class:`Span` from its text, the inverse of :meth:`as_text`. + + Args: + text: The text representation. + + Returns: + A new :class:`Span` instance. + + MEOS Functions: + _in + """ + return cls(string=text) + # ------------------------- Output ---------------------------------------- @abstractmethod def __str__(self) -> str: @@ -116,30 +132,38 @@ def __repr__(self): """ return f"{self.__class__.__name__}({self})" - def as_wkb(self) -> bytes: + def as_wkb(self, endian: str = "") -> bytes: """ Returns the WKB representation of ``self``. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`str` object with the WKB representation of ``self``. MEOS Functions: - span_as_wkb + span_as_wkb, wkb_variant_from_endian """ - return span_as_wkb(self._inner, 4) + return span_as_wkb(self._inner, wkb_variant_from_endian(endian)) - def as_hexwkb(self) -> str: + def as_hexwkb(self, endian: str = "") -> str: """ Returns the WKB representation of ``self`` in hex-encoded ASCII. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. MEOS Functions: - span_as_hexwkb + span_as_hexwkb, wkb_variant_from_endian """ - return span_as_hexwkb(self._inner, -1)[0] + return span_as_hexwkb(self._inner, wkb_variant_from_endian(endian))[0] # ------------------------- Conversions ----------------------------------- @abstractmethod diff --git a/pymeos/collections/base/spanset.py b/pymeos/collections/base/spanset.py index 9b63c0d0..39dcb096 100644 --- a/pymeos/collections/base/spanset.py +++ b/pymeos/collections/base/spanset.py @@ -92,6 +92,22 @@ def from_hexwkb(cls: type[Self], hexwkb: str) -> Self: result = spanset_from_hexwkb(hexwkb) return cls(_inner=result) + @classmethod + def from_text(cls: type[Self], text: str) -> Self: + """ + Returns a :class:`SpanSet` from its text, the inverse of :meth:`as_text`. + + Args: + text: The text representation. + + Returns: + A new :class:`SpanSet` instance. + + MEOS Functions: + _in + """ + return cls(string=text) + # ------------------------- Output ---------------------------------------- @abstractmethod def __str__(self): @@ -115,28 +131,36 @@ def __repr__(self): """ return f"{self.__class__.__name__}({self})" - def as_wkb(self) -> bytes: + def as_wkb(self, endian: str = "") -> bytes: """ Returns the WKB representation of ``self``. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`str` object with the WKB representation of ``self``. MEOS Functions: - spanset_as_wkb + spanset_as_wkb, wkb_variant_from_endian """ - return spanset_as_wkb(self._inner, 4) + return spanset_as_wkb(self._inner, wkb_variant_from_endian(endian)) - def as_hexwkb(self) -> str: + def as_hexwkb(self, endian: str = "") -> str: """ Returns the WKB representation of ``self`` in hex-encoded ASCII. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. MEOS Functions: - spanset_as_hexwkb + spanset_as_hexwkb, wkb_variant_from_endian """ - return spanset_as_hexwkb(self._inner, -1)[0] + return spanset_as_hexwkb(self._inner, wkb_variant_from_endian(endian))[0] # ------------------------- Conversions ----------------------------------- @abstractmethod diff --git a/pymeos/collections/cbuffer/cbuffer.py b/pymeos/collections/cbuffer/cbuffer.py index 234e23ee..3d549e25 100644 --- a/pymeos/collections/cbuffer/cbuffer.py +++ b/pymeos/collections/cbuffer/cbuffer.py @@ -170,30 +170,134 @@ def as_ewkt(self, max_decimals: int = 15) -> str: """ return cbuffer_as_ewkt(self._inner, max_decimals) - def as_wkb(self) -> bytes: + def as_wkb(self, endian: str = "") -> bytes: """ Returns the WKB representation of ``self``. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`bytes` object with the WKB representation of ``self``. MEOS Functions: - cbuffer_as_wkb + cbuffer_as_wkb, wkb_variant_from_endian """ - return cbuffer_as_wkb(self._inner, 4) + return cbuffer_as_wkb(self._inner, wkb_variant_from_endian(endian)) - def as_hexwkb(self) -> str: + def as_hexwkb(self, endian: str = "") -> str: """ Returns the WKB representation of ``self`` in hex-encoded ASCII. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. MEOS Functions: - cbuffer_as_hexwkb + cbuffer_as_hexwkb, wkb_variant_from_endian + """ + return cbuffer_as_hexwkb(self._inner, wkb_variant_from_endian(endian))[0] + + @staticmethod + def from_text(string: str) -> Cbuffer: + """ + Returns a :class:`Cbuffer` from its text, the inverse of :meth:`as_text`. + + Args: + string: The text representation. + + Returns: + A new :class:`Cbuffer` instance. + + MEOS Functions: + cbuffer_in + """ + return Cbuffer(_inner=cbuffer_in(string)) + + @staticmethod + def from_ewkt(ewkt: str) -> Cbuffer: + """ + Returns a :class:`Cbuffer` from its EWKT, the inverse of :meth:`as_ewkt`. + + Args: + ewkt: The EWKT representation. + + Returns: + A new :class:`Cbuffer` instance. + + MEOS Functions: + cbuffer_in + """ + return Cbuffer(_inner=cbuffer_in(ewkt)) + + @staticmethod + def from_ewkb(ewkb: bytes) -> Cbuffer: + """ + Returns a :class:`Cbuffer` from its EWKB, the inverse of :meth:`as_ewkb`. + + Args: + ewkb: The EWKB representation. + + Returns: + A new :class:`Cbuffer` instance. + + MEOS Functions: + cbuffer_from_wkb + """ + return Cbuffer(_inner=cbuffer_from_wkb(ewkb)) + + @staticmethod + def from_hexewkb(hexewkb: str) -> Cbuffer: + """ + Returns a :class:`Cbuffer` from its hex-encoded EWKB, the inverse of :meth:`as_hexewkb`. + + Args: + hexewkb: The HEXEWKB representation. + + Returns: + A new :class:`Cbuffer` instance. + + MEOS Functions: + cbuffer_from_hexwkb + """ + return Cbuffer(_inner=cbuffer_from_hexwkb(hexewkb)) + + def as_ewkb(self, endian: str = "") -> bytes: + """ + Returns its Extended Well-Known Binary, its WKB carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`bytes` instance. + + MEOS Functions: + cbuffer_as_ewkb, wkb_variant_from_endian + """ + return cbuffer_as_ewkb(self._inner, wkb_variant_from_endian(endian)) + + def as_hexewkb(self, endian: str = "") -> str: + """ + Returns its Extended Well-Known Binary as a hex-encoded string. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`str` instance. + + MEOS Functions: + cbuffer_as_hexewkb, wkb_variant_from_endian """ - return cbuffer_as_hexwkb(self._inner, -1)[0] + return cbuffer_as_hexewkb(self._inner, wkb_variant_from_endian(endian))[0] # ------------------------- Conversions ----------------------------------- def to_geometry(self, precision: int = 15) -> shp.BaseGeometry: diff --git a/pymeos/collections/cbuffer/cbufferset.py b/pymeos/collections/cbuffer/cbufferset.py index 7be001f1..c5577d70 100644 --- a/pymeos/collections/cbuffer/cbufferset.py +++ b/pymeos/collections/cbuffer/cbufferset.py @@ -34,6 +34,90 @@ class CbufferSet(Set[Cbuffer]): # ------------------------- Constructors ---------------------------------- + @classmethod + def from_ewkb(cls: type[Self], ewkb: bytes) -> Self: + """ + Returns a :class:`CbufferSet` from its EWKB, the inverse of :meth:`as_ewkb`. + + Args: + ewkb: The EWKB representation. + + Returns: + A new :class:`CbufferSet` instance. + + MEOS Functions: + set_from_wkb + """ + from ...factory import _CollectionFactory + + return _CollectionFactory.create_collection(set_from_wkb(ewkb)) + + @classmethod + def from_hexewkb(cls: type[Self], hexewkb: str) -> Self: + """ + Returns a :class:`CbufferSet` from its HEXEWKB, the inverse of :meth:`as_hexewkb`. + + Args: + hexewkb: The HEXEWKB representation. + + Returns: + A new :class:`CbufferSet` instance. + + MEOS Functions: + set_from_hexwkb + """ + from ...factory import _CollectionFactory + + return _CollectionFactory.create_collection(set_from_hexwkb(hexewkb)) + + @classmethod + def from_ewkt(cls: type[Self], ewkt: str) -> Self: + """ + Returns a :class:`CbufferSet` from its EWKT, the inverse of :meth:`as_ewkt`. + + Args: + ewkt: The EWKT representation. + + Returns: + A new :class:`CbufferSet` instance. + + MEOS Functions: + _in + """ + return cls.from_text(ewkt) + + def as_ewkb(self, endian: str = "") -> bytes: + """ + Returns the EWKB representation of ``self``, carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`bytes` instance. + + MEOS Functions: + spatialset_as_ewkb, wkb_variant_from_endian + """ + return spatialset_as_ewkb(self._inner, wkb_variant_from_endian(endian)) + + def as_hexewkb(self, endian: str = "") -> str: + """ + Returns the HEXEWKB representation of ``self``, carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`str` instance. + + MEOS Functions: + spatialset_as_hexewkb, wkb_variant_from_endian + """ + return spatialset_as_hexewkb(self._inner, wkb_variant_from_endian(endian))[0] + # ------------------------- Output ---------------------------------------- def __str__(self, max_decimals: int = 15): diff --git a/pymeos/collections/geo/geoset.py b/pymeos/collections/geo/geoset.py index fd3efde4..a44b6c9c 100644 --- a/pymeos/collections/geo/geoset.py +++ b/pymeos/collections/geo/geoset.py @@ -22,12 +22,17 @@ minus_geo_set, minus_set_geo, minus_set_set, + set_from_hexwkb, + set_from_wkb, set_round, + spatialset_as_ewkb, spatialset_as_ewkt, + spatialset_as_hexewkb, spatialset_as_text, spatialset_srid, union_set_geo, union_set_set, + wkb_variant_from_endian, ) from ..base import Set @@ -42,6 +47,90 @@ class GeoSet(Set[shp.Geometry], ABC): # ------------------------- Constructors ---------------------------------- + @classmethod + def from_ewkb(cls: type[Self], ewkb: bytes) -> Self: + """ + Returns a :class:`GeoSet` from its EWKB, the inverse of :meth:`as_ewkb`. + + Args: + ewkb: The EWKB representation. + + Returns: + A new :class:`GeoSet` instance. + + MEOS Functions: + set_from_wkb + """ + from ...factory import _CollectionFactory + + return _CollectionFactory.create_collection(set_from_wkb(ewkb)) + + @classmethod + def from_hexewkb(cls: type[Self], hexewkb: str) -> Self: + """ + Returns a :class:`GeoSet` from its HEXEWKB, the inverse of :meth:`as_hexewkb`. + + Args: + hexewkb: The HEXEWKB representation. + + Returns: + A new :class:`GeoSet` instance. + + MEOS Functions: + set_from_hexwkb + """ + from ...factory import _CollectionFactory + + return _CollectionFactory.create_collection(set_from_hexwkb(hexewkb)) + + @classmethod + def from_ewkt(cls: type[Self], ewkt: str) -> Self: + """ + Returns a :class:`GeoSet` from its EWKT, the inverse of :meth:`as_ewkt`. + + Args: + ewkt: The EWKT representation. + + Returns: + A new :class:`GeoSet` instance. + + MEOS Functions: + _in + """ + return cls.from_text(ewkt) + + def as_ewkb(self, endian: str = "") -> bytes: + """ + Returns the EWKB representation of ``self``, carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`bytes` instance. + + MEOS Functions: + spatialset_as_ewkb, wkb_variant_from_endian + """ + return spatialset_as_ewkb(self._inner, wkb_variant_from_endian(endian)) + + def as_hexewkb(self, endian: str = "") -> str: + """ + Returns the HEXEWKB representation of ``self``, carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`str` instance. + + MEOS Functions: + spatialset_as_hexewkb, wkb_variant_from_endian + """ + return spatialset_as_hexewkb(self._inner, wkb_variant_from_endian(endian))[0] + # ------------------------- Output ---------------------------------------- def __str__(self, max_decimals: int = 15): diff --git a/pymeos/collections/npoint/npoint.py b/pymeos/collections/npoint/npoint.py index 138eeaca..67f9ab11 100644 --- a/pymeos/collections/npoint/npoint.py +++ b/pymeos/collections/npoint/npoint.py @@ -138,30 +138,38 @@ def __repr__(self): """ return f"{self.__class__.__name__}({self})" - def as_wkb(self) -> bytes: + def as_wkb(self, endian: str = "") -> bytes: """ Returns the WKB representation of ``self``. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`bytes` object with the WKB representation of ``self``. MEOS Functions: - npoint_as_wkb + npoint_as_wkb, wkb_variant_from_endian """ - return npoint_as_wkb(self._inner, 4) + return npoint_as_wkb(self._inner, wkb_variant_from_endian(endian)) - def as_hexwkb(self) -> str: + def as_hexwkb(self, endian: str = "") -> str: """ Returns the WKB representation of ``self`` in hex-encoded ASCII. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. MEOS Functions: - npoint_as_hexwkb + npoint_as_hexwkb, wkb_variant_from_endian """ - return npoint_as_hexwkb(self._inner, -1)[0] + return npoint_as_hexwkb(self._inner, wkb_variant_from_endian(endian))[0] def as_ewkt(self, max_decimals: int = 15) -> str: """ @@ -195,6 +203,102 @@ def as_text(self, max_decimals: int = 15) -> str: """ return npoint_as_text(self._inner, max_decimals) + @staticmethod + def from_text(string: str) -> Npoint: + """ + Returns a :class:`Npoint` from its text, the inverse of :meth:`as_text`. + + Args: + string: The text representation. + + Returns: + A new :class:`Npoint` instance. + + MEOS Functions: + npoint_in + """ + return Npoint(_inner=npoint_in(string)) + + @staticmethod + def from_ewkt(ewkt: str) -> Npoint: + """ + Returns a :class:`Npoint` from its EWKT, the inverse of :meth:`as_ewkt`. + + Args: + ewkt: The EWKT representation. + + Returns: + A new :class:`Npoint` instance. + + MEOS Functions: + npoint_in + """ + return Npoint(_inner=npoint_in(ewkt)) + + @staticmethod + def from_ewkb(ewkb: bytes) -> Npoint: + """ + Returns a :class:`Npoint` from its EWKB, the inverse of :meth:`as_ewkb`. + + Args: + ewkb: The EWKB representation. + + Returns: + A new :class:`Npoint` instance. + + MEOS Functions: + npoint_from_wkb + """ + return Npoint(_inner=npoint_from_wkb(ewkb)) + + @staticmethod + def from_hexewkb(hexewkb: str) -> Npoint: + """ + Returns a :class:`Npoint` from its hex-encoded EWKB, the inverse of :meth:`as_hexewkb`. + + Args: + hexewkb: The HEXEWKB representation. + + Returns: + A new :class:`Npoint` instance. + + MEOS Functions: + npoint_from_hexwkb + """ + return Npoint(_inner=npoint_from_hexwkb(hexewkb)) + + def as_ewkb(self, endian: str = "") -> bytes: + """ + Returns its Extended Well-Known Binary, its WKB carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`bytes` instance. + + MEOS Functions: + npoint_as_ewkb, wkb_variant_from_endian + """ + return npoint_as_ewkb(self._inner, wkb_variant_from_endian(endian)) + + def as_hexewkb(self, endian: str = "") -> str: + """ + Returns its Extended Well-Known Binary as a hex-encoded string. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`str` instance. + + MEOS Functions: + npoint_as_hexewkb, wkb_variant_from_endian + """ + return npoint_as_hexewkb(self._inner, wkb_variant_from_endian(endian))[0] + # ------------------------- Conversions ----------------------------------- def to_geometry(self, precision: int = 15) -> shp.BaseGeometry: """ diff --git a/pymeos/collections/npoint/npointset.py b/pymeos/collections/npoint/npointset.py index a2c1b918..5c32de10 100644 --- a/pymeos/collections/npoint/npointset.py +++ b/pymeos/collections/npoint/npointset.py @@ -34,6 +34,90 @@ class NpointSet(Set[Npoint]): # ------------------------- Constructors ---------------------------------- + @classmethod + def from_ewkb(cls: type[Self], ewkb: bytes) -> Self: + """ + Returns a :class:`NpointSet` from its EWKB, the inverse of :meth:`as_ewkb`. + + Args: + ewkb: The EWKB representation. + + Returns: + A new :class:`NpointSet` instance. + + MEOS Functions: + set_from_wkb + """ + from ...factory import _CollectionFactory + + return _CollectionFactory.create_collection(set_from_wkb(ewkb)) + + @classmethod + def from_hexewkb(cls: type[Self], hexewkb: str) -> Self: + """ + Returns a :class:`NpointSet` from its HEXEWKB, the inverse of :meth:`as_hexewkb`. + + Args: + hexewkb: The HEXEWKB representation. + + Returns: + A new :class:`NpointSet` instance. + + MEOS Functions: + set_from_hexwkb + """ + from ...factory import _CollectionFactory + + return _CollectionFactory.create_collection(set_from_hexwkb(hexewkb)) + + @classmethod + def from_ewkt(cls: type[Self], ewkt: str) -> Self: + """ + Returns a :class:`NpointSet` from its EWKT, the inverse of :meth:`as_ewkt`. + + Args: + ewkt: The EWKT representation. + + Returns: + A new :class:`NpointSet` instance. + + MEOS Functions: + _in + """ + return cls.from_text(ewkt) + + def as_ewkb(self, endian: str = "") -> bytes: + """ + Returns the EWKB representation of ``self``, carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`bytes` instance. + + MEOS Functions: + spatialset_as_ewkb, wkb_variant_from_endian + """ + return spatialset_as_ewkb(self._inner, wkb_variant_from_endian(endian)) + + def as_hexewkb(self, endian: str = "") -> str: + """ + Returns the HEXEWKB representation of ``self``, carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`str` instance. + + MEOS Functions: + spatialset_as_hexewkb, wkb_variant_from_endian + """ + return spatialset_as_hexewkb(self._inner, wkb_variant_from_endian(endian))[0] + # ------------------------- Output ---------------------------------------- def __str__(self, max_decimals: int = 15): """ diff --git a/pymeos/collections/npoint/nsegment.py b/pymeos/collections/npoint/nsegment.py index e0ec2989..06547629 100644 --- a/pymeos/collections/npoint/nsegment.py +++ b/pymeos/collections/npoint/nsegment.py @@ -59,14 +59,20 @@ def __copy__(self) -> Nsegment: return Nsegment(_inner=nsegment_round(self._inner, 100)) # ------------------------- Output ---------------------------------------- - def __str__(self, max_decimals: int = 15) -> str: + def __str__(self) -> str: + """ + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self, max_decimals: int = 15) -> str: """ Returns the string representation of ``self``. MEOS Functions: - nsegment_out + nsegment_as_text """ - return nsegment_out(self._inner, max_decimals) + return nsegment_as_text(self._inner, max_decimals) def __repr__(self) -> str: return f"{self.__class__.__name__}({self})" @@ -108,6 +114,181 @@ def srid(self) -> int: """ return nsegment_srid(self._inner) + @staticmethod + def from_text(string: str) -> Nsegment: + """ + Returns a :class:`Nsegment` from its text, the inverse of :meth:`as_text`. + + Args: + string: The text representation. + + Returns: + A new :class:`Nsegment` instance. + + MEOS Functions: + nsegment_in + """ + return Nsegment(_inner=nsegment_in(string)) + + @staticmethod + def from_ewkt(ewkt: str) -> Nsegment: + """ + Returns a :class:`Nsegment` from its EWKT, the inverse of :meth:`as_ewkt`. + + Args: + ewkt: The EWKT representation. + + Returns: + A new :class:`Nsegment` instance. + + MEOS Functions: + nsegment_in + """ + return Nsegment(_inner=nsegment_in(ewkt)) + + @staticmethod + def from_wkb(wkb: bytes) -> Nsegment: + """ + Returns a :class:`Nsegment` from its WKB, the inverse of :meth:`as_wkb`. + + Args: + wkb: The WKB representation. + + Returns: + A new :class:`Nsegment` instance. + + MEOS Functions: + nsegment_from_wkb + """ + return Nsegment(_inner=nsegment_from_wkb(wkb)) + + @staticmethod + def from_hexwkb(hexwkb: str) -> Nsegment: + """ + Returns a :class:`Nsegment` from its hex-encoded WKB, the inverse of :meth:`as_hexwkb`. + + Args: + hexwkb: The HEXWKB representation. + + Returns: + A new :class:`Nsegment` instance. + + MEOS Functions: + nsegment_from_hexwkb + """ + return Nsegment(_inner=nsegment_from_hexwkb(hexwkb)) + + @staticmethod + def from_ewkb(ewkb: bytes) -> Nsegment: + """ + Returns a :class:`Nsegment` from its EWKB, the inverse of :meth:`as_ewkb`. + + Args: + ewkb: The EWKB representation. + + Returns: + A new :class:`Nsegment` instance. + + MEOS Functions: + nsegment_from_wkb + """ + return Nsegment(_inner=nsegment_from_wkb(ewkb)) + + @staticmethod + def from_hexewkb(hexewkb: str) -> Nsegment: + """ + Returns a :class:`Nsegment` from its hex-encoded EWKB, the inverse of :meth:`as_hexewkb`. + + Args: + hexewkb: The HEXEWKB representation. + + Returns: + A new :class:`Nsegment` instance. + + MEOS Functions: + nsegment_from_hexwkb + """ + return Nsegment(_inner=nsegment_from_hexwkb(hexewkb)) + + def as_ewkt(self, max_decimals: int = 15) -> str: + """ + Returns its Extended Well-Known Text, its text preceded by its SRID. + + Args: + max_decimals: The number of decimal places of the coordinates. + + Returns: + A :class:`str` instance. + + MEOS Functions: + nsegment_as_ewkt + """ + return nsegment_as_ewkt(self._inner, max_decimals) + + def as_wkb(self, endian: str = "") -> bytes: + """ + Returns its plain Well-Known Binary, as the SQL ``asBinary`` writes it. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`bytes` instance. + + MEOS Functions: + nsegment_as_wkb, wkb_variant_from_endian + """ + return nsegment_as_wkb(self._inner, wkb_variant_from_endian(endian)) + + def as_hexwkb(self, endian: str = "") -> str: + """ + Returns its plain Well-Known Binary as a hex-encoded string, as the SQL ``asHexWKB`` writes it. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`str` instance. + + MEOS Functions: + nsegment_as_hexwkb, wkb_variant_from_endian + """ + return nsegment_as_hexwkb(self._inner, wkb_variant_from_endian(endian))[0] + + def as_ewkb(self, endian: str = "") -> bytes: + """ + Returns its Extended Well-Known Binary, its WKB carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`bytes` instance. + + MEOS Functions: + nsegment_as_ewkb, wkb_variant_from_endian + """ + return nsegment_as_ewkb(self._inner, wkb_variant_from_endian(endian)) + + def as_hexewkb(self, endian: str = "") -> str: + """ + Returns its Extended Well-Known Binary as a hex-encoded string. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`str` instance. + + MEOS Functions: + nsegment_as_hexewkb, wkb_variant_from_endian + """ + return nsegment_as_hexewkb(self._inner, wkb_variant_from_endian(endian))[0] + # ------------------------- Conversions ----------------------------------- def to_geometry(self, precision: int = 15) -> shp.BaseGeometry: """ diff --git a/pymeos/collections/number/floatset.py b/pymeos/collections/number/floatset.py index 23a77774..2d67661f 100644 --- a/pymeos/collections/number/floatset.py +++ b/pymeos/collections/number/floatset.py @@ -64,9 +64,15 @@ class FloatSet(Set[float]): # ------------------------- Output ---------------------------------------- - def __str__(self, max_decimals: int = 15): + def __str__(self) -> str: """ - Return the string representation of the content of ``self``. + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self, max_decimals: int = 15) -> str: + """ + Return the text representation of the content of ``self``. Returns: A new :class:`str` instance diff --git a/pymeos/collections/number/floatspan.py b/pymeos/collections/number/floatspan.py index 0750e220..4ca773f6 100644 --- a/pymeos/collections/number/floatspan.py +++ b/pymeos/collections/number/floatspan.py @@ -70,9 +70,15 @@ class FloatSpan(Span[float]): _make_function = floatspan_make # ------------------------- Output ---------------------------------------- - def __str__(self, max_decimals: int = 15) -> str: + def __str__(self) -> str: """ - Return the string representation of the content of ``self``. + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self, max_decimals: int = 15) -> str: + """ + Return the text representation of the content of ``self``. Returns: A new :class:`str` instance diff --git a/pymeos/collections/number/floatspanset.py b/pymeos/collections/number/floatspanset.py index 911411dc..3592dd94 100644 --- a/pymeos/collections/number/floatspanset.py +++ b/pymeos/collections/number/floatspanset.py @@ -58,9 +58,15 @@ class FloatSpanSet(SpanSet[float]): _parse_value_function = lambda span: floatspanset_in(span)[0] if isinstance(span, str) else span._inner[0] # ------------------------- Output ---------------------------------------- - def __str__(self, max_decimals: int = 15): + def __str__(self) -> str: """ - Return the string representation of the content of ``self``. + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self, max_decimals: int = 15) -> str: + """ + Return the text representation of the content of ``self``. Returns: A new :class:`str` instance diff --git a/pymeos/collections/number/intset.py b/pymeos/collections/number/intset.py index 299ad444..3eb96247 100644 --- a/pymeos/collections/number/intset.py +++ b/pymeos/collections/number/intset.py @@ -63,9 +63,15 @@ class IntSet(Set[int]): # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: """ - Return the string representation of the content of ``self``. + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self) -> str: + """ + Return the text representation of the content of ``self``. Returns: A new :class:`str` instance diff --git a/pymeos/collections/number/intspan.py b/pymeos/collections/number/intspan.py index a42238d8..fab27b5f 100644 --- a/pymeos/collections/number/intspan.py +++ b/pymeos/collections/number/intspan.py @@ -69,9 +69,15 @@ class IntSpan(Span[int]): _make_function = intspan_make # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: """ - Return the string representation of the content of ``self``. + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self) -> str: + """ + Return the text representation of the content of ``self``. Returns: A new :class:`str` instance diff --git a/pymeos/collections/number/intspanset.py b/pymeos/collections/number/intspanset.py index f7d13967..df91ff15 100644 --- a/pymeos/collections/number/intspanset.py +++ b/pymeos/collections/number/intspanset.py @@ -58,9 +58,15 @@ class IntSpanSet(SpanSet[int]): _parse_value_function = lambda span: intspanset_in(span)[0] if isinstance(span, str) else span._inner[0] # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: """ - Return the string representation of the content of ``self``. + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self) -> str: + """ + Return the text representation of the content of ``self``. Returns: A new :class:`str` instance diff --git a/pymeos/collections/pose/pose.py b/pymeos/collections/pose/pose.py index 0dd806a4..991201dd 100644 --- a/pymeos/collections/pose/pose.py +++ b/pymeos/collections/pose/pose.py @@ -186,30 +186,134 @@ def as_ewkt(self, max_decimals: int = 15) -> str: """ return pose_as_ewkt(self._inner, max_decimals) - def as_wkb(self) -> bytes: + def as_wkb(self, endian: str = "") -> bytes: """ Returns the WKB representation of ``self``. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`bytes` object with the WKB representation of ``self``. MEOS Functions: - pose_as_wkb + pose_as_wkb, wkb_variant_from_endian """ - return pose_as_wkb(self._inner, 4) + return pose_as_wkb(self._inner, wkb_variant_from_endian(endian)) - def as_hexwkb(self) -> str: + def as_hexwkb(self, endian: str = "") -> str: """ Returns the WKB representation of ``self`` in hex-encoded ASCII. + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + Returns: A :class:`str` object with the WKB representation of ``self`` in hex-encoded ASCII. MEOS Functions: - pose_as_hexwkb + pose_as_hexwkb, wkb_variant_from_endian + """ + return pose_as_hexwkb(self._inner, wkb_variant_from_endian(endian))[0] + + @staticmethod + def from_text(string: str) -> Pose: + """ + Returns a :class:`Pose` from its text, the inverse of :meth:`as_text`. + + Args: + string: The text representation. + + Returns: + A new :class:`Pose` instance. + + MEOS Functions: + pose_from_text + """ + return Pose(_inner=pose_from_text(string)) + + @staticmethod + def from_ewkt(ewkt: str) -> Pose: + """ + Returns a :class:`Pose` from its EWKT, the inverse of :meth:`as_ewkt`. + + Args: + ewkt: The EWKT representation. + + Returns: + A new :class:`Pose` instance. + + MEOS Functions: + pose_from_text + """ + return Pose(_inner=pose_from_text(ewkt)) + + @staticmethod + def from_ewkb(ewkb: bytes) -> Pose: + """ + Returns a :class:`Pose` from its EWKB, the inverse of :meth:`as_ewkb`. + + Args: + ewkb: The EWKB representation. + + Returns: + A new :class:`Pose` instance. + + MEOS Functions: + pose_from_wkb + """ + return Pose(_inner=pose_from_wkb(ewkb)) + + @staticmethod + def from_hexewkb(hexewkb: str) -> Pose: + """ + Returns a :class:`Pose` from its hex-encoded EWKB, the inverse of :meth:`as_hexewkb`. + + Args: + hexewkb: The HEXEWKB representation. + + Returns: + A new :class:`Pose` instance. + + MEOS Functions: + pose_from_hexwkb + """ + return Pose(_inner=pose_from_hexwkb(hexewkb)) + + def as_ewkb(self, endian: str = "") -> bytes: + """ + Returns its Extended Well-Known Binary, its WKB carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`bytes` instance. + + MEOS Functions: + pose_as_ewkb, wkb_variant_from_endian + """ + return pose_as_ewkb(self._inner, wkb_variant_from_endian(endian)) + + def as_hexewkb(self, endian: str = "") -> str: + """ + Returns its Extended Well-Known Binary as a hex-encoded string. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`str` instance. + + MEOS Functions: + pose_as_hexewkb, wkb_variant_from_endian """ - return pose_as_hexwkb(self._inner, -1)[0] + return pose_as_hexewkb(self._inner, wkb_variant_from_endian(endian))[0] # ------------------------- Conversions ----------------------------------- def to_geometry(self, precision: int = 15) -> shp.BaseGeometry: diff --git a/pymeos/collections/pose/poseset.py b/pymeos/collections/pose/poseset.py index 5162a67a..f1b443a3 100644 --- a/pymeos/collections/pose/poseset.py +++ b/pymeos/collections/pose/poseset.py @@ -34,6 +34,90 @@ class PoseSet(Set[Pose]): # ------------------------- Constructors ---------------------------------- + @classmethod + def from_ewkb(cls: type[Self], ewkb: bytes) -> Self: + """ + Returns a :class:`PoseSet` from its EWKB, the inverse of :meth:`as_ewkb`. + + Args: + ewkb: The EWKB representation. + + Returns: + A new :class:`PoseSet` instance. + + MEOS Functions: + set_from_wkb + """ + from ...factory import _CollectionFactory + + return _CollectionFactory.create_collection(set_from_wkb(ewkb)) + + @classmethod + def from_hexewkb(cls: type[Self], hexewkb: str) -> Self: + """ + Returns a :class:`PoseSet` from its HEXEWKB, the inverse of :meth:`as_hexewkb`. + + Args: + hexewkb: The HEXEWKB representation. + + Returns: + A new :class:`PoseSet` instance. + + MEOS Functions: + set_from_hexwkb + """ + from ...factory import _CollectionFactory + + return _CollectionFactory.create_collection(set_from_hexwkb(hexewkb)) + + @classmethod + def from_ewkt(cls: type[Self], ewkt: str) -> Self: + """ + Returns a :class:`PoseSet` from its EWKT, the inverse of :meth:`as_ewkt`. + + Args: + ewkt: The EWKT representation. + + Returns: + A new :class:`PoseSet` instance. + + MEOS Functions: + _in + """ + return cls.from_text(ewkt) + + def as_ewkb(self, endian: str = "") -> bytes: + """ + Returns the EWKB representation of ``self``, carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`bytes` instance. + + MEOS Functions: + spatialset_as_ewkb, wkb_variant_from_endian + """ + return spatialset_as_ewkb(self._inner, wkb_variant_from_endian(endian)) + + def as_hexewkb(self, endian: str = "") -> str: + """ + Returns the HEXEWKB representation of ``self``, carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`str` instance. + + MEOS Functions: + spatialset_as_hexewkb, wkb_variant_from_endian + """ + return spatialset_as_hexewkb(self._inner, wkb_variant_from_endian(endian))[0] + # ------------------------- Output ---------------------------------------- def __str__(self, max_decimals: int = 15): diff --git a/pymeos/collections/text/textset.py b/pymeos/collections/text/textset.py index 28eb0e8b..bd5871cd 100644 --- a/pymeos/collections/text/textset.py +++ b/pymeos/collections/text/textset.py @@ -35,9 +35,15 @@ class TextSet(Set[str]): # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: """ - Return the string representation of the content of ``self``. + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self) -> str: + """ + Return the text representation of the content of ``self``. Returns: A new :class:`str` instance diff --git a/pymeos/collections/time/dateset.py b/pymeos/collections/time/dateset.py index 29e64672..35ff3fbb 100644 --- a/pymeos/collections/time/dateset.py +++ b/pymeos/collections/time/dateset.py @@ -69,9 +69,15 @@ class DateSet(Set[date], TimeCollection[date]): # ------------------------- Constructors ---------------------------------- # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: """ - Return the string representation of the content of ``self``. + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self) -> str: + """ + Return the text representation of the content of ``self``. Returns: A new :class:`str` instance diff --git a/pymeos/collections/time/datespan.py b/pymeos/collections/time/datespan.py index 45f24c3b..5bf41043 100644 --- a/pymeos/collections/time/datespan.py +++ b/pymeos/collections/time/datespan.py @@ -75,9 +75,15 @@ class DateSpan(Span[date], TimeCollection[date]): # ------------------------- Constructors ---------------------------------- # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: """ - Return the string representation of the content of ``self``. + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self) -> str: + """ + Return the text representation of the content of ``self``. Returns: A new :class:`str` instance diff --git a/pymeos/collections/time/datespanset.py b/pymeos/collections/time/datespanset.py index 2b177f21..74b724c5 100644 --- a/pymeos/collections/time/datespanset.py +++ b/pymeos/collections/time/datespanset.py @@ -70,9 +70,15 @@ class DateSpanSet(SpanSet[date], TimeCollection[date]): ) # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: """ - Return the string representation of the content of ``self``. + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self) -> str: + """ + Return the text representation of the content of ``self``. Returns: A new :class:`str` instance diff --git a/pymeos/collections/time/tstzset.py b/pymeos/collections/time/tstzset.py index 839862f6..6403760a 100644 --- a/pymeos/collections/time/tstzset.py +++ b/pymeos/collections/time/tstzset.py @@ -45,9 +45,15 @@ class TsTzSet(Set[datetime], TimeCollection[datetime]): # ------------------------- Constructors ---------------------------------- # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: """ - Return the string representation of the content of ``self``. + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self) -> str: + """ + Return the text representation of the content of ``self``. Returns: A new :class:`str` instance diff --git a/pymeos/collections/time/tstzspan.py b/pymeos/collections/time/tstzspan.py index 017b22e5..913842f5 100644 --- a/pymeos/collections/time/tstzspan.py +++ b/pymeos/collections/time/tstzspan.py @@ -47,9 +47,15 @@ class TsTzSpan(Span[datetime], TimeCollection[datetime]): # ------------------------- Constructors ---------------------------------- # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: """ - Return the string representation of the content of ``self``. + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self) -> str: + """ + Return the text representation of the content of ``self``. Returns: A new :class:`str` instance diff --git a/pymeos/collections/time/tstzspanset.py b/pymeos/collections/time/tstzspanset.py index 424491ad..03d21bcf 100644 --- a/pymeos/collections/time/tstzspanset.py +++ b/pymeos/collections/time/tstzspanset.py @@ -45,9 +45,15 @@ class TsTzSpanSet(SpanSet[datetime], TimeCollection[datetime]): ) # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: """ - Return the string representation of the content of ``self``. + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self) -> str: + """ + Return the text representation of the content of ``self``. Returns: A new :class:`str` instance diff --git a/pymeos/main/tbool.py b/pymeos/main/tbool.py index 2ddacc92..368c41d1 100644 --- a/pymeos/main/tbool.py +++ b/pymeos/main/tbool.py @@ -101,7 +101,13 @@ def from_mfjson(cls: type[Self], mfjson: str) -> Self: return Temporal._factory(result) # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: + """ + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self) -> str: """ Returns the string representation of `self`. diff --git a/pymeos/main/tcbuffer.py b/pymeos/main/tcbuffer.py index 8b5b0fd7..a1705eec 100644 --- a/pymeos/main/tcbuffer.py +++ b/pymeos/main/tcbuffer.py @@ -8,7 +8,7 @@ from ..collections import * from ..collections.cbuffer import Cbuffer, CbufferSet -from ..mixins import TTemporallyComparable +from ..mixins import TSpatialIO, TTemporallyComparable from ..temporal import Temporal, TInstant, TInterpolation, TSequence, TSequenceSet from .tfloat import TFloat from .tpoint import TPoint @@ -23,6 +23,7 @@ class TCbuffer( Temporal[Cbuffer, "TCbuffer", "TCbufferInst", "TCbufferSeq", "TCbufferSeqSet"], TTemporallyComparable, + TSpatialIO, ABC, ): """ @@ -59,7 +60,13 @@ def from_point_radius(point: TPoint, radius: TFloat) -> TCbuffer: return Temporal._factory(result) # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: + """ + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self, max_decimals: int = 15) -> str: """ Returns the string representation of `self`. @@ -69,7 +76,7 @@ def __str__(self): MEOS Functions: tspatial_as_text """ - return tspatial_as_text(self._inner, 15) + return tspatial_as_text(self._inner, max_decimals) def as_wkt(self, precision: int = 15) -> str: """ diff --git a/pymeos/main/tfloat.py b/pymeos/main/tfloat.py index acf659da..432a3cab 100644 --- a/pymeos/main/tfloat.py +++ b/pymeos/main/tfloat.py @@ -119,7 +119,13 @@ def from_mfjson(cls: type[Self], mfjson: str) -> Self: return Temporal._factory(result) # ------------------------- Output ---------------------------------------- - def __str__(self, max_decimals: int = 15): + def __str__(self) -> str: + """ + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self, max_decimals: int = 15) -> str: """ Returns a string representation of `self`. diff --git a/pymeos/main/tint.py b/pymeos/main/tint.py index 7d218c3b..3862c8a6 100644 --- a/pymeos/main/tint.py +++ b/pymeos/main/tint.py @@ -107,7 +107,13 @@ def from_mfjson(cls: type[Self], mfjson: str) -> Self: return Temporal._factory(result) # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: + """ + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self) -> str: """ Returns a string representation of `self`. diff --git a/pymeos/main/tnpoint.py b/pymeos/main/tnpoint.py index 03974145..88db3093 100644 --- a/pymeos/main/tnpoint.py +++ b/pymeos/main/tnpoint.py @@ -8,7 +8,7 @@ from ..collections import * from ..collections.npoint import Npoint, NpointSet, Nsegment -from ..mixins import TTemporallyComparable +from ..mixins import TSpatialIO, TTemporallyComparable from ..temporal import Temporal, TInstant, TInterpolation, TSequence, TSequenceSet if TYPE_CHECKING: @@ -24,6 +24,7 @@ class TNpoint( Temporal[Npoint, "TNpoint", "TNpointInst", "TNpointSeq", "TNpointSeqSet"], TTemporallyComparable, + TSpatialIO, ABC, ): """ @@ -120,7 +121,13 @@ def from_mfjson(cls: type[Self], mfjson: str) -> Self: return Temporal._factory(result) # ------------------------- Output ---------------------------------------- - def __str__(self, max_decimals: int = 15) -> str: + def __str__(self) -> str: + """ + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self, max_decimals: int = 15) -> str: """ Returns the string representation of `self`. @@ -128,9 +135,9 @@ def __str__(self, max_decimals: int = 15) -> str: A string with the string representation of `self`. MEOS Functions: - tnpoint_out + tspatial_as_text """ - return tnpoint_out(self._inner, max_decimals) + return tspatial_as_text(self._inner, max_decimals) def as_wkt(self, max_decimals: int = 15) -> str: """ @@ -140,9 +147,9 @@ def as_wkt(self, max_decimals: int = 15) -> str: A string with the Well-Known Text representation of `self`. MEOS Functions: - tnpoint_out + tspatial_as_text """ - return tnpoint_out(self._inner, max_decimals) + return tspatial_as_text(self._inner, max_decimals) # ------------------------- Value Accessors ------------------------------- def start_value(self) -> Npoint: diff --git a/pymeos/main/tpoint.py b/pymeos/main/tpoint.py index fb5e2ffe..c528f30e 100644 --- a/pymeos/main/tpoint.py +++ b/pymeos/main/tpoint.py @@ -12,7 +12,7 @@ from pymeos_cffi import * from ..collections import * -from ..mixins import TSimplifiable +from ..mixins import TSimplifiable, TSpatialIO from ..temporal import Temporal, TInstant, TInterpolation, TSequence, TSequenceSet from .tbool import TBool from .tfloat import TFloat, TFloatInst, TFloatSeq, TFloatSeqSet @@ -41,7 +41,7 @@ def import_geopandas(): TF = TypeVar("TF", bound="TFloat", covariant=True) -class TPoint(Temporal[shp.Point, TG, TI, TS, TSS], TSimplifiable, ABC): +class TPoint(Temporal[shp.Point, TG, TI, TS, TSS], TSimplifiable, TSpatialIO, ABC): """ Abstract class for temporal points. """ @@ -58,7 +58,13 @@ def from_hexwkb(cls: type[Self], hexwkb: str, srid: int | None = None) -> Self: return result.set_srid(srid) if srid is not None else result # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: + """ + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self, max_decimals: int = 15) -> str: """ Returns the string representation of the temporal point. @@ -68,7 +74,7 @@ def __str__(self): MEOS Functions: tspatial_as_text """ - return tspatial_as_text(self._inner, 15) + return tspatial_as_text(self._inner, max_decimals) def as_wkt(self, precision: int = 15) -> str: """ diff --git a/pymeos/main/tpose.py b/pymeos/main/tpose.py index 21ad36e6..b1b4e136 100644 --- a/pymeos/main/tpose.py +++ b/pymeos/main/tpose.py @@ -8,7 +8,7 @@ from ..collections import * from ..collections.pose import Pose, PoseSet -from ..mixins import TTemporallyComparable +from ..mixins import TSpatialIO, TTemporallyComparable from ..temporal import Temporal, TInstant, TInterpolation, TSequence, TSequenceSet from .tbool import TBool from .tfloat import TFloat @@ -23,6 +23,7 @@ class TPose( Temporal[Pose, "TPose", "TPoseInst", "TPoseSeq", "TPoseSeqSet"], TTemporallyComparable, + TSpatialIO, ABC, ): """ @@ -138,7 +139,13 @@ def from_tpoint_tfloat(tpoint: TPoint, tradius: TFloat) -> TPose: return Temporal._factory(result) # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: + """ + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self, max_decimals: int = 15) -> str: """ Returns the string representation of `self`. @@ -148,7 +155,7 @@ def __str__(self): MEOS Functions: tspatial_as_text """ - return tspatial_as_text(self._inner, 15) + return tspatial_as_text(self._inner, max_decimals) def as_wkt(self, precision: int = 15) -> str: """ diff --git a/pymeos/main/trgeometry.py b/pymeos/main/trgeometry.py index 5388cd10..4cbb25b5 100644 --- a/pymeos/main/trgeometry.py +++ b/pymeos/main/trgeometry.py @@ -8,7 +8,7 @@ from ..collections import * from ..collections.pose import Pose -from ..mixins import TTemporallyComparable +from ..mixins import TSpatialIO, TTemporallyComparable from ..temporal import Temporal, TInstant, TInterpolation, TSequence, TSequenceSet from .tbool import TBool from .tfloat import TFloat @@ -30,6 +30,7 @@ class TRgeometry( "TRgeometrySeqSet", ], TTemporallyComparable, + TSpatialIO, ABC, ): """ @@ -109,7 +110,13 @@ def from_geometry_tpose(geometry: shpb.BaseGeometry, tpose: TPose) -> TRgeometry return Temporal._factory(result) # ------------------------- Output ---------------------------------------- - def __str__(self): + def __str__(self) -> str: + """ + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self, max_decimals: int = 15) -> str: """ Returns the string representation of `self`. @@ -119,7 +126,7 @@ def __str__(self): MEOS Functions: trgeometry_as_text """ - return trgeometry_as_text(self._inner, 15) + return trgeometry_as_text(self._inner, max_decimals) def as_wkt(self, precision: int = 15) -> str: """ diff --git a/pymeos/main/ttext.py b/pymeos/main/ttext.py index 2dfd521b..61898471 100644 --- a/pymeos/main/ttext.py +++ b/pymeos/main/ttext.py @@ -108,6 +108,12 @@ def from_mfjson(cls: type[Self], mfjson: str) -> Self: # ------------------------- Output ---------------------------------------- def __str__(self) -> str: + """ + Returns the text representation of ``self``, its :meth:`as_text`. + """ + return self.as_text() + + def as_text(self) -> str: """ Returns the string representation of `self`. diff --git a/pymeos/mixins/__init__.py b/pymeos/mixins/__init__.py index cf55cde0..e220550e 100644 --- a/pymeos/mixins/__init__.py +++ b/pymeos/mixins/__init__.py @@ -1,10 +1,12 @@ from .comparison import TComparable from .simplify import TSimplifiable +from .spatial_io import TSpatialIO from .temporal_comparison import TTemporallyComparable, TTemporallyEquatable __all__ = [ "TComparable", "TSimplifiable", + "TSpatialIO", "TTemporallyComparable", "TTemporallyEquatable", ] diff --git a/pymeos/mixins/spatial_io.py b/pymeos/mixins/spatial_io.py new file mode 100644 index 00000000..6693cb36 --- /dev/null +++ b/pymeos/mixins/spatial_io.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, TypeVar + +from pymeos_cffi import ( + temporal_from_hexwkb, + temporal_from_wkb, + tspatial_as_ewkb, + tspatial_as_ewkt, + tspatial_as_hexewkb, + wkb_variant_from_endian, +) + +if TYPE_CHECKING: + from ..temporal import Temporal + +Self = TypeVar("Self", bound="Temporal[Any]") + + +class TSpatialIO: + """ + The input and output forms carrying the SRID of a spatial temporal value, + as the SQL ``asEWKT``, ``asEWKB``, ``asHexEWKB`` and their readers write + and read them; a mixin of the spatial temporal classes as #TSimplifiable + is of #TPoint, its ``as_ewkt`` the one #TPoint states. + """ + + def as_ewkt(self, max_decimals: int = 15) -> str: + """ + Returns the Extended Well-Known Text representation of ``self``, its + text preceded by its SRID. + + Args: + max_decimals: The number of decimal places of the coordinates. + + Returns: + A :class:`str` with the EWKT of ``self``. + + MEOS Functions: + tspatial_as_ewkt + """ + return tspatial_as_ewkt(self._inner, max_decimals) + + def as_ewkb(self, endian: str = "") -> bytes: + """ + Returns the Extended Well-Known Binary representation of ``self``, its + WKB carrying its SRID. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + The EWKB of ``self``. + + MEOS Functions: + tspatial_as_ewkb, wkb_variant_from_endian + """ + return tspatial_as_ewkb(self._inner, wkb_variant_from_endian(endian)) + + def as_hexewkb(self, endian: str = "") -> str: + """ + Returns the Extended Well-Known Binary representation of ``self`` as a + hex-encoded string. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + A :class:`str` with the hex-encoded EWKB of ``self``. + + MEOS Functions: + tspatial_as_hexewkb, wkb_variant_from_endian + """ + return tspatial_as_hexewkb(self._inner, wkb_variant_from_endian(endian))[0] + + @classmethod + def from_ewkt(cls: type[Self], ewkt: str) -> Self: + """ + Returns a temporal value from its EWKT, the inverse of :meth:`as_ewkt`. + + Args: + ewkt: The EWKT. + + Returns: + A temporal value of the class's type. + + MEOS Functions: + _in + """ + return cls.from_text(ewkt) + + @classmethod + def from_ewkb(cls: type[Self], ewkb: bytes) -> Self: + """ + Returns a temporal value from its EWKB, the inverse of :meth:`as_ewkb`. + + Args: + ewkb: The EWKB. + + Returns: + A temporal value of the class's type. + + MEOS Functions: + temporal_from_wkb + """ + from ..temporal import Temporal + + return Temporal._factory(temporal_from_wkb(ewkb)) + + @classmethod + def from_hexewkb(cls: type[Self], hexewkb: str) -> Self: + """ + Returns a temporal value from its hex-encoded EWKB, the inverse of + :meth:`as_hexewkb`. + + Args: + hexewkb: The hex-encoded EWKB. + + Returns: + A temporal value of the class's type. + + MEOS Functions: + temporal_from_hexwkb + """ + from ..temporal import Temporal + + return Temporal._factory(temporal_from_hexwkb(hexewkb)) diff --git a/pymeos/temporal/temporal.py b/pymeos/temporal/temporal.py index 9099df25..49d4050e 100644 --- a/pymeos/temporal/temporal.py +++ b/pymeos/temporal/temporal.py @@ -150,6 +150,23 @@ def from_hexwkb(cls: type[Self], hexwkb: str) -> Self: result = temporal_from_hexwkb(hexwkb) return Temporal._factory(result) + @classmethod + def from_text(cls: type[Self], text: str) -> Self: + """ + Returns a temporal object from its text representation, the inverse + of :meth:`as_text`. + + Args: + text: The text representation. + + Returns: + A temporal object. + + MEOS Functions: + _in + """ + return Temporal._factory(cls._parse_function(text)) + @classmethod def from_merge(cls: type[Self], *temporals: TG) -> Self: """ @@ -222,29 +239,49 @@ def as_mfjson( """ return temporal_as_mfjson(self._inner, with_bbox, flags, precision, srs) - def as_wkb(self) -> bytes: + def as_text(self) -> str: """ - Returns the temporal object as a hex-encoded WKB string. + Returns the text representation of the temporal object, its WKT for + a spatial one. Returns: - The temporal object as a hex-encoded WKB string. + The text representation of the temporal object. + """ + return self.as_wkt() + + def as_wkb(self, endian: str = "") -> bytes: + """ + Returns the temporal object as WKB bytes, plain WKB for a spatial one, + as the SQL ``asBinary`` writes it. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. + + Returns: + The temporal object as WKB bytes. MEOS Functions: - temporal_as_hexwkb + temporal_as_wkb, wkb_variant_from_endian """ - return temporal_as_wkb(self._inner, 4) + return temporal_as_wkb(self._inner, wkb_variant_from_endian(endian)) - def as_hexwkb(self) -> str: + def as_hexwkb(self, endian: str = "") -> str: """ - Returns the temporal object as a hex-encoded WKB string. + Returns the temporal object as a hex-encoded WKB string, plain WKB for + a spatial one, as the SQL ``asHexWKB`` writes it. + + Args: + endian: The byte order, ``"NDR"``, ``"XDR"`` or ``""`` for the + machine's own. Returns: The temporal object as a hex-encoded WKB string. MEOS Functions: - temporal_as_hexwkb + temporal_as_hexwkb, wkb_variant_from_endian """ - return temporal_as_hexwkb(self._inner, 4)[0] + return temporal_as_hexwkb(self._inner, wkb_variant_from_endian(endian))[0] # ------------------------- Accessors ------------------------------------- def bounding_box(self) -> TsTzSpan | Box: diff --git a/tests/io_forms_test.py b/tests/io_forms_test.py new file mode 100644 index 00000000..8f5db045 --- /dev/null +++ b/tests/io_forms_test.py @@ -0,0 +1,150 @@ +import pytest + +from pymeos import ( + Cbuffer, + CbufferSet, + FloatSet, + FloatSpan, + GeometrySet, + IntSet, + IntSpan, + IntSpanSet, + Npoint, + NpointSet, + Nsegment, + Pose, + PoseSet, + STBox, + TBoolInst, + TBox, + TCbufferInst, + TextSet, + TFloatSeq, + TGeogPointInst, + TGeomPointSeq, + TIntSeq, + TNpointInst, + TPoseInst, + TRgeometryInst, + TsTzSet, + TsTzSpan, + TsTzSpanSet, + TTextInst, +) +from tests.conftest import TestPyMEOS + +# Every value with the text its as_text writes, which str returns. +TEXT = [ + (TFloatSeq("[1.5@2001-01-01, 2.5@2001-01-02]"), "[1.5@2001-01-01 00:00:00+00, 2.5@2001-01-02 00:00:00+00]"), + (TIntSeq("[1@2001-01-01, 2@2001-01-02]"), "[1@2001-01-01 00:00:00+00, 2@2001-01-02 00:00:00+00]"), + (TBoolInst("t@2001-01-01"), "t@2001-01-01 00:00:00+00"), + (TTextInst("AAA@2001-01-01"), '"AAA"@2001-01-01 00:00:00+00'), + ( + TGeomPointSeq("SRID=4326;[Point(1 1)@2001-01-01, Point(2 2)@2001-01-02]"), + "[POINT(1 1)@2001-01-01 00:00:00+00, POINT(2 2)@2001-01-02 00:00:00+00]", + ), + (TGeogPointInst("SRID=4326;Point(1 1)@2001-01-01"), "POINT(1 1)@2001-01-01 00:00:00+00"), + (TCbufferInst("SRID=3812;Cbuffer(Point(1 1),0.5)@2001-01-01"), "Cbuffer(POINT(1 1),0.5)@2001-01-01 00:00:00+00"), + (TPoseInst("SRID=3812;Pose(Point(1 1),0.5)@2001-01-01"), "Pose(POINT(1 1),0.5)@2001-01-01 00:00:00+00"), + (TNpointInst("NPoint(1,0.5)@2001-01-01"), "NPoint(1,0.5)@2001-01-01 00:00:00+00"), + ( + TRgeometryInst("SRID=3812;Polygon((0 0,1 0,0 1,0 0));Pose(Point(1 1),0.5)@2001-01-01"), + "POLYGON((0 0,1 0,0 1,0 0));Pose(POINT(1 1),0.5)@2001-01-01 00:00:00+00", + ), + (IntSet("{1, 2}"), "{1, 2}"), + (FloatSet("{1.5, 2.5}"), "{1.5, 2.5}"), + (TextSet('{"a", "b"}'), '{"a", "b"}'), + (TsTzSet("{2001-01-01, 2001-01-02}"), '{"2001-01-01 00:00:00+00", "2001-01-02 00:00:00+00"}'), + (GeometrySet("SRID=4326;{Point(1 1), Point(2 2)}"), '{"POINT(1 1)", "POINT(2 2)"}'), + (PoseSet('SRID=3812;{"Pose(Point(1 1),0.5)"}'), '{"Pose(POINT(1 1),0.5)"}'), + (CbufferSet('SRID=3812;{"Cbuffer(Point(1 1),0.5)"}'), '{"Cbuffer(POINT(1 1),0.5)"}'), + (NpointSet('{"NPoint(1,0.5)"}'), '{"NPoint(1,0.5)"}'), + (IntSpan("[1, 3)"), "[1, 3)"), + (FloatSpan("[1.5, 2.5]"), "[1.5, 2.5]"), + (TsTzSpan("[2001-01-01, 2001-01-02]"), "[2001-01-01 00:00:00+00, 2001-01-02 00:00:00+00]"), + (IntSpanSet("{[1, 3), [4, 5)}"), "{[1, 3), [4, 5)}"), + (TsTzSpanSet("{[2001-01-01, 2001-01-02]}"), "{[2001-01-01 00:00:00+00, 2001-01-02 00:00:00+00]}"), + ( + TBox("TBOXFLOAT XT([1.5, 2.5],[2001-01-01, 2001-01-02])"), + "TBOXFLOAT XT([1.5, 2.5],[2001-01-01 00:00:00+00, 2001-01-02 00:00:00+00])", + ), + ( + STBox("SRID=4326;STBOX XT(((1,1),(2,2)),[2001-01-01, 2001-01-02])"), + "SRID=4326;STBOX XT(((1,1),(2,2)),[2001-01-01 00:00:00+00, 2001-01-02 00:00:00+00])", + ), + (Pose("SRID=3812;Pose(Point(1 1),0.5)"), "Pose(POINT(1 1),0.5)"), + (Cbuffer("SRID=3812;Cbuffer(Point(1 1),0.5)"), "Cbuffer(POINT(1 1),0.5)"), + (Npoint("NPoint(1,0.5)"), "NPoint(1,0.5)"), + (Nsegment("NSegment(1,0.2,0.5)"), "NSegment(1,0.2,0.5)"), +] +IDS = [type(v).__name__ for v, _ in TEXT] + +# Every value carrying an SRID with the EWKT its as_ewkt writes. +EWKT = [ + (TEXT[i][0], srid + TEXT[i][1]) + for i, srid in ( + (4, "SRID=4326;"), + (5, "SRID=4326;"), + (6, "SRID=3812;"), + (7, "SRID=3812;"), + (8, "SRID=5676;"), + (9, "SRID=3812;"), + (14, "SRID=4326;"), + (15, "SRID=3812;"), + (16, "SRID=3812;"), + (17, "SRID=5676;"), + (25, "SRID=3812;"), + (26, "SRID=3812;"), + (27, "SRID=5676;"), + (28, "SRID=5676;"), + ) +] +EWKT_IDS = [type(v).__name__ for v, _ in EWKT] +# The values whose binary forms carry an SRID: those of EWKT, and STBox, whose +# text carries it. +EWKB = [v for v, _ in EWKT] + [TEXT[24][0]] +EWKB_IDS = [type(v).__name__ for v in EWKB] + + +class TestIOForms(TestPyMEOS): + """ + The portable readers and writers of every class, as #TestPose states + ``test_str`` and ``test_from_as_hexwkb`` for a pose: ``str`` is ``as_text``, + the plain forms carry no SRID and the E forms carry it, as the SQL + ``asText``, ``asBinary``, ``asEWKT`` and ``asEWKB`` write them. + """ + + @pytest.mark.parametrize("value, text", TEXT, ids=IDS) + def test_str_is_as_text(self, value, text): + assert value.as_text() == text + assert str(value) == text + + @pytest.mark.parametrize("value, text", TEXT, ids=IDS) + def test_from_text(self, value, text): + assert type(value).from_text(text).as_text() == text + + @pytest.mark.parametrize("value, text", TEXT, ids=IDS) + def test_wkb_endian(self, value, text): + assert value.as_hexwkb("NDR")[:2] == "01" + assert value.as_hexwkb("XDR")[:2] == "00" + assert value.as_wkb("NDR") == bytes.fromhex(value.as_hexwkb("NDR")) + reread = type(value).from_hexwkb(value.as_hexwkb("XDR")) + assert reread.as_hexwkb("NDR") == value.as_hexwkb("NDR") + + @pytest.mark.parametrize("value, ewkt", EWKT, ids=EWKT_IDS) + def test_as_ewkt(self, value, ewkt): + assert value.as_ewkt() == ewkt + assert type(value).from_ewkt(ewkt) == value + + @pytest.mark.parametrize("value", EWKB, ids=EWKB_IDS) + def test_ewkb_round_trip(self, value): + cls = type(value) + assert cls.from_ewkb(value.as_ewkb()) == value + assert cls.from_hexewkb(value.as_hexewkb()) == value + assert cls.from_hexewkb(value.as_hexewkb("XDR")) == value + + def test_plain_wkb_carries_no_srid(self): + value = TGeomPointSeq("SRID=4326;[Point(1 1)@2001-01-01, Point(2 2)@2001-01-02]") + assert TGeomPointSeq.from_hexwkb(value.as_hexwkb()).srid() == 0 + assert TGeomPointSeq.from_hexewkb(value.as_hexewkb()).srid() == 4326 + assert value.as_hexwkb() != value.as_hexewkb() From 0afc75efe064a1683e4607170d6df3a6ac5da487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20Zim=C3=A1nyi?= Date: Sat, 31 Oct 2026 23:10:51 +0100 Subject: [PATCH 2/2] Generate the regular method families of the temporal classes from the catalog tools/oo_codegen/codegen.py reads the meos-idl.json catalog of the MEOS the package builds against, given by --idl or MEOS_IDL, and writes the regular method families of TCbuffer, TPose, TNpoint and TRgeometry, the comparisons, the spatial relationships, the distances and the restrictions, as the mixins pymeos/main/_generated/_methods.py: each method dispatches by the argument's type to the pymeos_cffi function of its overload, and each temporal class takes its mixin as its first base, so the generated method is the one the class answers with and its fallback reaches Temporal. The constructors, the conversions, the accessors, the transformations and the input and output stay in the hand-written classes. The generator writes a mixin through ruff check --fix and ruff format, so the committed file is its output and passes the ruff workflow; a catalog argument token the family model does not map, or that has no place in the dispatch order, stops it with the token's name. --check accounts for every catalogued function, emitted or counted under a named exclusion. The rigid geometry family reads the trgeometry token of the catalog and restricts through the public trgeometry_at_* and trgeometry_minus_* functions, to a geometry, a pose, a set of poses and the time types; a temporal pose restricts to an elevation span through tpose_at_elevation and tpose_minus_elevation. The test workflow sets MEOS_IDL to the catalog it derives; tests/test_oo_codegen_coverage.py checks the accounting, that every committed mixin is the generator's output on that catalog and that every function a mixin calls is a function of the catalog. Witness: against PyMEOS master 59191dd, TPoseSeq('[Pose(Point(1 1 1),0.0,0.0,0.0,1.0)@2001-01-01, Pose(Point(1 1 3),0.0,0.0,0.0,1.0)@2001-01-03]').at(FloatSpan('[1.5, 2]')) raises TypeError and a TRgeometry has no within_distance; against this branch the first answers {[Pose(POINT Z (1 1 1.5),0,0,0,1)@2001-01-01 12:00:00+00, Pose(POINT Z (1 1 2),0,0,0,1)@2001-01-02 00:00:00+00]} and the within_distance of a moving triangle with itself at 1.0 answers {[t@2001-01-01 00:00:00+00, t@2001-01-03 00:00:00+00]}. Measured: on the catalog of MobilityDB 5bf840dca5, 5446 functions accounted for, 123 regular methods across the six families, 77 of them wired into the four classes, 76 in place of the hand-written methods of the same name and TRgeometry's within_distance; a second generator run rewrites the four mixins byte for byte; 4851 tests, 6 of them the new restrictions and within_distance; ruff check and ruff format report no finding. Why: the bindings are projections of the MEOS catalog, so a method family that the catalog states is generated from it and follows it as MEOS gains overloads, where a hand-written family falls behind. --- .github/workflows/test.yml | 2 + pymeos/main/_generated/__init__.py | 1 + pymeos/main/_generated/tcbuffer_methods.py | 556 ++++++++++++++++ pymeos/main/_generated/tnpoint_methods.py | 236 +++++++ pymeos/main/_generated/tpose_methods.py | 237 +++++++ pymeos/main/_generated/trgeometry_methods.py | 415 ++++++++++++ pymeos/main/tcbuffer.py | 627 +---------------- pymeos/main/tnpoint.py | 295 +------- pymeos/main/tpose.py | 288 +------- pymeos/main/trgeometry.py | 438 +----------- tests/main/tpose_test.py | 16 + tests/main/trgeometry_test.py | 28 + tests/test_oo_codegen_coverage.py | 93 +++ tools/oo_codegen/.gitignore | 1 + tools/oo_codegen/README.md | 37 ++ tools/oo_codegen/codegen.py | 664 +++++++++++++++++++ 16 files changed, 2295 insertions(+), 1639 deletions(-) create mode 100644 pymeos/main/_generated/__init__.py create mode 100644 pymeos/main/_generated/tcbuffer_methods.py create mode 100644 pymeos/main/_generated/tnpoint_methods.py create mode 100644 pymeos/main/_generated/tpose_methods.py create mode 100644 pymeos/main/_generated/trgeometry_methods.py create mode 100644 tests/test_oo_codegen_coverage.py create mode 100644 tools/oo_codegen/.gitignore create mode 100644 tools/oo_codegen/README.md create mode 100644 tools/oo_codegen/codegen.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 63db3bb7..1298dd05 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -193,6 +193,8 @@ jobs: pip install . - name: Test PyMEOS with pytest + env: + MEOS_IDL: ${{ github.workspace }}/PyMEOS-CFFI/builder/meos-idl.json run: | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${{ matrix.ld_prefix }}/lib export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:${{ matrix.ld_prefix }}/lib diff --git a/pymeos/main/_generated/__init__.py b/pymeos/main/_generated/__init__.py new file mode 100644 index 00000000..a3f7eeb8 --- /dev/null +++ b/pymeos/main/_generated/__init__.py @@ -0,0 +1 @@ +# Generated OO method-family mixins (see tools/oo_codegen). DO NOT EDIT. diff --git a/pymeos/main/_generated/tcbuffer_methods.py b/pymeos/main/_generated/tcbuffer_methods.py new file mode 100644 index 00000000..496bfb7d --- /dev/null +++ b/pymeos/main/_generated/tcbuffer_methods.py @@ -0,0 +1,556 @@ +# Copyright (c) 2016-2026, Université libre de Bruxelles and PyMEOS +# contributors. Licensed under the PostgreSQL License (see LICENSE). +# +# ============================================================================ +# GENERATED by tools/oo_codegen/codegen.py -- DO NOT EDIT. +# Regenerate: +# python3 tools/oo_codegen/codegen.py --idl --mixin cbuffer \ +# --mixin-out pymeos/main/_generated/tcbuffer_methods.py +# ============================================================================ +# +# Wired into pymeos.main.TCbuffer via TCbufferRegularMixin. Every method +# dispatches by argument type to the EXACT pymeos_cffi backing the +# hand-written method used -- same native call, same transforms, same +# result, never reimplemented: identical by construction. +"""Generated regular OO methods for the TCbuffer family.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import shapely.geometry.base as shpb +from pymeos_cffi import * + +from ...collections.cbuffer import Cbuffer +from ...temporal import Temporal + +if TYPE_CHECKING: + pass + + +class TCbufferRegularMixin: + """Generated regular families (comparison, spatial relationship, + distance, restriction) for :class:`TCbuffer`.""" + + def always_equal(self, other): + """Generated regular ``always_equal``. + + MEOS Functions: + always_eq_cbuffer_tcbuffer, always_eq_tcbuffer_cbuffer, always_eq_tcbuffer_tcbuffer + """ + from ..tcbuffer import TCbuffer + + if isinstance(other, Cbuffer): + result = always_eq_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = always_eq_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def always_intersects(self, other): + """Generated regular ``always_intersects``. + + MEOS Functions: + aintersects_cbuffer_tcbuffer, aintersects_geo_tcbuffer, aintersects_tcbuffer_cbuffer, aintersects_tcbuffer_geo, aintersects_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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_not_equal(self, other): + """Generated regular ``always_not_equal``. + + MEOS Functions: + always_ne_cbuffer_tcbuffer, always_ne_tcbuffer_cbuffer, always_ne_tcbuffer_tcbuffer + """ + from ..tcbuffer import TCbuffer + + if isinstance(other, Cbuffer): + result = always_ne_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = always_ne_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def always_touches(self, other): + """Generated regular ``always_touches``. + + MEOS Functions: + atouches_cbuffer_tcbuffer, atouches_geo_tcbuffer, atouches_tcbuffer_cbuffer, atouches_tcbuffer_geo, atouches_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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 + + def at(self, other): + """Generated regular ``at``. + + MEOS Functions: + tcbuffer_at_cbuffer, tcbuffer_at_geom, tcbuffer_at_stbox + """ + from ...boxes import STBox + + if isinstance(other, shpb.BaseGeometry): + result = tcbuffer_at_geom(self._inner, geo_to_gserialized(other, False)) + elif isinstance(other, Cbuffer): + result = tcbuffer_at_cbuffer(self._inner, other._inner) + elif isinstance(other, STBox): + result = tcbuffer_at_stbox(self._inner, other._inner, True) + else: + return super().at(other) + return Temporal._factory(result) + + def contains(self, other): + """Generated regular ``contains``. + + MEOS Functions: + tcontains_cbuffer_tcbuffer, tcontains_geo_tcbuffer, tcontains_tcbuffer_cbuffer, tcontains_tcbuffer_geo, tcontains_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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): + """Generated regular ``covers``. + + MEOS Functions: + tcovers_cbuffer_tcbuffer, tcovers_geo_tcbuffer, tcovers_tcbuffer_cbuffer, tcovers_tcbuffer_geo, tcovers_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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): + """Generated regular ``disjoint``. + + MEOS Functions: + tdisjoint_cbuffer_tcbuffer, tdisjoint_geo_tcbuffer, tdisjoint_tcbuffer_cbuffer, tdisjoint_tcbuffer_geo, tdisjoint_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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 distance(self, other): + """Generated regular ``distance``. + + MEOS Functions: + tdistance_tcbuffer_cbuffer, tdistance_tcbuffer_geo, tdistance_tcbuffer_tcbuffer + """ + from ..tcbuffer import TCbuffer + + if isinstance(other, shpb.BaseGeometry): + result = tdistance_tcbuffer_geo(self._inner, geo_to_gserialized(other, False)) + 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 ever_equal(self, other): + """Generated regular ``ever_equal``. + + MEOS Functions: + ever_eq_cbuffer_tcbuffer, ever_eq_tcbuffer_cbuffer, ever_eq_tcbuffer_tcbuffer + """ + from ..tcbuffer import TCbuffer + + if isinstance(other, Cbuffer): + result = ever_eq_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = ever_eq_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def ever_intersects(self, other): + """Generated regular ``ever_intersects``. + + MEOS Functions: + eintersects_cbuffer_tcbuffer, eintersects_geo_tcbuffer, eintersects_tcbuffer_cbuffer, eintersects_tcbuffer_geo, eintersects_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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_not_equal(self, other): + """Generated regular ``ever_not_equal``. + + MEOS Functions: + ever_ne_cbuffer_tcbuffer, ever_ne_tcbuffer_cbuffer, ever_ne_tcbuffer_tcbuffer + """ + from ..tcbuffer import TCbuffer + + if isinstance(other, Cbuffer): + result = ever_ne_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = ever_ne_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def ever_touches(self, other): + """Generated regular ``ever_touches``. + + MEOS Functions: + etouches_cbuffer_tcbuffer, etouches_geo_tcbuffer, etouches_tcbuffer_cbuffer, etouches_tcbuffer_geo, etouches_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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 + + def intersects(self, other): + """Generated regular ``intersects``. + + MEOS Functions: + tintersects_cbuffer_tcbuffer, tintersects_geo_tcbuffer, tintersects_tcbuffer_cbuffer, tintersects_tcbuffer_geo, tintersects_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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 is_always_contains(self, other): + """Generated regular ``is_always_contains``. + + MEOS Functions: + acontains_cbuffer_tcbuffer, acontains_geo_tcbuffer, acontains_tcbuffer_cbuffer, acontains_tcbuffer_geo, acontains_tcbuffer_tcbuffer + """ + from ..tcbuffer import TCbuffer + + 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) + elif isinstance(other, TCbuffer): + result = acontains_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def is_always_covers(self, other): + """Generated regular ``is_always_covers``. + + MEOS Functions: + acovers_cbuffer_tcbuffer, acovers_geo_tcbuffer, acovers_tcbuffer_cbuffer, acovers_tcbuffer_geo, acovers_tcbuffer_tcbuffer + """ + from ..tcbuffer import TCbuffer + + 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) + elif isinstance(other, TCbuffer): + result = acovers_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def is_always_disjoint(self, other): + """Generated regular ``is_always_disjoint``. + + MEOS Functions: + adisjoint_cbuffer_tcbuffer, adisjoint_geo_tcbuffer, adisjoint_tcbuffer_cbuffer, adisjoint_tcbuffer_geo, adisjoint_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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, distance): + """Generated regular ``is_always_within_distance``. + + MEOS Functions: + adwithin_cbuffer_tcbuffer, adwithin_geo_tcbuffer, adwithin_tcbuffer_cbuffer, adwithin_tcbuffer_geo, adwithin_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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 is_ever_contains(self, other): + """Generated regular ``is_ever_contains``. + + MEOS Functions: + econtains_cbuffer_tcbuffer, econtains_geo_tcbuffer, econtains_tcbuffer_cbuffer, econtains_tcbuffer_geo, econtains_tcbuffer_tcbuffer + """ + from ..tcbuffer import TCbuffer + + 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) + elif isinstance(other, TCbuffer): + result = econtains_tcbuffer_tcbuffer(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def is_ever_covers(self, other): + """Generated regular ``is_ever_covers``. + + MEOS Functions: + ecovers_cbuffer_tcbuffer, ecovers_geo_tcbuffer, ecovers_tcbuffer_cbuffer, ecovers_tcbuffer_geo, ecovers_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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): + """Generated regular ``is_ever_disjoint``. + + MEOS Functions: + edisjoint_cbuffer_tcbuffer, edisjoint_geo_tcbuffer, edisjoint_tcbuffer_cbuffer, edisjoint_tcbuffer_geo, edisjoint_tcbuffer_tcbuffer + """ + from ..tcbuffer import TCbuffer + + 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) + elif isinstance(other, TCbuffer): + result = edisjoint_tcbuffer_tcbuffer(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, distance): + """Generated regular ``is_ever_within_distance``. + + MEOS Functions: + edwithin_cbuffer_tcbuffer, edwithin_geo_tcbuffer, edwithin_tcbuffer_cbuffer, edwithin_tcbuffer_geo, edwithin_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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 minus(self, other): + """Generated regular ``minus``. + + MEOS Functions: + tcbuffer_minus_cbuffer, tcbuffer_minus_geom, tcbuffer_minus_stbox + """ + from ...boxes import STBox + + if isinstance(other, shpb.BaseGeometry): + result = tcbuffer_minus_geom(self._inner, geo_to_gserialized(other, False)) + elif isinstance(other, Cbuffer): + result = tcbuffer_minus_cbuffer(self._inner, other._inner) + elif isinstance(other, STBox): + result = tcbuffer_minus_stbox(self._inner, other._inner, True) + else: + return super().minus(other) + return Temporal._factory(result) + + def nearest_approach_distance(self, other): + """Generated regular ``nearest_approach_distance``. + + MEOS Functions: + nad_cbuffer_stbox, nad_tcbuffer_cbuffer, nad_tcbuffer_geo, nad_tcbuffer_stbox, nad_tcbuffer_tcbuffer + """ + from ...boxes import STBox + from ..tcbuffer import TCbuffer + + if isinstance(other, shpb.BaseGeometry): + result = nad_tcbuffer_geo(self._inner, geo_to_gserialized(other, False)) + elif isinstance(other, Cbuffer): + result = nad_tcbuffer_cbuffer(self._inner, other._inner) + elif isinstance(other, TCbuffer): + result = nad_tcbuffer_tcbuffer(self._inner, other._inner) + elif isinstance(other, STBox): + result = nad_tcbuffer_stbox(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result + + def nearest_approach_instant(self, other): + """Generated regular ``nearest_approach_instant``. + + MEOS Functions: + nai_tcbuffer_cbuffer, nai_tcbuffer_geo, nai_tcbuffer_tcbuffer + """ + from ..tcbuffer import TCbuffer + + if isinstance(other, shpb.BaseGeometry): + result = nai_tcbuffer_geo(self._inner, geo_to_gserialized(other, False)) + 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): + """Generated regular ``shortest_line``. + + MEOS Functions: + shortestline_tcbuffer_cbuffer, shortestline_tcbuffer_geo, shortestline_tcbuffer_tcbuffer + """ + from ..tcbuffer import TCbuffer + + if isinstance(other, shpb.BaseGeometry): + result = shortestline_tcbuffer_geo(self._inner, geo_to_gserialized(other, False)) + 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) + + def temporal_equal(self, other): + """Generated regular ``temporal_equal``. + + MEOS Functions: + teq_cbuffer_tcbuffer, teq_tcbuffer_cbuffer + """ + 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): + """Generated regular ``temporal_not_equal``. + + MEOS Functions: + tne_cbuffer_tcbuffer, tne_tcbuffer_cbuffer + """ + if isinstance(other, Cbuffer): + result = tne_tcbuffer_cbuffer(self._inner, other._inner) + else: + return super().temporal_not_equal(other) + return Temporal._factory(result) + + def touches(self, other): + """Generated regular ``touches``. + + MEOS Functions: + ttouches_cbuffer_tcbuffer, ttouches_geo_tcbuffer, ttouches_tcbuffer_cbuffer, ttouches_tcbuffer_geo, ttouches_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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) + + def within_distance(self, other, distance): + """Generated regular ``within_distance``. + + MEOS Functions: + tdwithin_cbuffer_tcbuffer, tdwithin_geo_tcbuffer, tdwithin_tcbuffer_cbuffer, tdwithin_tcbuffer_geo, tdwithin_tcbuffer_tcbuffer + """ + from ..tcbuffer import 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) diff --git a/pymeos/main/_generated/tnpoint_methods.py b/pymeos/main/_generated/tnpoint_methods.py new file mode 100644 index 00000000..32c801c4 --- /dev/null +++ b/pymeos/main/_generated/tnpoint_methods.py @@ -0,0 +1,236 @@ +# Copyright (c) 2016-2026, Université libre de Bruxelles and PyMEOS +# contributors. Licensed under the PostgreSQL License (see LICENSE). +# +# ============================================================================ +# GENERATED by tools/oo_codegen/codegen.py -- DO NOT EDIT. +# Regenerate: +# python3 tools/oo_codegen/codegen.py --idl --mixin npoint \ +# --mixin-out pymeos/main/_generated/tnpoint_methods.py +# ============================================================================ +# +# Wired into pymeos.main.TNpoint via TNpointRegularMixin. Every method +# dispatches by argument type to the EXACT pymeos_cffi backing the +# hand-written method used -- same native call, same transforms, same +# result, never reimplemented: identical by construction. +"""Generated regular OO methods for the TNpoint family.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import shapely.geometry.base as shpb +from pymeos_cffi import * + +from ...collections.npoint import Npoint, NpointSet +from ...temporal import Temporal + +if TYPE_CHECKING: + pass + + +class TNpointRegularMixin: + """Generated regular families (comparison, spatial relationship, + distance, restriction) for :class:`TNpoint`.""" + + def always_equal(self, other): + """Generated regular ``always_equal``. + + MEOS Functions: + always_eq_npoint_tnpoint, always_eq_tnpoint_npoint, always_eq_tnpoint_tnpoint + """ + from ..tnpoint import TNpoint + + if isinstance(other, Npoint): + result = always_eq_tnpoint_npoint(self._inner, other._inner) + elif isinstance(other, TNpoint): + result = always_eq_tnpoint_tnpoint(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def always_not_equal(self, other): + """Generated regular ``always_not_equal``. + + MEOS Functions: + always_ne_npoint_tnpoint, always_ne_tnpoint_npoint, always_ne_tnpoint_tnpoint + """ + from ..tnpoint import TNpoint + + if isinstance(other, Npoint): + result = always_ne_tnpoint_npoint(self._inner, other._inner) + elif isinstance(other, TNpoint): + result = always_ne_tnpoint_tnpoint(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def at(self, other): + """Generated regular ``at``. + + MEOS Functions: + tnpoint_at_geom, tnpoint_at_npoint, tnpoint_at_npointset, tnpoint_at_stbox + """ + from ...boxes import STBox + + if isinstance(other, shpb.BaseGeometry): + result = tnpoint_at_geom(self._inner, geo_to_gserialized(other, False)) + elif 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, STBox): + result = tnpoint_at_stbox(self._inner, other._inner, True) + else: + return super().at(other) + return Temporal._factory(result) + + def distance(self, other): + """Generated regular ``distance``. + + MEOS Functions: + tdistance_tnpoint_geo, tdistance_tnpoint_npoint, tdistance_tnpoint_tnpoint + """ + from ..tnpoint import TNpoint + + if isinstance(other, shpb.BaseGeometry): + result = tdistance_tnpoint_geo(self._inner, geo_to_gserialized(other, False)) + elif isinstance(other, Npoint): + result = tdistance_tnpoint_npoint(self._inner, other._inner) + 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 ever_equal(self, other): + """Generated regular ``ever_equal``. + + MEOS Functions: + ever_eq_npoint_tnpoint, ever_eq_tnpoint_npoint, ever_eq_tnpoint_tnpoint + """ + from ..tnpoint import TNpoint + + if isinstance(other, Npoint): + result = ever_eq_tnpoint_npoint(self._inner, other._inner) + elif isinstance(other, TNpoint): + result = ever_eq_tnpoint_tnpoint(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def ever_not_equal(self, other): + """Generated regular ``ever_not_equal``. + + MEOS Functions: + ever_ne_npoint_tnpoint, ever_ne_tnpoint_npoint, ever_ne_tnpoint_tnpoint + """ + from ..tnpoint import TNpoint + + if isinstance(other, Npoint): + result = ever_ne_tnpoint_npoint(self._inner, other._inner) + elif isinstance(other, TNpoint): + result = ever_ne_tnpoint_tnpoint(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def minus(self, other): + """Generated regular ``minus``. + + MEOS Functions: + tnpoint_minus_geom, tnpoint_minus_npoint, tnpoint_minus_npointset, tnpoint_minus_stbox + """ + from ...boxes import STBox + + if isinstance(other, shpb.BaseGeometry): + result = tnpoint_minus_geom(self._inner, geo_to_gserialized(other, False)) + elif 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, STBox): + result = tnpoint_minus_stbox(self._inner, other._inner, True) + else: + return super().minus(other) + return Temporal._factory(result) + + def nearest_approach_distance(self, other): + """Generated regular ``nearest_approach_distance``. + + MEOS Functions: + nad_tnpoint_geo, nad_tnpoint_npoint, nad_tnpoint_stbox, nad_tnpoint_tnpoint + """ + from ...boxes import STBox + from ..tnpoint import TNpoint + + if isinstance(other, shpb.BaseGeometry): + result = nad_tnpoint_geo(self._inner, geo_to_gserialized(other, False)) + elif isinstance(other, Npoint): + result = nad_tnpoint_npoint(self._inner, other._inner) + elif isinstance(other, TNpoint): + result = nad_tnpoint_tnpoint(self._inner, other._inner) + elif isinstance(other, STBox): + result = nad_tnpoint_stbox(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result + + def nearest_approach_instant(self, other): + """Generated regular ``nearest_approach_instant``. + + MEOS Functions: + nai_tnpoint_geo, nai_tnpoint_npoint, nai_tnpoint_tnpoint + """ + from ..tnpoint import TNpoint + + if isinstance(other, shpb.BaseGeometry): + result = nai_tnpoint_geo(self._inner, geo_to_gserialized(other, False)) + elif isinstance(other, Npoint): + result = nai_tnpoint_npoint(self._inner, other._inner) + 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, precision: int = 15): + """Generated regular ``shortest_line``. + + MEOS Functions: + shortestline_tnpoint_geo, shortestline_tnpoint_npoint, shortestline_tnpoint_tnpoint + """ + from ..tnpoint import TNpoint + + if isinstance(other, shpb.BaseGeometry): + result = shortestline_tnpoint_geo(self._inner, geo_to_gserialized(other, False)) + elif isinstance(other, Npoint): + result = shortestline_tnpoint_npoint(self._inner, other._inner) + 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) + + def temporal_equal(self, other): + """Generated regular ``temporal_equal``. + + MEOS Functions: + teq_tnpoint_npoint + """ + 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): + """Generated regular ``temporal_not_equal``. + + MEOS Functions: + tne_tnpoint_npoint + """ + if isinstance(other, Npoint): + result = tne_tnpoint_npoint(self._inner, other._inner) + else: + return super().temporal_not_equal(other) + return Temporal._factory(result) diff --git a/pymeos/main/_generated/tpose_methods.py b/pymeos/main/_generated/tpose_methods.py new file mode 100644 index 00000000..b9bb57d8 --- /dev/null +++ b/pymeos/main/_generated/tpose_methods.py @@ -0,0 +1,237 @@ +# Copyright (c) 2016-2026, Université libre de Bruxelles and PyMEOS +# contributors. Licensed under the PostgreSQL License (see LICENSE). +# +# ============================================================================ +# GENERATED by tools/oo_codegen/codegen.py -- DO NOT EDIT. +# Regenerate: +# python3 tools/oo_codegen/codegen.py --idl --mixin pose \ +# --mixin-out pymeos/main/_generated/tpose_methods.py +# ============================================================================ +# +# Wired into pymeos.main.TPose via TPoseRegularMixin. Every method +# dispatches by argument type to the EXACT pymeos_cffi backing the +# hand-written method used -- same native call, same transforms, same +# result, never reimplemented: identical by construction. +"""Generated regular OO methods for the TPose family.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +import shapely.geometry.base as shpb +from pymeos_cffi import * + +from ...collections import FloatSpan +from ...collections.pose import Pose +from ...temporal import Temporal + +if TYPE_CHECKING: + pass + + +class TPoseRegularMixin: + """Generated regular families (comparison, spatial relationship, + distance, restriction) for :class:`TPose`.""" + + def always_equal(self, other): + """Generated regular ``always_equal``. + + MEOS Functions: + always_eq_pose_tpose, always_eq_tpose_pose, always_eq_tpose_tpose + """ + from ..tpose import TPose + + if isinstance(other, Pose): + result = always_eq_tpose_pose(self._inner, other._inner) + elif isinstance(other, TPose): + result = always_eq_tpose_tpose(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def always_not_equal(self, other): + """Generated regular ``always_not_equal``. + + MEOS Functions: + always_ne_pose_tpose, always_ne_tpose_pose, always_ne_tpose_tpose + """ + from ..tpose import TPose + + if isinstance(other, Pose): + result = always_ne_tpose_pose(self._inner, other._inner) + elif isinstance(other, TPose): + result = always_ne_tpose_tpose(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def at(self, other): + """Generated regular ``at``. + + MEOS Functions: + tpose_at_elevation, tpose_at_geom, tpose_at_pose, tpose_at_stbox + """ + from ...boxes import STBox + + if isinstance(other, shpb.BaseGeometry): + result = tpose_at_geom(self._inner, geo_to_gserialized(other, False)) + elif isinstance(other, Pose): + result = tpose_at_pose(self._inner, other._inner) + elif isinstance(other, FloatSpan): + result = tpose_at_elevation(self._inner, other._inner) + elif isinstance(other, STBox): + result = tpose_at_stbox(self._inner, other._inner, True) + else: + return super().at(other) + return Temporal._factory(result) + + def distance(self, other): + """Generated regular ``distance``. + + MEOS Functions: + tdistance_tpose_geo, tdistance_tpose_pose, tdistance_tpose_tpose + """ + from ..tpose import TPose + + if isinstance(other, shpb.BaseGeometry): + result = tdistance_tpose_geo(self._inner, geo_to_gserialized(other, False)) + 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 ever_equal(self, other): + """Generated regular ``ever_equal``. + + MEOS Functions: + ever_eq_pose_tpose, ever_eq_tpose_pose, ever_eq_tpose_tpose + """ + from ..tpose import TPose + + if isinstance(other, Pose): + result = ever_eq_tpose_pose(self._inner, other._inner) + elif isinstance(other, TPose): + result = ever_eq_tpose_tpose(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def ever_not_equal(self, other): + """Generated regular ``ever_not_equal``. + + MEOS Functions: + ever_ne_pose_tpose, ever_ne_tpose_pose, ever_ne_tpose_tpose + """ + from ..tpose import TPose + + if isinstance(other, Pose): + result = ever_ne_tpose_pose(self._inner, other._inner) + elif isinstance(other, TPose): + result = ever_ne_tpose_tpose(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def minus(self, other): + """Generated regular ``minus``. + + MEOS Functions: + tpose_minus_elevation, tpose_minus_geom, tpose_minus_pose, tpose_minus_stbox + """ + from ...boxes import STBox + + if isinstance(other, shpb.BaseGeometry): + result = tpose_minus_geom(self._inner, geo_to_gserialized(other, False)) + elif isinstance(other, Pose): + result = tpose_minus_pose(self._inner, other._inner) + elif isinstance(other, FloatSpan): + result = tpose_minus_elevation(self._inner, other._inner) + elif isinstance(other, STBox): + result = tpose_minus_stbox(self._inner, other._inner, True) + else: + return super().minus(other) + return Temporal._factory(result) + + def nearest_approach_distance(self, other): + """Generated regular ``nearest_approach_distance``. + + MEOS Functions: + nad_tpose_geo, nad_tpose_pose, nad_tpose_stbox, nad_tpose_tpose + """ + from ...boxes import STBox + from ..tpose import TPose + + if isinstance(other, shpb.BaseGeometry): + result = nad_tpose_geo(self._inner, geo_to_gserialized(other, False)) + elif isinstance(other, Pose): + result = nad_tpose_pose(self._inner, other._inner) + elif isinstance(other, TPose): + result = nad_tpose_tpose(self._inner, other._inner) + elif isinstance(other, STBox): + result = nad_tpose_stbox(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result + + def nearest_approach_instant(self, other): + """Generated regular ``nearest_approach_instant``. + + MEOS Functions: + nai_tpose_geo, nai_tpose_pose, nai_tpose_tpose + """ + from ..tpose import TPose + + if isinstance(other, shpb.BaseGeometry): + result = nai_tpose_geo(self._inner, geo_to_gserialized(other, False)) + 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): + """Generated regular ``shortest_line``. + + MEOS Functions: + shortestline_tpose_geo, shortestline_tpose_pose, shortestline_tpose_tpose + """ + from ..tpose import TPose + + if isinstance(other, shpb.BaseGeometry): + result = shortestline_tpose_geo(self._inner, geo_to_gserialized(other, False)) + 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) + + def temporal_equal(self, other): + """Generated regular ``temporal_equal``. + + MEOS Functions: + teq_pose_tpose, teq_tpose_pose + """ + 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): + """Generated regular ``temporal_not_equal``. + + MEOS Functions: + tne_pose_tpose, tne_tpose_pose + """ + if isinstance(other, Pose): + result = tne_tpose_pose(self._inner, other._inner) + else: + return super().temporal_not_equal(other) + return Temporal._factory(result) diff --git a/pymeos/main/_generated/trgeometry_methods.py b/pymeos/main/_generated/trgeometry_methods.py new file mode 100644 index 00000000..8a7e45da --- /dev/null +++ b/pymeos/main/_generated/trgeometry_methods.py @@ -0,0 +1,415 @@ +# Copyright (c) 2016-2026, Université libre de Bruxelles and PyMEOS +# contributors. Licensed under the PostgreSQL License (see LICENSE). +# +# ============================================================================ +# GENERATED by tools/oo_codegen/codegen.py -- DO NOT EDIT. +# Regenerate: +# python3 tools/oo_codegen/codegen.py --idl --mixin rgeo \ +# --mixin-out pymeos/main/_generated/trgeometry_methods.py +# ============================================================================ +# +# Wired into pymeos.main.TRgeometry via TRgeometryRegularMixin. Every method +# dispatches by argument type to the EXACT pymeos_cffi backing the +# hand-written method used -- same native call, same transforms, same +# result, never reimplemented: identical by construction. +"""Generated regular OO methods for the TRgeometry family.""" + +from __future__ import annotations + +from datetime import datetime +from typing import TYPE_CHECKING + +import shapely.geometry.base as shpb +from pymeos_cffi import * + +from ...collections import TsTzSet, TsTzSpan, TsTzSpanSet +from ...collections.pose import Pose, PoseSet +from ...temporal import Temporal +from ..tpoint import TPoint + +if TYPE_CHECKING: + pass + + +class TRgeometryRegularMixin: + """Generated regular families (comparison, spatial relationship, + distance, restriction) for :class:`TRgeometry`.""" + + def always_equal(self, other): + """Generated regular ``always_equal``. + + MEOS Functions: + always_eq_geo_trgeometry, always_eq_trgeometry_geo, always_eq_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = always_eq_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, TRgeometry): + result = always_eq_trgeometry_trgeometry(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def always_intersects(self, other): + """Generated regular ``always_intersects``. + + MEOS Functions: + aintersects_trgeometry_geo, aintersects_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = aintersects_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, TRgeometry): + result = aintersects_trgeometry_trgeometry(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def always_not_equal(self, other): + """Generated regular ``always_not_equal``. + + MEOS Functions: + always_ne_geo_trgeometry, always_ne_trgeometry_geo, always_ne_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = always_ne_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, TRgeometry): + result = always_ne_trgeometry_trgeometry(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def always_touches(self, other): + """Generated regular ``always_touches``. + + MEOS Functions: + atouches_trgeometry_geo + """ + if isinstance(other, shpb.BaseGeometry): + result = atouches_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def at(self, other): + """Generated regular ``at``. + + MEOS Functions: + trgeometry_at_geom, trgeometry_at_stbox, trgeometry_at_timestamptz, trgeometry_at_tstzset, trgeometry_at_tstzspan, trgeometry_at_tstzspanset, trgeometry_at_value, trgeometry_at_values + """ + from ...boxes import STBox + + if isinstance(other, shpb.BaseGeometry): + result = trgeometry_at_geom(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, Pose): + result = trgeometry_at_value(self._inner, other._inner) + elif isinstance(other, PoseSet): + result = trgeometry_at_values(self._inner, other._inner) + elif isinstance(other, STBox): + result = trgeometry_at_stbox(self._inner, other._inner, True) + elif isinstance(other, datetime): + result = trgeometry_at_timestamptz(self._inner, datetime_to_timestamptz(other)) + elif isinstance(other, TsTzSet): + result = trgeometry_at_tstzset(self._inner, other._inner) + elif isinstance(other, TsTzSpan): + result = trgeometry_at_tstzspan(self._inner, other._inner) + elif isinstance(other, TsTzSpanSet): + result = trgeometry_at_tstzspanset(self._inner, other._inner) + else: + return super().at(other) + return Temporal._factory(result) + + def distance(self, other): + """Generated regular ``distance``. + + MEOS Functions: + tdistance_trgeometry_geo, tdistance_trgeometry_tpoint, tdistance_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = tdistance_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, TPoint): + result = tdistance_trgeometry_tpoint(self._inner, other._inner) + elif isinstance(other, TRgeometry): + result = tdistance_trgeometry_trgeometry(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return Temporal._factory(result) + + def ever_equal(self, other): + """Generated regular ``ever_equal``. + + MEOS Functions: + ever_eq_geo_trgeometry, ever_eq_trgeometry_geo, ever_eq_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = ever_eq_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, TRgeometry): + result = ever_eq_trgeometry_trgeometry(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def ever_intersects(self, other): + """Generated regular ``ever_intersects``. + + MEOS Functions: + eintersects_trgeometry_geo, eintersects_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = eintersects_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, TRgeometry): + result = eintersects_trgeometry_trgeometry(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def ever_not_equal(self, other): + """Generated regular ``ever_not_equal``. + + MEOS Functions: + ever_ne_geo_trgeometry, ever_ne_trgeometry_geo, ever_ne_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = ever_ne_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, TRgeometry): + result = ever_ne_trgeometry_trgeometry(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result > 0 + + def ever_touches(self, other): + """Generated regular ``ever_touches``. + + MEOS Functions: + etouches_trgeometry_geo + """ + if isinstance(other, shpb.BaseGeometry): + result = etouches_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def is_always_covers(self, other): + """Generated regular ``is_always_covers``. + + MEOS Functions: + acovers_geo_trgeometry, acovers_trgeometry_geo + """ + if isinstance(other, shpb.BaseGeometry): + result = acovers_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def is_always_disjoint(self, other): + """Generated regular ``is_always_disjoint``. + + MEOS Functions: + adisjoint_trgeometry_geo, adisjoint_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = adisjoint_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, TRgeometry): + result = adisjoint_trgeometry_trgeometry(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, distance): + """Generated regular ``is_always_within_distance``. + + MEOS Functions: + adwithin_trgeometry_geo, adwithin_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = adwithin_trgeometry_geo(self._inner, geometry_to_gserialized(other), distance) + elif isinstance(other, TRgeometry): + result = adwithin_trgeometry_trgeometry(self._inner, other._inner, distance) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def is_ever_covers(self, other): + """Generated regular ``is_ever_covers``. + + MEOS Functions: + ecovers_geo_trgeometry, ecovers_trgeometry_geo + """ + if isinstance(other, shpb.BaseGeometry): + result = ecovers_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def is_ever_disjoint(self, other): + """Generated regular ``is_ever_disjoint``. + + MEOS Functions: + edisjoint_trgeometry_geo, edisjoint_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = edisjoint_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, TRgeometry): + result = edisjoint_trgeometry_trgeometry(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, distance): + """Generated regular ``is_ever_within_distance``. + + MEOS Functions: + edwithin_trgeometry_geo, edwithin_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = edwithin_trgeometry_geo(self._inner, geometry_to_gserialized(other), distance) + elif isinstance(other, TRgeometry): + result = edwithin_trgeometry_trgeometry(self._inner, other._inner, distance) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result == 1 + + def minus(self, other): + """Generated regular ``minus``. + + MEOS Functions: + trgeometry_minus_geom, trgeometry_minus_stbox, trgeometry_minus_timestamptz, trgeometry_minus_tstzset, trgeometry_minus_tstzspan, trgeometry_minus_tstzspanset, trgeometry_minus_value, trgeometry_minus_values + """ + from ...boxes import STBox + + if isinstance(other, shpb.BaseGeometry): + result = trgeometry_minus_geom(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, Pose): + result = trgeometry_minus_value(self._inner, other._inner) + elif isinstance(other, PoseSet): + result = trgeometry_minus_values(self._inner, other._inner) + elif isinstance(other, STBox): + result = trgeometry_minus_stbox(self._inner, other._inner, True) + elif isinstance(other, datetime): + result = trgeometry_minus_timestamptz(self._inner, datetime_to_timestamptz(other)) + elif isinstance(other, TsTzSet): + result = trgeometry_minus_tstzset(self._inner, other._inner) + elif isinstance(other, TsTzSpan): + result = trgeometry_minus_tstzspan(self._inner, other._inner) + elif isinstance(other, TsTzSpanSet): + result = trgeometry_minus_tstzspanset(self._inner, other._inner) + else: + return super().minus(other) + return Temporal._factory(result) + + def nearest_approach_distance(self, other): + """Generated regular ``nearest_approach_distance``. + + MEOS Functions: + nad_stbox_trgeometry, nad_trgeometry_geo, nad_trgeometry_stbox, nad_trgeometry_tpoint, nad_trgeometry_trgeometry + """ + from ...boxes import STBox + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = nad_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, TPoint): + result = nad_trgeometry_tpoint(self._inner, other._inner) + elif isinstance(other, TRgeometry): + result = nad_trgeometry_trgeometry(self._inner, other._inner) + elif isinstance(other, STBox): + result = nad_trgeometry_stbox(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return result + + def nearest_approach_instant(self, other): + """Generated regular ``nearest_approach_instant``. + + MEOS Functions: + nai_trgeometry_geo, nai_trgeometry_tpoint, nai_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = nai_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, TPoint): + result = nai_trgeometry_tpoint(self._inner, other._inner) + elif isinstance(other, TRgeometry): + result = nai_trgeometry_trgeometry(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return Temporal._factory(result) + + def shortest_line(self, other): + """Generated regular ``shortest_line``. + + MEOS Functions: + shortestline_trgeometry_geo, shortestline_trgeometry_tpoint, shortestline_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = shortestline_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + elif isinstance(other, TPoint): + result = shortestline_trgeometry_tpoint(self._inner, other._inner) + elif isinstance(other, TRgeometry): + result = shortestline_trgeometry_trgeometry(self._inner, other._inner) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return gserialized_to_shapely_geometry(result, 10) + + def temporal_equal(self, other): + """Generated regular ``temporal_equal``. + + MEOS Functions: + teq_geo_trgeometry, teq_trgeometry_geo + """ + if isinstance(other, shpb.BaseGeometry): + result = teq_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + else: + return super().temporal_equal(other) + return Temporal._factory(result) + + def temporal_not_equal(self, other): + """Generated regular ``temporal_not_equal``. + + MEOS Functions: + tne_geo_trgeometry, tne_trgeometry_geo + """ + if isinstance(other, shpb.BaseGeometry): + result = tne_trgeometry_geo(self._inner, geometry_to_gserialized(other)) + else: + return super().temporal_not_equal(other) + return Temporal._factory(result) + + def within_distance(self, other, distance): + """Generated regular ``within_distance``. + + MEOS Functions: + tdwithin_geo_trgeometry, tdwithin_trgeometry_geo, tdwithin_trgeometry_trgeometry + """ + from ..trgeometry import TRgeometry + + if isinstance(other, shpb.BaseGeometry): + result = tdwithin_trgeometry_geo(self._inner, geometry_to_gserialized(other), distance) + elif isinstance(other, TRgeometry): + result = tdwithin_trgeometry_trgeometry(self._inner, other._inner, distance) + else: + raise TypeError(f"Operation not supported with type {other.__class__}") + return Temporal._factory(result) diff --git a/pymeos/main/tcbuffer.py b/pymeos/main/tcbuffer.py index a1705eec..3968a53f 100644 --- a/pymeos/main/tcbuffer.py +++ b/pymeos/main/tcbuffer.py @@ -10,17 +10,18 @@ from ..collections.cbuffer import Cbuffer, CbufferSet from ..mixins import TSpatialIO, TTemporallyComparable from ..temporal import Temporal, TInstant, TInterpolation, TSequence, TSequenceSet +from ._generated.tcbuffer_methods import TCbufferRegularMixin from .tfloat import TFloat from .tpoint import TPoint if TYPE_CHECKING: from ..boxes import STBox - from .tbool import TBool Self = TypeVar("Self", bound="TCbuffer") class TCbuffer( + TCbufferRegularMixin, Temporal[Cbuffer, "TCbuffer", "TCbufferInst", "TCbufferSeq", "TCbufferSeqSet"], TTemporallyComparable, TSpatialIO, @@ -382,90 +383,6 @@ def from_mfjson(cls: type[Self], mfjson: str) -> Self: return Temporal._factory(result) # ------------------------- Ever and Always Comparisons ------------------- - def always_equal(self, value: 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: 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: 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: 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: Cbuffer | TCbuffer) -> bool: """ Returns whether the values of `self` are never equal to `value`. @@ -499,230 +416,9 @@ def never_not_equal(self, value: Cbuffer | TCbuffer) -> bool: return not self.ever_not_equal(value) # ------------------------- Temporal Comparisons -------------------------- - def temporal_equal(self, other: 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: 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: 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: 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: 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: 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: 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: 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: shpb.BaseGeometry | Cbuffer | TCbuffer) -> bool: - """ - Returns whether `self` ever contains `other`. - - MEOS Functions: - econtains_tcbuffer_geo, econtains_tcbuffer_cbuffer, - econtains_tcbuffer_tcbuffer - """ - 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) - elif isinstance(other, TCbuffer): - result = econtains_tcbuffer_tcbuffer(self._inner, other._inner) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return result == 1 - def is_ever_contained_in(self, container: shpb.BaseGeometry | Cbuffer) -> bool: """ Returns whether `self` is ever contained by `container`, as @@ -739,24 +435,6 @@ def is_ever_contained_in(self, container: shpb.BaseGeometry | Cbuffer) -> bool: raise TypeError(f"Operation not supported with type {container.__class__}") return result == 1 - def is_ever_covers(self, other: 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_coveredby(self, container: shpb.BaseGeometry | Cbuffer) -> bool: """ Returns whether `self` is ever covered by `container`: the covers @@ -774,101 +452,7 @@ def is_ever_coveredby(self, container: shpb.BaseGeometry | Cbuffer) -> bool: raise TypeError(f"Operation not supported with type {container.__class__}") return result == 1 - def is_ever_disjoint(self, other: shpb.BaseGeometry | Cbuffer | TCbuffer) -> bool: - """ - Returns whether `self` is ever disjoint from `other`. - - MEOS Functions: - edisjoint_tcbuffer_geo, edisjoint_tcbuffer_cbuffer, - edisjoint_tcbuffer_tcbuffer - """ - 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) - elif isinstance(other, TCbuffer): - result = edisjoint_tcbuffer_tcbuffer(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: 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: 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: 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: shpb.BaseGeometry | Cbuffer | TCbuffer) -> bool: - """ - Returns whether `self` always contains `other`. - - MEOS Functions: - acontains_tcbuffer_geo, acontains_tcbuffer_cbuffer, - acontains_tcbuffer_tcbuffer - """ - 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) - elif isinstance(other, TCbuffer): - result = acontains_tcbuffer_tcbuffer(self._inner, other._inner) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return result == 1 - def is_always_contained_in(self, container: shpb.BaseGeometry | Cbuffer) -> bool: """ Returns whether `self` is always contained by `container`, as @@ -885,24 +469,6 @@ def is_always_contained_in(self, container: shpb.BaseGeometry | Cbuffer) -> bool raise TypeError(f"Operation not supported with type {container.__class__}") return result == 1 - def is_always_covers(self, other: shpb.BaseGeometry | Cbuffer | TCbuffer) -> bool: - """ - Returns whether `self` always covers `other`. - - MEOS Functions: - acovers_tcbuffer_geo, acovers_tcbuffer_cbuffer, - acovers_tcbuffer_tcbuffer - """ - 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) - elif isinstance(other, TCbuffer): - result = acovers_tcbuffer_tcbuffer(self._inner, other._inner) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return result == 1 - def is_always_coveredby(self, container: shpb.BaseGeometry | Cbuffer) -> bool: """ Returns whether `self` is always covered by `container`: the covers @@ -920,196 +486,7 @@ def is_always_coveredby(self, container: shpb.BaseGeometry | Cbuffer) -> bool: raise TypeError(f"Operation not supported with type {container.__class__}") return result == 1 - def is_always_disjoint(self, other: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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): diff --git a/pymeos/main/tnpoint.py b/pymeos/main/tnpoint.py index 88db3093..0d3820ae 100644 --- a/pymeos/main/tnpoint.py +++ b/pymeos/main/tnpoint.py @@ -7,13 +7,13 @@ from pymeos_cffi import * from ..collections import * -from ..collections.npoint import Npoint, NpointSet, Nsegment +from ..collections.npoint import Npoint, Nsegment from ..mixins import TSpatialIO, TTemporallyComparable from ..temporal import Temporal, TInstant, TInterpolation, TSequence, TSequenceSet +from ._generated.tnpoint_methods import TNpointRegularMixin if TYPE_CHECKING: from ..boxes import STBox - from .tbool import TBool from .tfloat import TFloat from .tpoint import TGeomPoint @@ -22,6 +22,7 @@ class TNpoint( + TNpointRegularMixin, Temporal[Npoint, "TNpoint", "TNpointInst", "TNpointSeq", "TNpointSeqSet"], TTemporallyComparable, TSpatialIO, @@ -389,90 +390,6 @@ def set_srid(self: Self, srid: int) -> Self: return self.__class__(_inner=tspatial_set_srid(self._inner, srid)) # ------------------------- Ever and Always Comparisons ------------------- - def always_equal(self, value: 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: 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: 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: 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: Npoint | TNpoint) -> bool: """ Returns whether the values of `self` are never equal to `value`. @@ -506,214 +423,8 @@ def never_not_equal(self, value: Npoint | TNpoint) -> bool: return not self.ever_not_equal(value) # ------------------------- Temporal Comparisons -------------------------- - def temporal_equal(self, other: 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: 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: 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_geo, 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_geo(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: 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: 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: 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: 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: 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): diff --git a/pymeos/main/tpose.py b/pymeos/main/tpose.py index b1b4e136..bc1024c1 100644 --- a/pymeos/main/tpose.py +++ b/pymeos/main/tpose.py @@ -10,7 +10,7 @@ from ..collections.pose import Pose, PoseSet from ..mixins import TSpatialIO, TTemporallyComparable from ..temporal import Temporal, TInstant, TInterpolation, TSequence, TSequenceSet -from .tbool import TBool +from ._generated.tpose_methods import TPoseRegularMixin from .tfloat import TFloat from .tpoint import TPoint @@ -21,6 +21,7 @@ class TPose( + TPoseRegularMixin, Temporal[Pose, "TPose", "TPoseInst", "TPoseSeq", "TPoseSeqSet"], TTemporallyComparable, TSpatialIO, @@ -397,90 +398,6 @@ def transform(self: Self, srid: int) -> Self: return Temporal._factory(result) # ------------------------- Ever and Always Comparisons ------------------- - def always_equal(self, value: 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: 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: 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: 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: Pose | TPose) -> bool: """ Returns whether the values of `self` are never equal to `value`. @@ -514,209 +431,8 @@ def never_not_equal(self, value: Pose | TPose) -> bool: return not self.ever_not_equal(value) # ------------------------- Temporal Comparisons -------------------------- - def temporal_equal(self, other: 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: 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: 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: 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: 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_geo, tdistance_tpose_pose, tdistance_tpose_tpose - """ - if isinstance(other, shpb.BaseGeometry): - gs = geo_to_gserialized(other, False) - result = tdistance_tpose_geo(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: 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: 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: 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): diff --git a/pymeos/main/trgeometry.py b/pymeos/main/trgeometry.py index 4cbb25b5..6de0b7a2 100644 --- a/pymeos/main/trgeometry.py +++ b/pymeos/main/trgeometry.py @@ -10,7 +10,7 @@ from ..collections.pose import Pose from ..mixins import TSpatialIO, TTemporallyComparable from ..temporal import Temporal, TInstant, TInterpolation, TSequence, TSequenceSet -from .tbool import TBool +from ._generated.trgeometry_methods import TRgeometryRegularMixin from .tfloat import TFloat from .tpoint import TPoint @@ -22,6 +22,7 @@ class TRgeometry( + TRgeometryRegularMixin, Temporal[ shpb.BaseGeometry, "TRgeometry", @@ -527,98 +528,6 @@ def append_sequence(self: Self, sequence: TRgeometrySeq) -> TRgeometry: return Temporal._factory(result) # ------------------------- Ever and Always Comparisons ------------------- - def always_equal(self, value: 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_trgeometry_geo, always_eq_trgeometry_trgeometry - """ - if isinstance(value, shpb.BaseGeometry): - gs = geometry_to_gserialized(value) - return always_eq_trgeometry_geo(self._inner, gs) > 0 - elif isinstance(value, TRgeometry): - return always_eq_trgeometry_trgeometry(self._inner, value._inner) > 0 - else: - raise TypeError(f"Operation not supported with type {value.__class__}") - - def always_not_equal(self, value: 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_trgeometry_geo, always_ne_trgeometry_trgeometry - """ - if isinstance(value, shpb.BaseGeometry): - gs = geometry_to_gserialized(value) - return always_ne_trgeometry_geo(self._inner, gs) > 0 - elif isinstance(value, TRgeometry): - return always_ne_trgeometry_trgeometry(self._inner, value._inner) > 0 - else: - raise TypeError(f"Operation not supported with type {value.__class__}") - - def ever_equal(self, value: 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_trgeometry_geo, ever_eq_trgeometry_trgeometry - """ - if isinstance(value, shpb.BaseGeometry): - gs = geometry_to_gserialized(value) - return ever_eq_trgeometry_geo(self._inner, gs) > 0 - elif isinstance(value, TRgeometry): - return ever_eq_trgeometry_trgeometry(self._inner, value._inner) > 0 - else: - raise TypeError(f"Operation not supported with type {value.__class__}") - - def ever_not_equal(self, value: 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_trgeometry_geo, ever_ne_trgeometry_trgeometry - """ - if isinstance(value, shpb.BaseGeometry): - gs = geometry_to_gserialized(value) - return ever_ne_trgeometry_geo(self._inner, gs) > 0 - elif isinstance(value, TRgeometry): - return ever_ne_trgeometry_trgeometry(self._inner, value._inner) > 0 - else: - raise TypeError(f"Operation not supported with type {value.__class__}") - def never_equal(self, value: shpb.BaseGeometry | TRgeometry) -> bool: """ Returns whether the values of `self` are never equal to `value`. @@ -654,49 +563,6 @@ def never_not_equal(self, value: shpb.BaseGeometry | TRgeometry) -> bool: return not self.ever_not_equal(value) # ------------------------- Temporal Comparisons -------------------------- - def temporal_equal(self, other: 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_trgeometry_geo, teq_temporal_temporal - """ - if isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other) - result = teq_trgeometry_geo(self._inner, gs) - else: - return super().temporal_equal(other) - return Temporal._factory(result) - - def temporal_not_equal(self, other: 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_trgeometry_geo, tne_temporal_temporal - """ - if isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other) - result = tne_trgeometry_geo(self._inner, gs) - else: - return super().temporal_not_equal(other) - return Temporal._factory(result) - # ------------------------- Ever Spatial Relationships -------------------- # The names and dispatch are those of #TCbuffer.is_ever_covers and its # siblings, and #TPoint.is_ever_contained_in for a geometry containing self. @@ -713,19 +579,6 @@ def is_ever_contained_in(self, container: shpb.BaseGeometry) -> bool: raise TypeError(f"Operation not supported with type {container.__class__}") return result == 1 - def is_ever_covers(self, other: shpb.BaseGeometry) -> bool: - """ - Returns whether `self` ever covers `other`. - - MEOS Functions: - ecovers_trgeometry_geo - """ - if isinstance(other, shpb.BaseGeometry): - result = ecovers_trgeometry_geo(self._inner, geo_to_gserialized(other, False)) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return result == 1 - def is_ever_coveredby(self, container: shpb.BaseGeometry) -> bool: """ Returns whether `self` is ever covered by `container`: the covers @@ -741,64 +594,6 @@ def is_ever_coveredby(self, container: shpb.BaseGeometry) -> bool: raise TypeError(f"Operation not supported with type {container.__class__}") return result == 1 - def is_ever_disjoint(self, other: shpb.BaseGeometry | TRgeometry) -> bool: - """ - Returns whether `self` is ever disjoint from `other`. - - MEOS Functions: - edisjoint_trgeometry_geo, edisjoint_trgeometry_trgeometry - """ - if isinstance(other, shpb.BaseGeometry): - result = edisjoint_trgeometry_geo(self._inner, geo_to_gserialized(other, False)) - elif isinstance(other, TRgeometry): - result = edisjoint_trgeometry_trgeometry(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: shpb.BaseGeometry | TRgeometry, distance: float) -> bool: - """ - Returns whether `self` is ever within `distance` of `other`. - - MEOS Functions: - edwithin_trgeometry_geo, edwithin_trgeometry_trgeometry - """ - if isinstance(other, shpb.BaseGeometry): - result = edwithin_trgeometry_geo(self._inner, geo_to_gserialized(other, False), distance) - elif isinstance(other, TRgeometry): - result = edwithin_trgeometry_trgeometry(self._inner, other._inner, distance) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return result == 1 - - def ever_intersects(self, other: shpb.BaseGeometry | TRgeometry) -> bool: - """ - Returns whether `self` ever intersects `other`. - - MEOS Functions: - eintersects_trgeometry_geo, eintersects_trgeometry_trgeometry - """ - if isinstance(other, shpb.BaseGeometry): - result = eintersects_trgeometry_geo(self._inner, geo_to_gserialized(other, False)) - elif isinstance(other, TRgeometry): - result = eintersects_trgeometry_trgeometry(self._inner, other._inner) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return result == 1 - - def ever_touches(self, other: shpb.BaseGeometry) -> bool: - """ - Returns whether `self` ever touches `other`. - - MEOS Functions: - etouches_trgeometry_geo - """ - if isinstance(other, shpb.BaseGeometry): - result = etouches_trgeometry_geo(self._inner, geo_to_gserialized(other, False)) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return result == 1 - # ------------------------- Always Spatial Relationships ------------------ def is_always_contained_in(self, container: shpb.BaseGeometry) -> bool: """ @@ -813,19 +608,6 @@ def is_always_contained_in(self, container: shpb.BaseGeometry) -> bool: raise TypeError(f"Operation not supported with type {container.__class__}") return result == 1 - def is_always_covers(self, other: shpb.BaseGeometry) -> bool: - """ - Returns whether `self` always covers `other`. - - MEOS Functions: - acovers_trgeometry_geo - """ - if isinstance(other, shpb.BaseGeometry): - result = acovers_trgeometry_geo(self._inner, geo_to_gserialized(other, False)) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return result == 1 - def is_always_coveredby(self, container: shpb.BaseGeometry) -> bool: """ Returns whether `self` is always covered by `container`: the covers @@ -841,122 +623,7 @@ def is_always_coveredby(self, container: shpb.BaseGeometry) -> bool: raise TypeError(f"Operation not supported with type {container.__class__}") return result == 1 - def is_always_disjoint(self, other: shpb.BaseGeometry | TRgeometry) -> bool: - """ - Returns whether `self` is always disjoint from `other`. - - MEOS Functions: - adisjoint_trgeometry_geo, adisjoint_trgeometry_trgeometry - """ - if isinstance(other, shpb.BaseGeometry): - result = adisjoint_trgeometry_geo(self._inner, geo_to_gserialized(other, False)) - elif isinstance(other, TRgeometry): - result = adisjoint_trgeometry_trgeometry(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: shpb.BaseGeometry | TRgeometry, distance: float) -> bool: - """ - Returns whether `self` is always within `distance` of `other`. - - MEOS Functions: - adwithin_trgeometry_geo, adwithin_trgeometry_trgeometry - """ - if isinstance(other, shpb.BaseGeometry): - result = adwithin_trgeometry_geo(self._inner, geo_to_gserialized(other, False), distance) - elif isinstance(other, TRgeometry): - result = adwithin_trgeometry_trgeometry(self._inner, other._inner, distance) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return result == 1 - - def always_intersects(self, other: shpb.BaseGeometry | TRgeometry) -> bool: - """ - Returns whether `self` always intersects `other`. - - MEOS Functions: - aintersects_trgeometry_geo, aintersects_trgeometry_trgeometry - """ - if isinstance(other, shpb.BaseGeometry): - result = aintersects_trgeometry_geo(self._inner, geo_to_gserialized(other, False)) - elif isinstance(other, TRgeometry): - result = aintersects_trgeometry_trgeometry(self._inner, other._inner) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return result == 1 - - def always_touches(self, other: shpb.BaseGeometry) -> bool: - """ - Returns whether `self` always touches `other`. - - MEOS Functions: - atouches_trgeometry_geo - """ - if isinstance(other, shpb.BaseGeometry): - result = atouches_trgeometry_geo(self._inner, geo_to_gserialized(other, False)) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return result == 1 - # ------------------------- 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: - trgeometry_restrict_timestamptz, trgeometry_restrict_tstzset, - trgeometry_restrict_tstzspan, trgeometry_restrict_tstzspanset - """ - if isinstance(other, datetime): - result = trgeometry_restrict_timestamptz(self._inner, datetime_to_timestamptz(other), True) - elif isinstance(other, TsTzSet): - result = trgeometry_restrict_tstzset(self._inner, other._inner, True) - elif isinstance(other, TsTzSpan): - result = trgeometry_restrict_tstzspan(self._inner, other._inner, True) - elif isinstance(other, TsTzSpanSet): - result = trgeometry_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: - trgeometry_restrict_timestamptz, trgeometry_restrict_tstzset, - trgeometry_restrict_tstzspan, trgeometry_restrict_tstzspanset - """ - if isinstance(other, datetime): - result = trgeometry_restrict_timestamptz(self._inner, datetime_to_timestamptz(other), False) - elif isinstance(other, TsTzSet): - result = trgeometry_restrict_tstzset(self._inner, other._inner, False) - elif isinstance(other, TsTzSpan): - result = trgeometry_restrict_tstzspan(self._inner, other._inner, False) - elif isinstance(other, TsTzSpanSet): - result = trgeometry_restrict_tstzspanset(self._inner, other._inner, False) - else: - return super().minus(other) - return Temporal._factory(result) - # ------------------------- Splitting ------------------------------------- def segments(self) -> list[TRgeometrySeq]: """ @@ -1025,107 +692,6 @@ def after(self, timestamp: datetime, strict: bool = False) -> TRgeometry: return Temporal._factory(result) # ------------------------- Distance Operations --------------------------- - def distance(self, other: 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_trgeometry_geo, tdistance_trgeometry_tpoint, tdistance_trgeometry_trgeometry - """ - if isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other) - result = tdistance_trgeometry_geo(self._inner, gs) - elif isinstance(other, TPoint): - result = tdistance_trgeometry_tpoint(self._inner, other._inner) - elif isinstance(other, TRgeometry): - result = tdistance_trgeometry_trgeometry(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: 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_trgeometry_geo, nad_trgeometry_stbox, nad_trgeometry_tpoint, nad_trgeometry_trgeometry - """ - from ..boxes import STBox - - if isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other) - return nad_trgeometry_geo(self._inner, gs) - elif isinstance(other, STBox): - return nad_trgeometry_stbox(self._inner, other._inner) - elif isinstance(other, TPoint): - return nad_trgeometry_tpoint(self._inner, other._inner) - elif isinstance(other, TRgeometry): - return nad_trgeometry_trgeometry(self._inner, other._inner) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - - def nearest_approach_instant(self, other: 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_trgeometry_geo, nai_trgeometry_tpoint, nai_trgeometry_trgeometry - """ - if isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other) - result = nai_trgeometry_geo(self._inner, gs) - elif isinstance(other, TPoint): - result = nai_trgeometry_tpoint(self._inner, other._inner) - elif isinstance(other, TRgeometry): - result = nai_trgeometry_trgeometry(self._inner, other._inner) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return Temporal._factory(result) - - def shortest_line(self, other: 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_trgeometry_geo, shortestline_trgeometry_tpoint, - shortestline_trgeometry_trgeometry - """ - if isinstance(other, shpb.BaseGeometry): - gs = geometry_to_gserialized(other) - result = shortestline_trgeometry_geo(self._inner, gs) - elif isinstance(other, TPoint): - result = shortestline_trgeometry_tpoint(self._inner, other._inner) - elif isinstance(other, TRgeometry): - result = shortestline_trgeometry_trgeometry(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): diff --git a/tests/main/tpose_test.py b/tests/main/tpose_test.py index b93d463b..0cbf921d 100644 --- a/tests/main/tpose_test.py +++ b/tests/main/tpose_test.py @@ -4,6 +4,7 @@ import pytest from pymeos import ( + FloatSpan, Pose, PoseSet, TInterpolation, @@ -117,6 +118,21 @@ def test_never_not_equal(self): class TestTPoseRestrictions(TestTPose): tps = TPoseSeq("[Pose(Point(1 1), 0.5)@2019-09-01, Pose(Point(2 2), 0.3)@2019-09-02]") + tps3d = TPoseSeq("[Pose(Point(1 1 1),0.0,0.0,0.0,1.0)@2001-01-01, Pose(Point(1 1 3),0.0,0.0,0.0,1.0)@2001-01-03]") + + def test_at_elevation(self): + assert str(self.tps3d.at(FloatSpan("[1.5, 2]"))) == ( + "{[Pose(POINT Z (1 1 1.5),0,0,0,1)@2001-01-01 12:00:00+00, " + "Pose(POINT Z (1 1 2),0,0,0,1)@2001-01-02 00:00:00+00]}" + ) + + def test_minus_elevation(self): + assert str(self.tps3d.minus(FloatSpan("[1.5, 2]"))) == ( + "{[Pose(POINT Z (1 1 1),0,0,0,1)@2001-01-01 00:00:00+00, " + "Pose(POINT Z (1 1 1.5),0,0,0,1)@2001-01-01 12:00:00+00), " + "(Pose(POINT Z (1 1 2),0,0,0,1)@2001-01-02 00:00:00+00, " + "Pose(POINT Z (1 1 3),0,0,0,1)@2001-01-03 00:00:00+00]}" + ) def test_at_pose(self): result = self.tps.at(Pose("Pose(Point(1 1), 0.5)")) diff --git a/tests/main/trgeometry_test.py b/tests/main/trgeometry_test.py index 291ce0cd..27fab869 100644 --- a/tests/main/trgeometry_test.py +++ b/tests/main/trgeometry_test.py @@ -13,6 +13,7 @@ TRgeometryInst, TRgeometrySeq, TRgeometrySeqSet, + TsTzSpan, ) from tests.conftest import TestPyMEOS @@ -198,6 +199,33 @@ 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) + moving = TRgeometrySeq("Polygon((0 0,1 0,0 1,0 0));[Pose(Point(1 1),0)@2001-01-01, Pose(Point(3 1),0)@2001-01-03]") + + def test_at_timestamptz_value(self): + assert str(self.moving.at(datetime(2001, 1, 2, tzinfo=timezone.utc))) == ( + "POLYGON((0 0,1 0,0 1,0 0));Pose(POINT(2 1),0)@2001-01-02 00:00:00+00" + ) + + def test_at_minus_tstzspan(self): + span = TsTzSpan("[2001-01-01, 2001-01-02]") + assert str(self.moving.at(span)) == ( + "POLYGON((0 0,1 0,0 1,0 0));[Pose(POINT(1 1),0)@2001-01-01 00:00:00+00, " + "Pose(POINT(2 1),0)@2001-01-02 00:00:00+00]" + ) + assert str(self.moving.minus(span)) == ( + "POLYGON((0 0,1 0,0 1,0 0));{(Pose(POINT(2 1),0)@2001-01-02 00:00:00+00, " + "Pose(POINT(3 1),0)@2001-01-03 00:00:00+00]}" + ) + + def test_at_pose(self): + assert str(self.moving.at(Pose("Pose(Point(1 1),0)"))) == ( + "POLYGON((0 0,1 0,0 1,0 0));{[Pose(POINT(1 1),0)@2001-01-01 00:00:00+00]}" + ) + + def test_within_distance(self): + assert str(self.moving.within_distance(self.moving, 1.0)) == ( + "{[t@2001-01-01 00:00:00+00, t@2001-01-03 00:00:00+00]}" + ) def test_at_timestamp(self): result = self.trs.at(datetime(2019, 9, 1, tzinfo=timezone.utc)) diff --git a/tests/test_oo_codegen_coverage.py b/tests/test_oo_codegen_coverage.py new file mode 100644 index 00000000..b9a7dbf7 --- /dev/null +++ b/tests/test_oo_codegen_coverage.py @@ -0,0 +1,93 @@ +"""Coverage and backing gate of the catalog-driven OO method-family generator. + +``tools/oo_codegen/codegen.py`` reads the meos-idl.json catalog of the MEOS the +package builds against (``$MEOS_IDL``, the catalog the test workflow derives) +and accounts for every catalogued function: emitted as a regular method or +counted under a named exclusion, none unaccounted, all six temporal type +families present. The mixins committed in ``pymeos/main/_generated/`` are the +generator's output on that catalog as #format_mixin lays it out, and every MEOS function they call is a +function of the catalog. The tests call the generator's own #collect and +#emit_faithful_mixin rather than restating them. + + MEOS_IDL= python3 -m pytest tests/test_oo_codegen_coverage.py +""" + +import ast +import json +import os +import sys +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +TOOLS = ROOT / "tools" / "oo_codegen" +sys.path.insert(0, str(TOOLS)) + +import codegen as cg # noqa: E402 (the generator, found through the path set above) + +IN_SCOPE = ["cbuffer", "geo", "npoint", "pose", "rgeo", "temporal"] +# The conversions the binding owns, which the mixins call beside MEOS. +BINDING_CONVERSIONS = { + "datetime_to_timestamptz", + "geo_to_gserialized", + "geometry_to_gserialized", + "gserialized_to_shapely_geometry", +} + + +def _idl(): + """The catalog $MEOS_IDL names, as #collect reads it.""" + path = os.environ.get("MEOS_IDL") + if not path: + raise RuntimeError( + "MEOS_IDL names no catalog: set it to the meos-idl.json of the MEOS the package builds against" + ) + return json.loads(Path(path).read_text()) + + +def _called_names(tree): + """The names of the plain functions a module calls, as #emit_faithful_mixin writes them.""" + return {node.func.id for node in ast.walk(tree) if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)} + + +class CoverageAccounting(unittest.TestCase): + """The accounting #collect states.""" + + def test_zero_unaccounted(self): + idl = _idl() + _, st = cg.collect(idl) + accounted = st.emitted_overloads + st.datum + st.irregular + st.out_of_scope + self.assertEqual(len(idl["functions"]) - accounted, 0) + + def test_all_six_families_present(self): + fams, _ = cg.collect(_idl()) + self.assertEqual(sorted(fams), IN_SCOPE) + + +class BackingValidity(unittest.TestCase): + """The committed mixins are #emit_faithful_mixin's output and call catalog functions.""" + + def test_committed_matches_generator(self): + fams, _ = cg.collect(_idl()) + for family in cg.FAMILY_MODEL: + path = cg.mixin_path(family) + with self.subTest(family=family): + generated = cg.format_mixin(cg.emit_faithful_mixin(family, fams[family]), path) + self.assertEqual( + path.read_text(), + generated, + f"{path.name} is not the generator's output: run codegen.py --mixin {family}", + ) + + def test_every_called_function_is_in_the_catalog(self): + names = {f["name"] for f in _idl()["functions"]} + for family in cg.FAMILY_MODEL: + path = cg.mixin_path(family) + called = _called_names(ast.parse(path.read_text())) + meos = {n for n in called if "_" in n and n.islower()} - BINDING_CONVERSIONS + with self.subTest(module=path.name): + self.assertEqual(sorted(meos - names), []) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/oo_codegen/.gitignore b/tools/oo_codegen/.gitignore new file mode 100644 index 00000000..c18dd8d8 --- /dev/null +++ b/tools/oo_codegen/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/tools/oo_codegen/README.md b/tools/oo_codegen/README.md new file mode 100644 index 00000000..3497a0e7 --- /dev/null +++ b/tools/oo_codegen/README.md @@ -0,0 +1,37 @@ +# PyMEOS OO method-family generator + +`codegen.py` reads the `meos-idl.json` catalog of the MEOS the package builds +against, the catalog the [MEOS-API](https://github.com/MobilityDB/MEOS-API) +parser derives from the installed MEOS headers, and writes the *regular* +method families of the temporal classes as mixins of the package: +comparison, spatial relationship (ever, always, temporal), distance and +restriction. + +Each family `FAMILY_MODEL` describes gets one mixin, +`pymeos/main/_generated/_methods.py`, which its temporal class takes +as its first base. Every generated method dispatches by the argument's type +to the `pymeos_cffi` function of that overload; nothing is reimplemented. +The irregular core (constructors, conversions, accessors, transforms, input +and output) stays hand-written in `pymeos/main/.py`. + +## Running + +``` +export MEOS_IDL= +python3 tools/oo_codegen/codegen.py --check # accounting gate +python3 tools/oo_codegen/codegen.py --mixin cbuffer # rewrite one mixin +``` + +`--check` fails when a catalogued function is neither emitted nor counted +under a named exclusion (Datum-bearing internals, the hand-written core, +functions outside the temporal families). `--mixin` writes the mixin of one +family through `ruff check --fix` and `ruff format`, so the committed file +is the generator's output and passes the repository's ruff workflow. A +catalog argument token the family model does not map stops the generator +with its name, so a new MEOS overload is never dropped silently. + +The test workflow derives the catalog from the MobilityDB branch the package +version names and sets `MEOS_IDL` for pytest; +`tests/test_oo_codegen_coverage.py` checks the accounting, that every +committed mixin is the generator's output on that catalog, and that every +function a mixin calls is a function of the catalog. diff --git a/tools/oo_codegen/codegen.py b/tools/oo_codegen/codegen.py new file mode 100644 index 00000000..01d09378 --- /dev/null +++ b/tools/oo_codegen/codegen.py @@ -0,0 +1,664 @@ +#!/usr/bin/env python3 +# Copyright (c) 2016-2026, Université libre de Bruxelles and PyMEOS +# contributors. Licensed under the PostgreSQL License (see LICENSE). +# +# Catalog-driven generator of PyMEOS's regular OO method families. +# +# The temporal classes of pymeos/main spell out, uniformly across the +# temporal types, the regular method families: comparison, spatial +# relationship (ever, always, temporal), distance and restriction. Each +# member is an isinstance ladder forwarding self._inner and the unwrapped +# argument to a typed MEOS overload and post-processing the result. This +# tool reads the meos-idl.json catalog of the MEOS the package builds +# against (--idl, or $MEOS_IDL, the catalog the test workflow derives), +# groups the regular-family C functions by temporal type family and member, +# and writes, for each family FAMILY_MODEL describes, one mixin of +# dispatching methods into pymeos/main/_generated/_methods.py, laid +# out by the repository's ruff configuration. Each generated method calls +# the pymeos_cffi function of its overload; nothing is reimplemented. +# +# The irregular core -- constructors, conversions, accessors, transforms, +# input and output -- stays hand-written. The generator counts and names +# every function it does not emit, and --check fails when one is neither +# emitted nor counted. +# +# Usage: +# python3 tools/oo_codegen/codegen.py --idl --check +# python3 tools/oo_codegen/codegen.py --idl --mixin + +from __future__ import annotations + +import argparse +import json +import os +import re +import subprocess +import sys +from collections import defaultdict +from dataclasses import dataclass, field +from pathlib import Path + +HERE = Path(__file__).resolve().parent + +# The six in-scope temporal type families, witnessed on the C function name. +# Order is significant: the most specific token wins, so `geo` does not swallow a +# `tcbuffer`/`tnpoint`/`tpose`/`trgeometry` name. +FAMILY_PATTERNS = [ + ("cbuffer", r"cbuffer"), + ("npoint", r"npoint"), + ("pose", r"(^|_)t?pose(_|$)"), + ("rgeo", r"(t?rgeo|trgeometry|rgeometry)"), + ("geo", r"(^|_)(tgeo|tgeompoint|tgeogpoint|tpoint|tspatial|geo|geom|geog)(_|$)"), + ("temporal", r"(^|_)(temporal|tnumber|tbool|tint|tfloat|ttext)(_|$)"), +] +_FAMILY_RE = [(f, re.compile(p, re.I)) for f, p in FAMILY_PATTERNS] + +# Result post-processing kind for each regular member class: +# bool_gt0 -> `> 0` (ever/always equality predicates) +# bool_eq1 -> `== 1` (ever/always spatial relationships) +# temporal -> Temporal._factory (temporal predicates / restriction / +# temporal comparison / temporal distance) +# scalar -> pass through (nearest-approach distance: float) +# wrap -> generic factory (nearest-approach instant, shortest line) +# +# Member spec: C-name prefix -> (oo_method_name, result_kind). These are the +# exact prefixes observed in meos-idl.json across the six family headers; the +# oo_method_name matches the hand-written method so a reviewer can diff them +# line for line. +MEMBER_SPEC: dict[str, tuple[str, str]] = { + # --- comparison (boolean) --- + "always_eq": ("always_equal", "bool_gt0"), + "always_ne": ("always_not_equal", "bool_gt0"), + "ever_eq": ("ever_equal", "bool_gt0"), + "ever_ne": ("ever_not_equal", "bool_gt0"), + # --- comparison (temporal) --- + "teq": ("temporal_equal", "temporal"), + "tne": ("temporal_not_equal", "temporal"), + "tlt": ("temporal_less", "temporal"), + "tle": ("temporal_less_or_equal", "temporal"), + "tgt": ("temporal_greater", "temporal"), + "tge": ("temporal_greater_or_equal", "temporal"), + # --- spatial relationship: ever --- + "econtains": ("is_ever_contains", "bool_eq1"), + "ecovers": ("is_ever_covers", "bool_eq1"), + "edisjoint": ("is_ever_disjoint", "bool_eq1"), + "edwithin": ("is_ever_within_distance", "bool_eq1"), + "eintersects": ("ever_intersects", "bool_eq1"), + "etouches": ("ever_touches", "bool_eq1"), + # --- spatial relationship: always --- + "acontains": ("is_always_contains", "bool_eq1"), + "acovers": ("is_always_covers", "bool_eq1"), + "adisjoint": ("is_always_disjoint", "bool_eq1"), + "adwithin": ("is_always_within_distance", "bool_eq1"), + "aintersects": ("always_intersects", "bool_eq1"), + "atouches": ("always_touches", "bool_eq1"), + # --- spatial relationship: temporal --- + "tcontains": ("contains", "temporal"), + "tcovers": ("covers", "temporal"), + "tdisjoint": ("disjoint", "temporal"), + "tdwithin": ("within_distance", "temporal"), + "tintersects": ("intersects", "temporal"), + "ttouches": ("touches", "temporal"), + # --- distance --- + "tdistance": ("distance", "temporal"), + "nad": ("nearest_approach_distance", "scalar"), + "nai": ("nearest_approach_instant", "wrap"), + "shortestline": ("shortest_line", "wrap"), +} + +# Regular restriction members are spelled `_at_*` / +# `_minus_*` (and the generic `temporal_at_*`/`temporal_minus_*` +# fallbacks), so they are matched structurally rather than by a fixed prefix. +_RESTRICT_RE = re.compile(r"^[a-z0-9]+_(at|minus)_[a-z0-9]+$") + +# Longest-first so `always_eq`/`ever_eq` are tried before any shorter token +# and `tdistance` before a hypothetical `t`-prefixed member. +_MEMBER_PREFIXES = sorted(MEMBER_SPEC, key=len, reverse=True) + + +def _family_of(cname: str) -> str | None: + for fam, rx in _FAMILY_RE: + if rx.search(cname): + return fam + return None + + +def _is_datum_internal(entry: dict) -> bool: + """Datum-bearing functions are MEOS-internal helpers the hand-written + surface re-exposes through typed overloads; the codegen cannot synthesise + those overloads from signatures, so they are an explicit counted + exclusion, never a silent gap (mirrors GoMEOS codegen).""" + if "Datum" in entry["returnType"]["c"].split(): + return True + return any("Datum" in p["cType"].split() for p in entry["params"]) + + +def _arg_token(cname: str, member: str, kind: str) -> str: + """The dispatch key for an overload: the trailing C-name token(s) after + the member prefix and the type-family token. Mirrors the isinstance + ladder of the hand-written method but is derived deterministically from + the catalog (e.g. ``econtains_tcbuffer_cbuffer`` -> ``cbuffer``, + ``temporal_at_tstzspan`` -> ``tstzspan``).""" + if kind == "restrict": + # `_at_` -> arg is everything after `_at_`/`_minus_`. + return re.sub(r"^[a-z0-9]+_(at|minus)_", "", cname) + rest = cname[len(member) :].lstrip("_") + parts = rest.split("_") + # Drop the first token (the type-family token: tcbuffer/tnpoint/...); the + # remainder is the argument kind. A single token means a self-only + # overload (rare); key it on the type family itself. + return "_".join(parts[1:]) if len(parts) > 1 else (parts[0] if parts else "") + + +@dataclass +class Method: + oo_name: str + kind: str + # arg-kind token -> backing pymeos_cffi C function name + overloads: dict[str, str] = field(default_factory=dict) + c_names: list[str] = field(default_factory=list) + # backing C function name -> its IDL canonical param type list (used by + # the faithful-mixin emitter to detect the trailing `distance` arg and + # the restriction `*_stbox` border flag). + params: dict[str, list[str]] = field(default_factory=dict) + + +@dataclass +class Stats: + emitted_methods: int = 0 + emitted_overloads: int = 0 + datum: int = 0 + irregular: int = 0 + out_of_scope: int = 0 + by_family: dict = field(default_factory=lambda: defaultdict(int)) + + +def collect(idl: dict) -> tuple[dict[str, dict[str, Method]], Stats]: + """Group regular-family functions into family -> {oo_name: Method}.""" + fams: dict[str, dict[str, Method]] = defaultdict(dict) + st = Stats() + for entry in idl["functions"]: + cname = entry["name"] + fam = _family_of(cname) + if fam is None: + st.out_of_scope += 1 + continue + if _is_datum_internal(entry): + st.datum += 1 + continue + + member = next( + (p for p in _MEMBER_PREFIXES if cname == p or cname.startswith(p + "_")), + None, + ) + if member is not None: + oo_name, kind = MEMBER_SPEC[member] + token = _arg_token(cname, member, "member") + elif _RESTRICT_RE.match(cname): + verb = _RESTRICT_RE.match(cname).group(1) + oo_name, kind = ("at" if verb == "at" else "minus"), "restrict" + token = _arg_token(cname, "", "restrict") + else: + # In-scope but not a regular family member: a constructor, + # conversion, accessor, transform or I/O function that stays + # hand-written. Counted, not dropped. + st.irregular += 1 + continue + + meth = fams[fam].setdefault(oo_name, Method(oo_name, kind)) + # First overload wins a key; collisions across headers are recorded + # so a reviewer sees every backing symbol. + meth.overloads.setdefault(token or fam, cname) + meth.c_names.append(cname) + meth.params[cname] = [p.get("canonical", p.get("cType", "")) for p in entry["params"]] + st.emitted_overloads += 1 + st.by_family[fam] += 1 + return fams, st + + +# --- mixin emission ----------------------------------------------------- +# +# A family's mixin holds the isinstance ladder of each regular member, its +# argument transforms, its result post-processing and its super() or raise +# fallback. A family is wired in when FAMILY_MODEL describes it. + +# Per-family binding: base scalar class, the family's own temporal class, +# the import path of each, and the arg-token -> (isinstance type, call-arg +# expression) map. ``$o`` is the Python argument. Tokens absent from a +# family's map are reported (never silently dropped). +FAMILY_MODEL = { + "cbuffer": { + "mixin_class": "TCbufferRegularMixin", + "base_class": "Cbuffer", + "base_import": "from ...collections.cbuffer import Cbuffer", + "temporal_class": "TCbuffer", + "temporal_import": "from ..tcbuffer import TCbuffer", + # The temporal operand's C-name token. A faithful temporal-type + # mixin uses ONLY `__` overloads (the + # temporal value is the left operand); the reversed `_ + # _` and base-class forms belong to the base + # scalar class, not here -- exactly as the hand-written code does. + "temporal_token": "tcbuffer", + "tokens": { + "cbuffer": ("Cbuffer", "$o._inner"), + "tcbuffer": ("TCbuffer", "$o._inner"), + "geo": ("shpb.BaseGeometry", "geo_to_gserialized($o, False)"), + "geom": ("shpb.BaseGeometry", "geo_to_gserialized($o, False)"), + "stbox": ("STBox", "$o._inner"), + }, + "stbox_lazy": "from ...boxes import STBox", + }, + "pose": { + "mixin_class": "TPoseRegularMixin", + "base_class": "Pose", + "base_import": "from ...collections import FloatSpan\nfrom ...collections.pose import Pose", + "temporal_class": "TPose", + "temporal_import": "from ..tpose import TPose", + "temporal_token": "tpose", + "tokens": { + # tpose_at_elevation / tpose_minus_elevation take the elevation + # span as a float span + "elevation": ("FloatSpan", "$o._inner"), + "pose": ("Pose", "$o._inner"), + "tpose": ("TPose", "$o._inner"), + "geo": ("shpb.BaseGeometry", "geo_to_gserialized($o, False)"), + "geom": ("shpb.BaseGeometry", "geo_to_gserialized($o, False)"), + # tpose distance spells its geometry overload `_tpose_point` + "point": ("shpb.BaseGeometry", "geo_to_gserialized($o, False)"), + "stbox": ("STBox", "$o._inner"), + }, + "stbox_lazy": "from ...boxes import STBox", + }, + "npoint": { + "mixin_class": "TNpointRegularMixin", + "base_class": "Npoint", + "base_import": "from ...collections.npoint import Npoint, NpointSet", + "temporal_class": "TNpoint", + "temporal_import": "from ..tnpoint import TNpoint", + "temporal_token": "tnpoint", + "tokens": { + "npoint": ("Npoint", "$o._inner"), + "tnpoint": ("TNpoint", "$o._inner"), + "npointset": ("NpointSet", "$o._inner"), + "geo": ("shpb.BaseGeometry", "geo_to_gserialized($o, False)"), + "geom": ("shpb.BaseGeometry", "geo_to_gserialized($o, False)"), + # tnpoint distance spells its geometry overload `_tnpoint_point` + "point": ("shpb.BaseGeometry", "geo_to_gserialized($o, False)"), + "stbox": ("STBox", "$o._inner"), + }, + "stbox_lazy": "from ...boxes import STBox", + # tnpoint.shortest_line takes `precision: int = 15` (tcbuffer/tpose + # hardcode 10 with no param -> they omit this key, unaffected). + "shortest_line_precision": 15, + }, + "rgeo": { + "mixin_class": "TRgeometryRegularMixin", + "base_class": "TRgeometry", + # `tpoint` arg token needs TPoint at runtime (isinstance); no + # rgeometry base-value wrapper exists (base value is a shapely + # geometry), so the base import is TPoint. + "base_import": "from datetime import datetime\n\n" + "from ...collections import TsTzSet, TsTzSpan, TsTzSpanSet\n" + "from ...collections.pose import Pose, PoseSet\n" + "from ..tpoint import TPoint", + "temporal_class": "TRgeometry", + "temporal_import": "from ..trgeometry import TRgeometry", + "temporal_token": "trgeometry", + "tokens": { + # rgeo uses geometry_to_gserialized(g) (single arg), not the + # geo_to_gserialized(g, False) the other spatial families use. + "geo": ("shpb.BaseGeometry", "geometry_to_gserialized($o)"), + "trgeometry": ("TRgeometry", "$o._inner"), + # trgeometry_at_* / trgeometry_minus_* restrict to a geometry, a + # pose, a set of poses and the time types + "geom": ("shpb.BaseGeometry", "geometry_to_gserialized($o)"), + "value": ("Pose", "$o._inner"), + "values": ("PoseSet", "$o._inner"), + "timestamptz": ("datetime", "datetime_to_timestamptz($o)"), + "tstzset": ("TsTzSet", "$o._inner"), + "tstzspan": ("TsTzSpan", "$o._inner"), + "tstzspanset": ("TsTzSpanSet", "$o._inner"), + "tpoint": ("TPoint", "$o._inner"), + "stbox": ("STBox", "$o._inner"), + }, + "stbox_lazy": "from ...boxes import STBox", + }, +} + +# Result post-processing, derived verbatim from the hand-written oracle. +_BOOL_GT0 = {"always_equal", "always_not_equal", "ever_equal", "ever_not_equal"} +_BOOL_EQ1 = { + "is_ever_contains", + "is_ever_covers", + "is_ever_disjoint", + "is_ever_within_distance", + "ever_intersects", + "ever_touches", + "is_always_contains", + "is_always_covers", + "is_always_disjoint", + "is_always_within_distance", + "always_intersects", + "always_touches", +} +_SHAPELY = {"shortest_line"} +_RAW = {"nearest_approach_distance"} +# Members whose unmatched-type branch delegates to the generic base impl +# (the rest raise TypeError, mirroring the hand-written code exactly). +_SUPER_FALLBACK = { + "at", + "minus", + "temporal_equal", + "temporal_not_equal", + "temporal_less", + "temporal_less_or_equal", + "temporal_greater", + "temporal_greater_or_equal", +} +# Members that take a trailing ``distance`` argument. +_WITHIN_DISTANCE = { + "is_ever_within_distance", + "is_always_within_distance", + "within_distance", +} + +_ORDER = [ + "geo", + "geom", + "point", + "tpoint", + "trgeo", + "cbuffer", + "tcbuffer", + "npoint", + "tnpoint", + "npointset", + "pose", + "tpose", + "rgeometry", + "trgeometry", + "value", + "values", + "elevation", + "stbox", + "timestamptz", + "tstzset", + "tstzspan", + "tstzspanset", +] + +# oo_method_name -> the C-name member prefix (reverse of MEMBER_SPEC). +_OO_TO_CPREFIX = {oo: pfx for pfx, (oo, _k) in MEMBER_SPEC.items()} + + +def _faithful_overloads(oo_name: str, m: Method, ttok: str) -> dict[str, str]: + """Rebuild the dispatch table for a temporal-type mixin: keep only the + overloads whose C name is ``__`` (or, for + restriction, ``_(at|minus)_``) and key on ````. This + discards reversed-operand / base-class forms so the generated method + calls exactly the backing the hand-written method called.""" + out: dict[str, str] = {} + if oo_name in ("at", "minus"): + pat = re.compile(rf"^{re.escape(ttok)}_{oo_name}_(.+)$") + else: + cpre = _OO_TO_CPREFIX[oo_name] + pat = re.compile(rf"^{re.escape(cpre)}_{re.escape(ttok)}_(.+)$") + for cn in m.c_names: + mm = pat.match(cn) + if mm: + out.setdefault(mm.group(1), cn) + return out + + +_MIXIN_HEADER = '''\ +# Copyright (c) 2016-2026, Université libre de Bruxelles and PyMEOS +# contributors. Licensed under the PostgreSQL License (see LICENSE). +# +# ============================================================================ +# GENERATED by tools/oo_codegen/codegen.py -- DO NOT EDIT. +# Regenerate: +# python3 tools/oo_codegen/codegen.py --idl --mixin {family} \\ +# --mixin-out pymeos/main/_generated/{module}_methods.py +# ============================================================================ +# +# Wired into pymeos.main.{temporal_class} via {mixin_class}. Every method +# dispatches by argument type to the EXACT pymeos_cffi backing the +# hand-written method used -- same native call, same transforms, same +# result, never reimplemented: identical by construction. +"""Generated regular OO methods for the {temporal_class} family.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +import shapely.geometry.base as shpb +from pymeos_cffi import * + +from ...temporal import Temporal +{base_import} + +if TYPE_CHECKING: + {temporal_import} + + +class {mixin_class}: + """Generated regular families (comparison, spatial relationship, + distance, restriction) for :class:`{temporal_class}`.""" +''' + + +def _result_return(oo_name: str, model: dict) -> str: + if oo_name in _BOOL_GT0: + return " return result > 0\n" + if oo_name in _BOOL_EQ1: + return " return result == 1\n" + if oo_name in _RAW: + return " return result\n" + if oo_name in _SHAPELY: + # A family whose shortest_line takes a `precision` argument (tnpoint, + # `precision: int = 15`) says so in its model; the others write 10 + # decimal digits. + if model.get("shortest_line_precision") is not None: + return " return gserialized_to_shapely_geometry(result, precision)\n" + return " return gserialized_to_shapely_geometry(result, 10)\n" + return " return Temporal._factory(result)\n" + + +ROOT = HERE.parent.parent + + +def mixin_path(family: str) -> Path: + """The file of the package a family's mixin is committed in.""" + module = FAMILY_MODEL[family]["temporal_class"].lower() + return ROOT / "pymeos" / "main" / "_generated" / f"{module}_methods.py" + + +def format_mixin(src: str, path: Path) -> str: + """Return a mixin as the repository's ruff configuration writes it: its + imports sorted and its unused ones dropped by ``ruff check --fix``, then + laid out by ``ruff format``, so the committed file is the generator's + output and passes the ruff workflow unchanged.""" + for cmd in (["ruff", "check", "--fix", "--quiet", "--exit-zero"], ["ruff", "format", "--quiet"]): + src = subprocess.run( + cmd + ["--stdin-filename", str(path), "-"], + input=src, + capture_output=True, + text=True, + check=True, + cwd=ROOT, + ).stdout + return src + + +def emit_faithful_mixin(family: str, methods: dict[str, Method]) -> str: + """Emit the behaviourally-faithful wired-in mixin for one modelled + family. Raises if a regular overload's arg token has no model mapping + (so a coverage gap is loud, never silent).""" + model = FAMILY_MODEL[family] + tokens = model["tokens"] + ttok = model["temporal_token"] + out = [ + _MIXIN_HEADER.format( + family=family, + mixin_class=model["mixin_class"], + temporal_class=model["temporal_class"], + base_import=model["base_import"], + module=model["temporal_class"].lower(), + temporal_import=model["temporal_import"], + ) + ] + + # Faithful dispatch tables (temporal operand first); reversed/base forms + # discarded. Any arg token without a model mapping is loud, not silent. + faithful = {oo: _faithful_overloads(oo, m, ttok) for oo, m in methods.items()} + unmodelled = sorted({tok for tbl in faithful.values() for tok in tbl if tok not in tokens}) + if unmodelled: + raise SystemExit( + f"[{family}] arg tokens with no FAMILY_MODEL mapping: {unmodelled} -- extend FAMILY_MODEL before wiring" + ) + + for oo_name in sorted(methods): + m = methods[oo_name] + ov = faithful[oo_name] + if not ov: + # No temporal-operand overload (member is base-class only); + # the hand-written class does not expose it either. + continue + has_dist = oo_name in _WITHIN_DISTANCE + sig = "self, other, distance" if has_dist else "self, other" + if oo_name in _SHAPELY and model.get("shortest_line_precision") is not None: + sig = f"self, other, precision: int = {model['shortest_line_precision']}" + body: list[str] = [] + # Lazy imports mirroring the hand-written idiom (self temporal type + # and STBox are imported inside the method to avoid import cycles). + needs_self = any(tokens[t][0] == model["temporal_class"] for t in ov) + needs_stbox = "stbox" in ov + if needs_self: + body.append(f" {model['temporal_import']}\n") + if needs_stbox: + body.append(f" {model['stbox_lazy']}\n") + + unordered = sorted(t for t in ov if t not in _ORDER) + if unordered: + raise SystemExit( + f"[{family}] {oo_name}: arg tokens with no place in _ORDER: {unordered} -- extend _ORDER before wiring" + ) + branches = [t for t in _ORDER if t in ov] + for i, tok in enumerate(branches): + cfn = ov[tok] + pytype, argexpr = tokens[tok] + argexpr = argexpr.replace("$o", "other") + nparams = len(m.params.get(cfn, [])) + call_args = ["self._inner", argexpr] + if has_dist: + call_args.append("distance") + elif oo_name in ("at", "minus") and tok == "stbox" and nparams == 3: + call_args.append("True") + kw = "if" if i == 0 else "elif" + body.append( + f" {kw} isinstance(other, {pytype}):\n result = {cfn}({', '.join(call_args)})\n" + ) + if oo_name in _SUPER_FALLBACK: + body.append(f" else:\n return super().{oo_name}(other)\n") + else: + body.append( + " else:\n" + " raise TypeError(\n" + ' f"Operation not supported with type "\n' + ' f"{other.__class__}"\n' + " )\n" + ) + body.append(_result_return(oo_name, model)) + + meos_fns = ", ".join(sorted(set(m.c_names))) + out.append( + f"\n def {oo_name}({sig}):\n" + f' """Generated regular ``{oo_name}``.\n\n' + f" MEOS Functions:\n" + f" {meos_fns}\n" + f' """\n' + "".join(body) + ) + return "".join(out) + + +# --- driver ------------------------------------------------------------- + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument( + "--idl", + default=os.environ.get("MEOS_IDL"), + required="MEOS_IDL" not in os.environ, + help="the meos-idl.json catalog of the MEOS the package builds against (default: $MEOS_IDL)", + ) + ap.add_argument( + "--check", + action="store_true", + help="exit non-zero if any in-scope function is neither emitted nor counted as an explicit exclusion", + ) + ap.add_argument( + "--mixin", + metavar="FAMILY", + help="write the mixin of one family FAMILY_MODEL describes", + ) + ap.add_argument( + "--mixin-out", + metavar="PATH", + help="destination for --mixin (default: stdout)", + ) + args = ap.parse_args() + + idl = json.loads(Path(args.idl).read_text()) + fams, st = collect(idl) + + if args.mixin: + if args.mixin not in FAMILY_MODEL: + raise SystemExit(f"--mixin {args.mixin!r}: no FAMILY_MODEL (modelled: {sorted(FAMILY_MODEL)})") + out = Path(args.mixin_out) if args.mixin_out else mixin_path(args.mixin) + src = format_mixin(emit_faithful_mixin(args.mixin, fams[args.mixin]), out) + if args.mixin_out: + Path(args.mixin_out).parent.mkdir(parents=True, exist_ok=True) + Path(args.mixin_out).write_text(src) + print( + f"[oo-codegen] wrote faithful mixin " + f"{FAMILY_MODEL[args.mixin]['mixin_class']} -> " + f"{args.mixin_out} ({len(fams[args.mixin])} methods)" + ) + else: + print(src) + return 0 + + st.emitted_methods = sum(len(m) for m in fams.values()) + total = len(idl["functions"]) + accounted = st.emitted_overloads + st.datum + st.irregular + st.out_of_scope + + print(f"[oo-codegen] idl functions : {total}") + print( + f"[oo-codegen] emitted methods : {st.emitted_methods} " + f"({st.emitted_overloads} overloads) across " + f"{len(fams)} families" + ) + for fam in sorted(st.by_family): + print(f" {fam:<9}: {len(fams[fam]):>3} methods, {st.by_family[fam]:>4} overloads") + print(f"[oo-codegen] hand-written core : {st.irregular} (constructors/conversions/accessors/transforms/IO)") + print(f"[oo-codegen] Datum-internal : {st.datum} (excluded)") + print(f"[oo-codegen] out of scope : {st.out_of_scope} (non-temporal-family headers)") + print(f"[oo-codegen] accounted : {accounted}/{total}") + + unaccounted = total - accounted + ok = unaccounted == 0 and len(fams) == 6 + if args.check: + print( + "CHECK: " + + ( + "PASS - every function emitted or explicitly counted, all 6 families present" + if ok + else f"FAIL - {unaccounted} unaccounted, {len(fams)} families" + ) + ) + return 0 if ok else 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main())