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

    SELECT tempSubtype(tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz));
    -- Instant
    
  • Construct a temporal pcpatch of sequence subtype

    tpcpatchSeq(tpcpatch[]) → tpcpatch

    tpcpatchSeq(tpcpatch[],text) → tpcpatch

    SELECT numInstants(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)])), interp(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]));
    -- 2 | Step
    
  • Construct a temporal pcpatch of sequence-set subtype

    tpcpatchSeqSet(tpcpatch[]) → tpcpatch

    SELECT numSequences(tpcpatchSeqSet(ARRAY[tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)])]));
    -- 1
    

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.

Transformations

  • Transform a tpcpatch to another subtype

    tpcpatchInst(tpcpatch) → tpcpatch

    tpcpatchSeq(tpcpatch,interp='step') → tpcpatch

    tpcpatchSeqSet(tpcpatch) → tpcpatch

    SELECT tempSubtype(tpcpatchSeq(tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz)));
    -- Sequence
    
  • Transform a tpcpatch to another interpolation

    setInterp(tpcpatch,interp) → tpcpatch

    SELECT interp(setInterp(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)], 'discrete'), 'step'));
    -- Step
    

Restrictions

  • Restrict a tpcpatch to (the complement of) a pcpatch value or a set of pcpatch values

    atValue(tpcpatch,pcpatch) → tpcpatch

    minusValue(tpcpatch,pcpatch) → tpcpatch

    atValues(tpcpatch,pcpatchset) → tpcpatch

    minusValues(tpcpatch,pcpatchset) → tpcpatch

    -- The matching value is held up to the next instant; the closing bound
    -- is stored as a second instant.
    SELECT numInstants(atValue(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]),
      pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2))));
    -- 2
    SELECT numInstants(minusValue(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]),
      pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2))));
    -- 1
    
  • 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

    SELECT numInstants(atTpcbox(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]),
      tpcbox_xt(0, 0, 2, 2, tstzspan '[2001-01-01, 2001-01-03]', 1)));
    -- 1
    -- The dropped patch stays present on the open interval before its instant,
    -- so the complement keeps two instants.
    SELECT numInstants(minusTpcbox(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]),
      tpcbox_xt(0, 0, 2, 2, tstzspan '[2001-01-01, 2001-01-03]', 1)));
    -- 2
    
  • 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

    SELECT numInstants(atTpcboxFine(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]),
      tpcbox_xt(0, 0, 1, 1, tstzspan '[2001-01-01, 2001-01-03]', 1)));
    -- 1
    
  • 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

    -- The polygon boundary touches the point (3 3) of the patch at
    -- 2001-01-02, so that patch survives both the at and the minus side.
    SELECT numInstants(atGeometry(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]),
      geometry 'POLYGON((0 0, 0 3, 3 3, 3 0, 0 0))'));
    -- 2
    SELECT numInstants(minusGeometry(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]),
      geometry 'POLYGON((0 0, 0 3, 3 3, 3 0, 0 0))'));
    -- 1
    
  • Restrict a tpcpatch to the instants before or after a timestamp. The strict flag excludes the instant at the timestamp itself

    beforeTimestamp(tpcpatch,timestamptz,strict bool=TRUE) → tpcpatch

    afterTimestamp(tpcpatch,timestamptz,strict bool=TRUE) → tpcpatch

    SELECT timeSpan(beforeTimestamp(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 5, 5, 5)), '2001-01-03'::timestamptz)]), timestamptz '2001-01-02'));
    -- [2001-01-01, 2001-01-02)
    SELECT timeSpan(afterTimestamp(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 5, 5, 5)), '2001-01-03'::timestamptz)]), timestamptz '2001-01-02'));
    -- (2001-01-02, 2001-01-03]
    

Modifications

  • Insert a tpcpatch into another one, or update another one with it

    insert(tpcpatch,tpcpatch,connect bool=TRUE) → tpcpatch

    update(tpcpatch,tpcpatch,connect bool=TRUE) → tpcpatch

    SELECT numInstants(insert(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]), tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 5, 5, 5)), '2001-01-03'::timestamptz)])));
    -- 3
    
  • Delete a time selector (timestamp, set, period, or spanset) from a tpcpatch

    deleteTime(tpcpatch,timestamptz,connect bool=TRUE) → tpcpatch

    deleteTime(tpcpatch,tstzset,connect bool=TRUE) → tpcpatch

    deleteTime(tpcpatch,tstzspan,connect bool=TRUE) → tpcpatch

    deleteTime(tpcpatch,tstzspanset,connect bool=TRUE) → tpcpatch

    -- Deleting the middle instant splits the sequence into
    -- {[2001-01-01, 2001-01-02), (2001-01-02, 2001-01-03]}; each half stores
    -- its bound at the deleted timestamp, giving four instants.
    SELECT numInstants(deleteTime(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 5, 5, 5)), '2001-01-03'::timestamptz)]), timestamptz '2001-01-02'));
    -- 4
    
  • Append an instant or a sequence to a tpcpatch

    appendInstant(tpcpatch,tpcpatch) → tpcpatch

    appendSequence(tpcpatch,tpcpatch) → tpcpatch

    SELECT numInstants(appendInstant(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]),
      tpcpatch(pcpatch(1, pcpoint(1, 5, 5, 5)), '2001-01-03'::timestamptz)));
    -- 3
    

Splitting

  • Return the distinct values of a tpcpatch with the span set on which each of them is taken

    unnest(tpcpatch) → {(value,time)}

    SELECT (un).time
    FROM (SELECT unnest(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2024-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 5, 5, 5), pcpoint(1, 6, 6, 6)), '2024-01-02'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2024-01-03'::timestamptz)])) AS un) t;
    -- {[2024-01-02, 2024-01-03)}
    -- {[2024-01-01, 2024-01-02), [2024-01-03, 2024-01-03]}
    
  • Split a tpcpatch into fragments, one per time bin

    timeSplit(tpcpatch,bin_width interval,origin timestamptz='2000-01-03') → {(time,tpcpatch)}

    SELECT (ts).time, numInstants((ts).temp)
    FROM (SELECT timeSplit(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2024-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 5, 5, 5), pcpoint(1, 6, 6, 6)), '2024-01-02'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2024-01-03'::timestamptz)]),
      interval '1 day', '2024-01-01'::timestamptz) AS ts) t;
    -- 2024-01-01 | 2
    -- 2024-01-02 | 2
    -- 2024-01-03 | 1
    
  • Return the array of time spans of a tpcpatch's segments

    spans(tpcpatch) → tstzspan[]

    SELECT spans(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2024-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 5, 5, 5), pcpoint(1, 6, 6, 6)), '2024-01-02'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2024-01-03'::timestamptz)]));
    -- {"[2024-01-01, 2024-01-02]","[2024-01-02, 2024-01-03]"}
    
  • Return the time spans of a tpcpatch split into a given number of bins, or with a given number of segments per bin

    splitNSpans(tpcpatch, integer) → tstzspan[]

    splitEachNSpans(tpcpatch, integer) → tstzspan[]

    SELECT splitNSpans(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2024-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 5, 5, 5), pcpoint(1, 6, 6, 6)), '2024-01-02'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2024-01-03'::timestamptz)]), 2);
    -- {"[2024-01-01, 2024-01-02]","[2024-01-02, 2024-01-03]"}
    

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

    SELECT eIntersects(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]), geometry 'POINT(1 1)');
    -- t
    

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

Comparisons

  • Test whether the value at any (ever) or every (always) instant equals, or differs from, a pcpatch or another tpcpatch

    eEq(pcpatch,tpcpatch) → boolean

    eEq(tpcpatch,pcpatch) → boolean

    eEq(tpcpatch,tpcpatch) → boolean

    aEq, eNe and aNe take the same three argument pairs

    pcpatch ?= tpcpatch → boolean

    tpcpatch ?= pcpatch → boolean

    tpcpatch ?= tpcpatch → boolean

    The operators %=, ?<> and %<> take the same three operand pairs

    SELECT eEq(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]));
    -- t
    SELECT aEq(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]), pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)));
    -- f
    
  • Return the temporal equality or inequality of a tpcpatch with a pcpatch or with another tpcpatch, as a tbool

    tEq(pcpatch,tpcpatch) → tbool

    tEq(tpcpatch,pcpatch) → tbool

    tEq(tpcpatch,tpcpatch) → tbool

    tNe takes the same three argument pairs

    pcpatch #= tpcpatch → tbool

    tpcpatch #= pcpatch → tbool

    tpcpatch #= tpcpatch → tbool

    The operator #<> takes the same three operand pairs

    SELECT tEq(tpcpatchSeq(ARRAY[
      tpcpatch(pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)), '2001-01-01'::timestamptz),
      tpcpatch(pcpatch(1, pcpoint(1, 3, 3, 3), pcpoint(1, 4, 4, 4)), '2001-01-02'::timestamptz)]), pcpatch(1, pcpoint(1, 1, 1, 1), pcpoint(1, 2, 2, 2)));
    -- [t@2001-01-01, f@2001-01-02]
    

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.