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.
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.
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
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
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
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.
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.