Temporal Type tpcpatch

A tpcpatch is the lifting of pcpatch. It mirrors tpcpoint in every regard — same subtypes, same accessors, same casts and operators — except that each instant carries an entire compressed batch of points instead of a single point. The bounding box is again a tpcbox, computed in O(1) per instant from the patch's embedded PCBOUNDS.

Constructors

  • Construct a temporal pcpatch of instant subtype

    tpcpatch(pcpatch,timestamptz) → tpcpatch

  • Construct a temporal pcpatch of sequence subtype

    tpcpatchSeq(tpcpatch[]) → tpcpatch

    tpcpatchSeq(tpcpatch[],text) → tpcpatch

  • Construct a temporal pcpatch of sequence-set subtype

    tpcpatchSeqSet(tpcpatch[]) → tpcpatch

Accessors

All the generic temporal accessors apply, plus the tpcpoint-style pcid and SRID. In addition:

  • Return the number of points in the patch carried by the first or last instant

    startNumPoints(tpcpatch) → integer

    endNumPoints(tpcpatch) → integer

    SELECT startNumPoints(tpcpatch(PC_Patch(PC_MakePoint(1, ARRAY[10.0, 20.0, 30.0])),
      '2024-02-01'::timestamptz));
    -- 1
    
  • Return the total number of points across every instant's patch. Read directly from each patch's header — no decompression.

    numPoints(tpcpatch) → bigint

    SELECT numPoints(tpcpatchSeq(ARRAY[
      tpcpatch(PC_Patch(ARRAY[PC_MakePoint(1, ARRAY[1.0, 1.0, 1.0]),
                              PC_MakePoint(1, ARRAY[2.0, 2.0, 2.0])]),
               '2024-01-01'::timestamptz),
      tpcpatch(PC_Patch(ARRAY[PC_MakePoint(1, ARRAY[3.0, 3.0, 3.0])]),
               '2024-01-02'::timestamptz)]));
    -- 3
    
  • Set-returning function: emits one row per (instant timestamp, point) by walking each instant's patch and decomposing it through pgPointCloud's in-memory C API. Useful for joining a tpcpatch column against per-point predicates that the bbox-level operators can't express. Cost is O(total points) — one per-instant decompression plus one row emission per point.

    points(tpcpatch) → setof (t timestamptz, point pcpoint)

    SELECT t, point FROM points(tpcpatch(
      PC_Patch(ARRAY[PC_MakePoint(1, ARRAY[1.0, 1.0, 1.0]),
                     PC_MakePoint(1, ARRAY[2.0, 2.0, 2.0])]),
      '2024-01-01'::timestamptz));
    -- ('2024-01-01', '01:0000000000000000000000F03F0000000000000000F03F00000000000000F03F'::pcpoint)
    -- ('2024-01-01', '01:0000000000000000000000004000000000000000400000000000000040'::pcpoint)
    

See the section called “Per-point operations: what is and is not available” for the limits of these accessors and what is and is not available at per-point granularity.

Conversions

  • Convert a temporal point cloud patch to the Arrow C Data Interface and back, returning the reconstructed value

    arrowRoundtrip(tpcpatch) → tpcpatch

    SELECT arrowRoundtrip(tpcpatch(PC_Patch(ARRAY[PC_MakePoint(1, ARRAY[1.0, 1.0,
      1.0]), PC_MakePoint(1, ARRAY[2.0, 2.0, 2.0])]), '2024-01-01'::timestamptz)) =
      tpcpatch(PC_Patch(ARRAY[PC_MakePoint(1, ARRAY[1.0, 1.0, 1.0]),
      PC_MakePoint(1, ARRAY[2.0, 2.0, 2.0])]), '2024-01-01'::timestamptz);
    -- t
    

Restrictions

  • Restrict a tpcpatch to the instants whose patch passes a coarse PCBOUNDS overlap test against the tpcbox, or remove them. Granularity is patch-level: each surviving instant keeps its pcpatch payload verbatim, with no per-point decompression. Because pgPointCloud's PCBOUNDS is 2D, the Z dimension of the box is ignored at this granularity. Returns NULL when the tpcbox's pcid does not match.

    atTpcbox(tpcpatch,tpcbox,border_inc bool=TRUE) → tpcpatch

    minusTpcbox(tpcpatch,tpcbox,border_inc bool=TRUE) → tpcpatch

  • Per-point variant of the previous: each surviving instant carries a freshly-built pcpatch holding only the points inside (or outside, for minus) the tpcbox in 2D — and 3D when the box has a Z dimension. Instants whose patch filters to zero points are dropped. Slower than the coarse variant because every patch is decompressed and rebuilt; use when you need point-level fidelity.

    atTpcboxFine(tpcpatch,tpcbox,border_inc bool=TRUE) → tpcpatch

    minusTpcboxFine(tpcpatch,tpcbox,border_inc bool=TRUE) → tpcpatch

  • Restrict a tpcpatch to the points whose XY projection intersects (or does not intersect, for minus) a 2D geometry. Z is ignored. SRID compatibility between the patch schema and the geometry must be ensured by the caller.

    atGeometry(tpcpatch,geometry) → tpcpatch

    minusGeometry(tpcpatch,geometry) → tpcpatch

Spatial relationships

  • Return true iff at least one point in any of the tpcpatch's instants intersects the geometry (XY only).

    eIntersects(tpcpatch,geometry) → boolean

Bounding-box operators

The same bbox operator surface as the section called “Bounding-box operators” applies to tpcpatch. The predicate evaluates against the value's tpcbox, computed in O(1) per instant from the patch's embedded PCBOUNDS.

  • Topological predicates: &&, @>, <@, ~=, -|-. See the section called “Topological Operators”.

  • Strict directional predicates on the X / Y / Z / time axes (<<, >>, <<|, |>>, <</, />>, <<#, #>>) and their "overlaps-or-X" variants (&<, &>, &<|, |&>, &</, /&>, &<#, #&>). See the section called “Position Operators”.

  • Nearest-approach distance (|=|) is KNN-orderable through the GiST opclass.

    nearestApproachDistance(tpcpatch,tpcbox) → float

    nearestApproachDistance(tpcpatch,tpcpatch) → float

B-tree ordering — what ORDER BY tpcpatch means

The tpcpatch_btree_ops opclass produces a total order over tpcpatch values, but the order is not spatial. The comparator on the underlying pcpatch is byte-wise (memcmp over the meaningful varlena bytes); the temporal-level comparator is the lexicographic combination of per-instant timestamps and per-instant pcpatch byte order. The observable consequences:

  • pcid is the primary discriminator. A patch with a smaller pcid sorts before one with a larger pcid, regardless of point coordinates, count, or compression. This makes ORDER BY on a mixed-pcid column group rows by schema first, which is sometimes useful as a coarse-grained "chunk by source schema" pass.

  • Within a single pcid, ordering follows the on-disk byte layout: compression scheme, then npoints, then the 2D PCBOUNDS (xmin, xmax, ymin, ymax), then the compressed data payload. This is well-defined and stable for a given pgPointCloud release, but it is not a geometric or spatial ordering — two patches whose bounding boxes overlap in 3D space can sort arbitrarily relative to each other if their compression scheme or point count differs.

  • Equality is exact-bytes equality over the meaningful bytes (the trailing zero-padding pgPointCloud's varlena reserves is excluded). Two patches built from the same point set in the same order, with the same compression, are equal; reordering the points or changing compression makes them unequal.

For spatial-meaningful ordering, use the GiST KNN operator |=| (see nearest-approach distance on tpcpatch); for time-meaningful ordering, project to startTimestamp or timespan and order on that.

Per-point operations: what is and is not available

Operations on tpcpatch values come at two granularities: patch-level and per-point. The split matters because pgPointCloud patches store their points in a compressed payload that the bbox layer cannot inspect.

  • Patch-level — supported. Each instant's patch is treated as opaque and is kept or dropped as a whole, using only the 4-double PCBOUNDS header (xmin / xmax / ymin / ymax) and the instant's timestamp. This is the granularity of atTpcbox / minusTpcbox and the bounding-box operators in the section called “Bounding-box operators”, and of the bbox-driven tpcbox aggregate. No payload decompression happens; queries are O(number of instants).

    Because PCBOUNDS is 2D, the Z dimension of any tpcbox argument is ignored at this granularity even when the underlying schema has a Z dimension.

  • Per-point inspection — supported. The points set-returning function emits one row per (instant timestamp, point) by decomposing each instant's patch through pgPointCloud's in-memory C API. Cost is O(total points) — one per-instant decompression plus one row emission per point.

  • Per-point filtering / construction — supported. atTpcboxFine / minusTpcboxFine, atGeometry / minusGeometry, and eIntersects(tpcpatch, geometry) walk every point of every instant in C, applying their predicate against the actual point coordinates rather than just the patch bounding box. Surviving instants carry a freshly-built patch holding only the points that passed; instants whose patches filter to zero points are dropped.

The recommended workflow when both granularities are available is: (a) prune at the patch level with atTpcbox or an SP-GiST/GiST index scan to drop entire instants whose PCBOUNDS doesn't overlap the area of interest, and (b) refine on the survivors with atTpcboxFine / atGeometry when point-level fidelity is needed.