meta/object-model.json is the single codegen source of truth for the
class hierarchy implicit in MEOS. The pipeline folds it into
meos-idl.json as objectModel. Every binding/engine (PyMEOS, JMEOS,
MEOS.NET, MobilityDuck, MobilitySpark, …) derives the identical
classes and methods from this one mapping, so the OO surface is no longer
re-curated by hand in each repo.
MEOS is C: it has no classes. The object model is encoded by convention in three places:
- The template axis — the
Temporal/TInstant/TSequence/TSequenceSetstruct family, discriminated by thesubtypefield. - The type-family axis — the
temptypediscriminator. Its base type (e.g.T_TFLOAT→T_FLOAT8) is the missing template parameter; this is the inheritance lattice. - The method binding — a function's name prefix says which class it
is a method of:
temporal_*is the late-bound superclass (every temporal type),tnumber_*/tspatial_*/tpoint_*/tgeo_*are the abstract families,tbool_*/tint_*/tfloat_*/… are the exact leaf types,tinstant_*/tsequence_*/tsequenceset_*are the template subtypes.
The most mature hand-built model (PyMEOS) is used as a parity oracle, not the source of truth — it is a strict subset of today's MEOS.
Single-inheritance tree. The base type is the missing template parameter; the geometry/geodetic distinction is a trait axis, not a parent (so there is no diamond):
The spatial subtree follows the authoritative MobilityDB manual
(Ch. 7 Figure 7.1): TGeo is the broad parent of every PostGIS-derived
type; TPoint is an API-level intermediate under TGeo (see
Manual reconciliation).
Temporal temporal_type (the late-bound superclass)
├─ TAlpha talpha_type {tbool, ttext}
│ ├─ TBool base BOOL
│ └─ TText base TEXT
├─ TNumber tnumber_type {tint, tfloat}
│ ├─ TInt base INT4
│ └─ TFloat base FLOAT8
└─ TSpatial tspatial_type
├─ TGeo tgeo_type_all (PostGIS-derived; manual)
│ ├─ TPoint tpoint_type {tgeompoint, tgeogpoint}
│ │ ├─ TGeomPoint base GEOMETRY ·geometryBased
│ │ └─ TGeogPoint base GEOGRAPHY ·geodetic
│ ├─ TGeometry base GEOMETRY ·geometryBased
│ └─ TGeography base GEOGRAPHY ·geodetic
├─ TCbuffer base CBUFFER (#if CBUFFER)
├─ TNpoint base NPOINT (#if NPOINT)
├─ TPose base POSE (#if POSE)
└─ TRGeometry base POSE (#if RGEO)
A concrete class is the product leaf × subtype — TFloatSeq,
TGeomPointInst, TRGeometrySeqSet. Methods of a node are inherited by
all descendants; objectModel.lattice carries the derived
children/ancestors/depth so consumers can expand the effective
method set per concrete class.
cbuffer, npoint, pose, rgeo are full leaf classes and in
scope — never deferred. trgeometry is the user-facing name; internal
functions keep the trgeo_ prefix and are not normalized.
The MobilityDB manual (Ch. 7, Temporal Geometry Types, Figure 7.1
"Hierarchy of spatiotemporal types", source doc/images/tspatial.svg) is
the authoritative conceptual model for the spatial subtree. The model
reconciles to it exactly, with one documented difference:
- The figure is partial — spatial-only; it omits the
Temporalroot and the wholeTAlpha/TNumbersubtree (OM-M6). This model is the complete superset. - The figure makes
TGeothe broad parent ofTGeometry,TGeography,TGeomPoint,TGeogPoint("TGeo and its subtypes … derived from the PostGIS types geometry and geography"). The model uses the broad C predicatetgeo_type_allforTGeoclass membership; the narrowtgeo_type()(and the point-rejectingtgeo_*functions) is the real irregularity, sharpened inOM-M1. - The figure draws no
TPointnode, but the C API hastpoint_type()and a 25-functiontpoint_*family that must bind to a class. The model insertsTPointas an API-level abstract underTGeo— the single, documented addition (OM-M6). tpcpoint/tpcpatch(temporal point-cloud point/patch) are absent from both master MEOS and Figure 7.1 (OM-M7); they are out of the drift-gated source of truth and derived automatically once MEOS defines them — never fabricated.- Class names use the manual spelling (
TGeo,TNpoint,TCbuffer,TPose,TRGeometry); C prefixes (tnpoint_,tcbuffer_,trgeo_) are unchanged.
tests/test_object_model.py::ModelFileTests::test_matches_manual_figure_7_1
gates this: the model's spatial node set must equal the figure's nodes
plus TPoint, with the figure's parent edges intact — so the
reconciliation cannot silently regress.
MEOS is a closed algebra: temporal operations return and consume spans,
sets and boxes (tnumber_to_span → a Span, temporal_time → a
TsTzSpanSet, tnumber_to_tbox → TBox). The methods cannot be typed
without these, so objectModel.companions carries two parallel
hierarchies — Box (TBox, STBox) and Collection
(Set/Span/SpanSet with the concrete int/bigint/float/text/date/
tstz/geo/… leaves) — and objectModel.algebra records which companion a
temporal family yields.
objectModel.functionToClass maps every catalog function to the class it
is a method of, by longest-prefix match (so tgeompoint_* beats
tgeo_*, tsequenceset_* beats tsequence_*, and tfloatinst_*
resolves to the concrete TFloatInst). The assignment reuses the
function itself as the backing symbol — equivalence by construction, no
C-symbol guessing. A function with no prefix match (operator overloads,
datum_*/geo_* base helpers, plumbing) is recorded honestly with
class: null and a reason — never force-fitted.
For 4 of the 6 temporal-type families the per-member argument→backing
routing is mechanically derivable from the <member>_<type>_<arg> C-name
token model, so faithful codegen needs nothing more than
functionToClass. The geo (TGeomPoint/TGeogPoint) and
temporal (TFloat/TInt/TBool/TText) families encode editorial
dispatch decisions that are absent from the C signatures (e.g. a Python
Point vs BaseGeometry split routing to different backings; scalar
arguments passed by value with a per-member cast; IntSet→FloatSet
coercion via the superclass). objectModel.dispatch makes that routing a
catalog fact, transcribed verbatim from the PyMEOS cross-repo handoff
RFC #94 §3 (the source of truth — extracted from PyMEOS's working
hand-written oracle; never re-derived), so every binding's faithful
generator emits geo/temporal with equivalence by construction instead of
per-binding editorial guesses.
dispatch.geo is single-block (dispatch.geo.<member>; TGeomPoint
vs TGeogPoint is disambiguated at runtime by geodeticFromSelf).
dispatch.temporal is per concrete type —
dispatch.temporal.{tfloat,tint,tbool,ttext}.<member> — fully resolved
(no <t>/<base> placeholders), because the editorial routing differs
per type (e.g. tint coerces Float→Int, the opposite of tfloat;
tint.temporal_equal takes the value uncast while tfloat casts;
tbool exposes only temporal_equal/not_equal/at/minus).
Each member has an ordered dispatch table (py type token → fn
backing; optional argTransform/extraArgs/coerce+via/
geodeticFromSelf; a py:"scalar" entry carries scalarType, the exact
isinstance test, e.g. "float", "int|float", "bool", "str"),
plus fallback and result. The py token may be "scalar",
"self", a class name, or "list[str]"
(isinstance(o, list) and isinstance(o[0], str)). The tables are
transcribed verbatim from the hand-written oracle (RFC #94 §3 + the
complete extended §7) — never derived.
argTransform is a closed, named vocabulary — each binding maps every
name to its own idiom; the set is finite because the editorial decisions
are finite:
| Name | Meaning (PyMEOS idiom shown) |
|---|---|
geoToGserialized |
shapely geometry → GSERIALIZED (geo_to_gserialized($o, <geodetic>)) |
stboxToGeo |
STBox → geometry (stbox_to_geo($o._inner)) |
scalarCast |
scalar cast to the block's concrete base (float($o) for tfloat, int($o) for tint) |
scalarValue |
scalar passed by value as-is ($o) |
textsetMake |
list[str] → text set (textset_make($o)) |
innerPtr |
pass the wrapped C pointer ($o._inner) |
geodeticFromSelf |
the only runtime-self primitive (PyMEOS → isinstance(self, TGeogPoint)) |
coerce+via:super |
Python-side type coercion then delegate to the superclass method |
MEOS has a single raise mechanism:
meos_error(int errlevel, int errcode, const char *fmt, ...), where
errcode is an errorCode enum value. objectModel.errors.codes carries
the full taxonomy (verbatim, drift-gated against meos.h).
objectModel.errors.raises is derived by a static scan of the MobilityDB
C sources: the literal meos_error codes in each function body, plus one
indirection level through the ensure_* argument guards (tagged
via: "direct" | "ensure"). If the sources are unavailable the scan is a
no-op and errors.status = "source-unavailable" — an honest signal,
never a fabricated empty set.
object_model_parity.py is the object-model analogue of
portable_parity.py. It parses the PyMEOS factory (the oracle, never
hard-coded) and writes output/meos-object-model-parity.json: every
structural divergence (classes/abstracts/collections MEOS defines that
PyMEOS lacks) as a worklist entry. A divergence already explained by a
curated corrections item is known; an unexplained one is
needs-correction. tests/test_object_model_parity.py gates
0 needs-correction (every divergence has a stated correction) and
that nothing is silently dropped — the analogue of the portable
0-unbacked gate. If the oracle is absent the audit degrades to
oracle-unavailable (curated corrections still carried, no fabricated
verdict).
Making the implicit model explicit surfaces irregularities in both
MEOS and PyMEOS (a decade of manual evolution). They are carried verbatim
in objectModel.corrections as a durable, reviewable worklist
(OM-M* = MEOS-side, OM-P* = PyMEOS-side), e.g.:
- OM-M1 the class
TGeois broad (manual =tgeo_type_all) but the narrow Ctgeo_type()and mosttgeo_*functions reject points — API applicability is narrower than class membership. - OM-M2
tgeometry_type()means geometry-based (non-geodetic), not is the TGeometry type — a misnomer paired withtgeodetic_type(). - OM-M3
TRGeometry's base type isT_POSE(base ≠ name). - OM-M4
talpha_typeis a real grouping with no user-facing class. - OM-M6 the manual Figure 7.1 is partial (spatial-only) and draws no
TPoint; the model is the superset and addsTPointunderTGeo. - OM-M7
tpcpoint/tpcpatchare planned but absent from master MEOS and the figure — out of the drift-gated SoT until MEOS adds them. - OM-P1/P6/P7 PyMEOS lacks the
TGeometry/TGeography/TCbuffer/ TNpoint/TPose/TRGeometryleaves, the full Collection hierarchy, and theTSpatial/TGeoabstract intermediates that MEOS defines.
Reporting only — the fixes land as separate PRs in those repos by their own sessions.
The curated lattice cannot silently drift from MEOS:
tests/test_object_model.py::DriftGate re-derives every membership set
from the MobilityDB sources (the predicate bodies, MEOS_TEMPTYPE_CATALOG,
the tempSubtype and errorCode enums) and asserts the curated meta
matches. (Public model excludes the internal T_TDOUBLE{2,3,4}
aggregation types.) Run python setup.py to fetch the sources, then
python3 tests/test_object_model.py.
Discussion MobilityDB#861 (edge-to-cloud portability). Source of truth:
MobilityDB meos/src/temporal/meos_catalog.c (predicates +
MEOS_TEMPTYPE_CATALOG) and meos/include/meos.h (tempSubtype,
errorCode). Oracle: PyMEOS pymeos/factory.py.