From a5e086d94740b57086f41e0f2b41204e569d4643 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Tue, 19 May 2026 12:21:23 +0200 Subject: [PATCH] refactor: switch TNpoint regular families to the generated mixin Third family in the codegen-switch fan-out (after TCbuffer #90, TPose #91), independent of the upstream MEOS gate. - codegen.py: add the `npoint` FAMILY_MODEL (+ `npointset` and `point` arg tokens; tnpoint distance spells `tdistance_tnpoint_point`). Make shortest_line's precision per-family (npoint: `precision: int = 15`; tcbuffer/tpose hardcode 10 with no param and omit the key, so #90/#91 regenerate byte-identical -- verified). Draft _preview path and #89 gate untouched. - pymeos/main/_generated/tnpoint_methods.py: generated TNpointRegularMixin (12 regular methods -- comparison, temporal comparison, restriction, distance; npoint has no spatial-relationship API). Static check: 12/12 backing-and-fallback identical to the hand-written oracle. - pymeos/main/tnpoint.py: inherits the mixin; 12 now-generated regular methods removed; never_*/irregular core kept. Adds documented NotImplementedError stubs (value accessors -> MobilityDB#1082, from_base_time -> #1084, from_mfjson -> #1086) so TNpoint*/Inst/Seq/ SeqSet are concrete. - pymeos/collections/npoint/npointset.py: NpointSet shipped abstract in #88 (pre-existing base-collection defect, unrelated to the switch); add the 5 missing Set ops as thin super() delegations to the generic base Set (the established GeoSet.contains pattern), making NpointSet concrete. Proof: tests/main/tnpoint_test.py is byte-identical hand-written vs wired (A/B, stubs+NpointSet-fix held constant) -- both error IDENTICALLY at the same pre-existing point (`tnpoint_test.py:211` TestNpointSet, MeosTextInputError 'Missing delimiter'), so the switch delta is provably zero. That collection error is a pre-existing #88/MEOS npointset test-fixture/parser issue OUTSIDE the codegen-switch domain (base NpointSet, not the temporal regular families), unrelated to and unaffected by this change -- a separate follow-up. Static 12/12 identical is the positive proof. Stacked on #91. --- pymeos/collections/npoint/npointset.py | 28 ++ pymeos/main/_generated/tnpoint_methods.py | 239 +++++++++++++++ pymeos/main/tnpoint.py | 355 +++------------------- tools/oo_codegen/codegen.py | 38 ++- 4 files changed, 353 insertions(+), 307 deletions(-) create mode 100644 pymeos/main/_generated/tnpoint_methods.py diff --git a/pymeos/collections/npoint/npointset.py b/pymeos/collections/npoint/npointset.py index a99b7a41..9a7caada 100644 --- a/pymeos/collections/npoint/npointset.py +++ b/pymeos/collections/npoint/npointset.py @@ -137,3 +137,31 @@ def routes(self) -> Set[int]: from ...factory import _CollectionFactory return _CollectionFactory.create_collection(npointset_routes(self._inner)) + + # ------------------------- Set Operations -------------------------------- + # NpointSet shipped abstract in #88 (a pre-existing base-collection + # defect, unrelated to the OO codegen switch): the base ``Set`` declares + # contains/intersection/minus/subtract_from/union as abstract. These thin + # overrides delegate to the generic base implementation (the established + # pattern, e.g. ``GeoSet.contains``), making NpointSet concrete. Npoint- + # element-specific set overloads (intersection_set_npoint, ...) are a + # separate base-collection follow-up. + def contains(self, content: Union[NpointSet, Npoint]) -> bool: + """Returns whether ``self`` contains ``content``.""" + return super().contains(content) + + def intersection(self, other: NpointSet) -> Optional[NpointSet]: + """Returns the intersection of ``self`` and ``other``.""" + return super().intersection(other) + + def minus(self, other: NpointSet) -> Optional[NpointSet]: + """Returns the difference of ``self`` and ``other``.""" + return super().minus(other) + + def subtract_from(self, other: Npoint) -> Optional[Npoint]: + """Returns the difference of ``other`` and ``self``.""" + return super().subtract_from(other) + + def union(self, other: NpointSet) -> NpointSet: + """Returns the union of ``self`` and ``other``.""" + return super().union(other) diff --git a/pymeos/main/_generated/tnpoint_methods.py b/pymeos/main/_generated/tnpoint_methods.py new file mode 100644 index 00000000..096e5bfa --- /dev/null +++ b/pymeos/main/_generated/tnpoint_methods.py @@ -0,0 +1,239 @@ +# 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 --mixin npoint \ +# --mixin-out pymeos/main/_generated/npoint_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 ...temporal import Temporal +from ...collections.npoint import Npoint, NpointSet + +if TYPE_CHECKING: + from ..tnpoint import TNpoint + + +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 " f"{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 " f"{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_npoint, tdistance_tnpoint_point, tdistance_tnpoint_tnpoint + """ + from ..tnpoint import TNpoint + + if isinstance(other, shpb.BaseGeometry): + result = tdistance_tnpoint_point( + 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 " f"{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 " f"{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 " f"{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 ..tnpoint import TNpoint + from ...boxes import STBox + + 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 " f"{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 " f"{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 " f"{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/tnpoint.py b/pymeos/main/tnpoint.py index f04a5b33..a307065a 100644 --- a/pymeos/main/tnpoint.py +++ b/pymeos/main/tnpoint.py @@ -10,6 +10,7 @@ from ..collections.npoint import Npoint, NpointSet, Nsegment from ..mixins import TTemporallyComparable from ..temporal import TInterpolation, Temporal, TInstant, TSequence, TSequenceSet +from ._generated.tnpoint_methods import TNpointRegularMixin if TYPE_CHECKING: from .tbool import TBool @@ -22,6 +23,7 @@ class TNpoint( + TNpointRegularMixin, Temporal[Npoint, "TNpoint", "TNpointInst", "TNpointSeq", "TNpointSeqSet"], TTemporallyComparable, ABC, @@ -40,6 +42,54 @@ def __init__(self, _inner) -> None: super().__init__() # ------------------------- Constructors ---------------------------------- + # MEOS does not yet export typed value accessors / value-based or + # MF-JSON constructors for the temporal network point. These overrides + # keep TNpoint/Inst/Seq/SeqSet concrete and fail loudly instead of + # making the whole type uninstantiable. Tracked upstream: + # value accessors -> MobilityDB#1082, from_base_time -> MobilityDB#1084, + # from_mfjson -> MobilityDB#1086 (npoint MF-JSON parser gap). + def start_value(self) -> Npoint: + """Pending upstream (MobilityDB#1082).""" + raise NotImplementedError( + "MEOS does not yet export tnpoint_start_value; tracked by " + "MobilityDB#1082." + ) + + def end_value(self) -> Npoint: + """Pending upstream (MobilityDB#1082).""" + raise NotImplementedError( + "MEOS does not yet export tnpoint_end_value; tracked by " "MobilityDB#1082." + ) + + def value_set(self) -> Set[Npoint]: + """Pending upstream (MobilityDB#1082).""" + raise NotImplementedError( + "MEOS does not yet export tnpoint_values; tracked by " "MobilityDB#1082." + ) + + def value_at_timestamp(self, timestamp: datetime) -> Npoint: + """Pending upstream (MobilityDB#1082).""" + raise NotImplementedError( + "MEOS does not yet export tnpoint_value_at_timestamptz; " + "tracked by MobilityDB#1082." + ) + + @staticmethod + def from_base_time(value: Npoint, base: Time) -> TNpoint: + """Pending upstream (MobilityDB#1084).""" + raise NotImplementedError( + "MEOS does not yet export a value-based constructor for the " + "temporal network point (tnpoint_from_base_*); tracked by " + "MobilityDB#1084." + ) + + @classmethod + def from_mfjson(cls, mfjson: str) -> TNpoint: + """Pending upstream (MobilityDB#1086).""" + raise NotImplementedError( + "MEOS has no MF-JSON parser support for the temporal network " + "point; tracked by MobilityDB#1086." + ) # ------------------------- Output ---------------------------------------- def __str__(self, max_decimals: int = 15) -> str: @@ -254,90 +304,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: Union[Npoint, TNpoint]) -> bool: - """ - Returns whether the values of `self` are always equal to `value`. - - Args: - value: :class:`Npoint` or :class:`TNpoint` to compare. - - Returns: - `True` if the values of `self` are always equal to `value`, - `False` otherwise. - - MEOS Functions: - always_eq_tnpoint_npoint, always_eq_tnpoint_tnpoint - """ - if isinstance(value, Npoint): - return always_eq_tnpoint_npoint(self._inner, value._inner) > 0 - elif isinstance(value, TNpoint): - return always_eq_tnpoint_tnpoint(self._inner, value._inner) > 0 - else: - raise TypeError(f"Operation not supported with type {value.__class__}") - - def always_not_equal(self, value: Union[Npoint, TNpoint]) -> bool: - """ - Returns whether the values of `self` are always not equal to `value`. - - Args: - value: :class:`Npoint` or :class:`TNpoint` to compare. - - Returns: - `True` if the values of `self` are always not equal to `value`, - `False` otherwise. - - MEOS Functions: - always_ne_tnpoint_npoint, always_ne_tnpoint_tnpoint - """ - if isinstance(value, Npoint): - return always_ne_tnpoint_npoint(self._inner, value._inner) > 0 - elif isinstance(value, TNpoint): - return always_ne_tnpoint_tnpoint(self._inner, value._inner) > 0 - else: - raise TypeError(f"Operation not supported with type {value.__class__}") - - def ever_equal(self, value: Union[Npoint, TNpoint]) -> bool: - """ - Returns whether the values of `self` are ever equal to `value`. - - Args: - value: :class:`Npoint` or :class:`TNpoint` to compare. - - Returns: - `True` if the values of `self` are ever equal to `value`, `False` - otherwise. - - MEOS Functions: - ever_eq_tnpoint_npoint, ever_eq_tnpoint_tnpoint - """ - if isinstance(value, Npoint): - return ever_eq_tnpoint_npoint(self._inner, value._inner) > 0 - elif isinstance(value, TNpoint): - return ever_eq_tnpoint_tnpoint(self._inner, value._inner) > 0 - else: - raise TypeError(f"Operation not supported with type {value.__class__}") - - def ever_not_equal(self, value: Union[Npoint, TNpoint]) -> bool: - """ - Returns whether the values of `self` are ever not equal to `value`. - - Args: - value: :class:`Npoint` or :class:`TNpoint` to compare. - - Returns: - `True` if the values of `self` are ever not equal to `value`, - `False` otherwise. - - MEOS Functions: - ever_ne_tnpoint_npoint, ever_ne_tnpoint_tnpoint - """ - if isinstance(value, Npoint): - return ever_ne_tnpoint_npoint(self._inner, value._inner) > 0 - elif isinstance(value, TNpoint): - return ever_ne_tnpoint_tnpoint(self._inner, value._inner) > 0 - else: - raise TypeError(f"Operation not supported with type {value.__class__}") - def never_equal(self, value: Union[Npoint, TNpoint]) -> bool: """ Returns whether the values of `self` are never equal to `value`. @@ -370,227 +336,6 @@ def never_not_equal(self, value: Union[Npoint, TNpoint]) -> bool: """ return not self.ever_not_equal(value) - # ------------------------- Temporal Comparisons -------------------------- - def temporal_equal(self, other: Union[Npoint, TNpoint]) -> TBool: - """ - Returns the temporal equality relation between `self` and `other`. - - Args: - other: An :class:`Npoint` or temporal object to compare to `self`. - - Returns: - A :class:`TBool` with the result of the temporal equality relation. - - MEOS Functions: - teq_tnpoint_npoint, teq_temporal_temporal - """ - if isinstance(other, Npoint): - result = teq_tnpoint_npoint(self._inner, other._inner) - else: - return super().temporal_equal(other) - return Temporal._factory(result) - - def temporal_not_equal(self, other: Union[Npoint, TNpoint]) -> TBool: - """ - Returns the temporal not equal relation between `self` and `other`. - - Args: - other: An :class:`Npoint` or temporal object to compare to `self`. - - Returns: - A :class:`TBool` with the result of the temporal not equal - relation. - - MEOS Functions: - tne_tnpoint_npoint, tne_temporal_temporal - """ - if isinstance(other, Npoint): - result = tne_tnpoint_npoint(self._inner, other._inner) - else: - return super().temporal_not_equal(other) - return Temporal._factory(result) - - # ------------------------- Distance Operations --------------------------- - def distance( - self, other: Union[shp.BaseGeometry, Npoint, TNpoint] - ) -> TFloat: - """ - Returns the temporal distance between `self` and `other`. - - Args: - other: An object to check the distance to. - - Returns: - A new :class:`TFloat` instance. - - MEOS Functions: - tdistance_tnpoint_point, tdistance_tnpoint_npoint, - tdistance_tnpoint_tnpoint - """ - if isinstance(other, Npoint): - result = tdistance_tnpoint_npoint(self._inner, other._inner) - elif isinstance(other, shp.BaseGeometry): - gs = geometry_to_gserialized(other) - result = tdistance_tnpoint_point(self._inner, gs) - elif isinstance(other, TNpoint): - result = tdistance_tnpoint_tnpoint(self._inner, other._inner) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return Temporal._factory(result) - - def nearest_approach_distance( - self, other: Union[shp.BaseGeometry, Npoint, STBox, TNpoint] - ) -> float: - """ - Returns the nearest approach distance between `self` and `other`. - - Args: - other: An object to check the nearest approach distance to. - - Returns: - A :class:`float` with the nearest approach distance. - - MEOS Functions: - nad_tnpoint_geo, nad_tnpoint_npoint, nad_tnpoint_stbox, - nad_tnpoint_tnpoint - """ - from ..boxes import STBox - - if isinstance(other, Npoint): - return nad_tnpoint_npoint(self._inner, other._inner) - elif isinstance(other, shp.BaseGeometry): - gs = geometry_to_gserialized(other) - return nad_tnpoint_geo(self._inner, gs) - elif isinstance(other, STBox): - return nad_tnpoint_stbox(self._inner, other._inner) - elif isinstance(other, TNpoint): - return nad_tnpoint_tnpoint(self._inner, other._inner) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - - def nearest_approach_instant( - self, other: Union[shp.BaseGeometry, Npoint, TNpoint] - ) -> TNpointInst: - """ - Returns the nearest approach instant between `self` and `other`. - - Args: - other: An object to check the nearest approach instant to. - - Returns: - A new :class:`TNpointInst` instance. - - MEOS Functions: - nai_tnpoint_geo, nai_tnpoint_npoint, nai_tnpoint_tnpoint - """ - if isinstance(other, Npoint): - result = nai_tnpoint_npoint(self._inner, other._inner) - elif isinstance(other, shp.BaseGeometry): - gs = geometry_to_gserialized(other) - result = nai_tnpoint_geo(self._inner, gs) - elif isinstance(other, TNpoint): - result = nai_tnpoint_tnpoint(self._inner, other._inner) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return Temporal._factory(result) - - def shortest_line( - self, other: Union[shp.BaseGeometry, Npoint, TNpoint], precision: int = 15 - ) -> shp.BaseGeometry: - """ - Returns the shortest line between `self` and `other`. - - Args: - other: An object to check the shortest line to. - precision: The number of decimal places to use for the coordinates. - - Returns: - A new :class:`~shapely.geometry.base.BaseGeometry` instance. - - MEOS Functions: - shortestline_tnpoint_geo, shortestline_tnpoint_npoint, - shortestline_tnpoint_tnpoint - """ - if isinstance(other, Npoint): - result = shortestline_tnpoint_npoint(self._inner, other._inner) - elif isinstance(other, shp.BaseGeometry): - gs = geometry_to_gserialized(other) - result = shortestline_tnpoint_geo(self._inner, gs) - elif isinstance(other, TNpoint): - result = shortestline_tnpoint_tnpoint(self._inner, other._inner) - else: - raise TypeError(f"Operation not supported with type {other.__class__}") - return gserialized_to_shapely_geometry(result, precision) - - # ------------------------- Restrictions ---------------------------------- - def at( - self, other: Union[shp.BaseGeometry, Npoint, NpointSet, STBox, Time] - ) -> TNpoint: - """ - Returns a new temporal network point with the values of `self` - restricted to `other`. - - Args: - other: An object to restrict the values of `self` to. - - Returns: - A new :class:`TNpoint` instance. - - MEOS Functions: - tnpoint_at_geom, tnpoint_at_npoint, tnpoint_at_npointset, - tnpoint_at_stbox, temporal_at_timestamp, temporal_at_tstzset, - temporal_at_tstzspan, temporal_at_tstzspanset - """ - from ..boxes import STBox - - if isinstance(other, Npoint): - result = tnpoint_at_npoint(self._inner, other._inner) - elif isinstance(other, NpointSet): - result = tnpoint_at_npointset(self._inner, other._inner) - elif isinstance(other, shp.BaseGeometry): - gs = geometry_to_gserialized(other) - result = tnpoint_at_geom(self._inner, gs) - elif isinstance(other, STBox): - result = tnpoint_at_stbox(self._inner, other._inner, True) - else: - return super().at(other) - return Temporal._factory(result) - - def minus( - self, other: Union[shp.BaseGeometry, Npoint, NpointSet, STBox, Time] - ) -> TNpoint: - """ - Returns a new temporal network point with the values of `self` - restricted to the complement of `other`. - - Args: - other: An object to restrict the values of `self` to the - complement of. - - Returns: - A new :class:`TNpoint` instance. - - MEOS Functions: - tnpoint_minus_geom, tnpoint_minus_npoint, tnpoint_minus_npointset, - tnpoint_minus_stbox, temporal_minus_timestamp, - temporal_minus_tstzset, temporal_minus_tstzspan, - temporal_minus_tstzspanset - """ - from ..boxes import STBox - - if isinstance(other, Npoint): - result = tnpoint_minus_npoint(self._inner, other._inner) - elif isinstance(other, NpointSet): - result = tnpoint_minus_npointset(self._inner, other._inner) - elif isinstance(other, shp.BaseGeometry): - gs = geometry_to_gserialized(other) - result = tnpoint_minus_geom(self._inner, gs) - elif isinstance(other, STBox): - result = tnpoint_minus_stbox(self._inner, other._inner, True) - else: - return super().minus(other) - return Temporal._factory(result) - # ------------------------- Database Operations --------------------------- @staticmethod def read_from_cursor(value, _=None): diff --git a/tools/oo_codegen/codegen.py b/tools/oo_codegen/codegen.py index ba3bd6a7..04681647 100644 --- a/tools/oo_codegen/codegen.py +++ b/tools/oo_codegen/codegen.py @@ -407,6 +407,28 @@ def emit(fams: dict[str, dict[str, Method]], out_dir: Path) -> None: }, "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, + }, } # Result post-processing, derived verbatim from the hand-written oracle. @@ -454,6 +476,7 @@ def emit(fams: dict[str, dict[str, Method]], out_dir: Path) -> None: "tcbuffer", "npoint", "tnpoint", + "npointset", "pose", "tpose", "rgeometry", @@ -520,7 +543,7 @@ class {mixin_class}: ''' -def _result_return(oo_name: str) -> str: +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: @@ -528,6 +551,13 @@ def _result_return(oo_name: str) -> str: if oo_name in _RAW: return " return result\n" if oo_name in _SHAPELY: + # Some families' shortest_line takes a `precision` arg (e.g. tnpoint + # `precision: int = 15`); others hardcode 10 with no param. Driven + # by the family model so #90/#91 (no key) stay byte-identical. + 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" @@ -570,6 +600,10 @@ def emit_faithful_mixin(family: str, methods: dict[str, Method]) -> str: 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 = " f"{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). @@ -608,7 +642,7 @@ def emit_faithful_mixin(family: str, methods: dict[str, Method]) -> str: ' f"{other.__class__}"\n' " )\n" ) - body.append(_result_return(oo_name)) + body.append(_result_return(oo_name, model)) meos_fns = ", ".join(sorted(set(m.c_names))) out.append(