Expand description
Reader for the COPC Temporal Index Extension.
When a COPC file contains data from multiple survey passes over the same area, a spatial query alone returns points from every pass that touched that region. The temporal index extension adds per-node GPS time metadata so that clients can filter by time before decompressing any point data.
This crate reads the temporal index incrementally via ByteSource, matching
the streaming design of copc_streaming.
§Quick start
use copc_streaming::{Aabb, CopcStreamingReader, FileSource};
use copc_temporal::{GpsTime, TemporalCache};
let mut reader = CopcStreamingReader::open(
FileSource::open("survey.copc.laz")?,
).await?;
let mut temporal = match TemporalCache::from_reader(&reader).await? {
Some(t) => t,
None => return Ok(()), // no temporal index in this file
};
let start = GpsTime(1_000_000.0);
let end = GpsTime(1_000_010.0);
// One call: loads hierarchy + temporal pages, fetches chunks,
// filters by both bounds and time.
let points = temporal.query_points(
&mut reader, &my_query_box, start, end,
).await?;§Fast path: select only the fields you need
TemporalCache::query_points decodes every field and materializes
las::Point values. For hot paths,
TemporalCache::query_chunks returns (Chunk, candidate_range)
pairs with a caller-chosen Fields mask and
lets you walk columns directly. candidate_range is the sub-range of
point indices within each chunk whose GPS times could possibly match
[start, end] based on the temporal index stride samples — use it to
skip points that are guaranteed not to match.
use copc_streaming::Fields;
use copc_temporal::indices_in_time_range;
let chunks = temporal
.query_chunks(
&mut reader,
&my_query_box,
start,
end,
Fields::Z | Fields::GPS_TIME,
)
.await?;
for (chunk, range) in &chunks {
// Narrow to the candidate range first, then filter exactly.
let precise: Vec<u32> = indices_in_time_range(chunk, start, end)
.unwrap()
.into_iter()
.filter(|&i| (range.start..range.end).contains(&i))
.collect();
// ... walk chunk.positions() / chunk.gps_time() at these indices
}§Low-level access
For full control over page loading, use the building blocks directly:
// Load only temporal pages that overlap the time range.
temporal.load_pages_for_time_range(reader.source(), start, end).await?;
// Find matching nodes, estimate point ranges, fetch chunks yourself.
for entry in temporal.nodes_in_range(start, end) {
let range = entry.estimate_point_range(
start, end, temporal.stride(), hier.point_count,
);
// ...
}§How it works
TemporalCache::from_reader loads the header and root page.
TemporalCache::query_chunks / TemporalCache::query_points then
load the relevant hierarchy and temporal pages, fetch matching chunks,
and return them (with candidate ranges) or the points inside both the
bounding box and time window.
For time-only queries (no spatial filter), use
TemporalCache::query_chunks_by_time /
TemporalCache::query_points_by_time.
For advanced use cases you can call TemporalCache::load_pages_for_time_range
and TemporalCache::nodes_in_range separately, or
TemporalCache::load_all_pages to fetch the entire index at once.
Structs§
- Aabb
- Axis-aligned bounding box.
- GpsTime
- Newtype over f64 GPS time. Implements Copy, PartialEq, PartialOrd.
- Node
Temporal Entry - Per-node temporal data: a set of sampled GPS timestamps.
- Temporal
Cache - Incrementally-loaded temporal index cache.
- Temporal
Header - Header of the temporal index EVLR (32 bytes).
- Voxel
Key - Octree node key: (level, x, y, z).
Enums§
- Temporal
Error - Errors that can occur when reading the temporal index.
Traits§
- Byte
Source - Async random-access byte source.
Functions§
- filter_
points_ by_ time - Filter points to only those whose GPS time falls within
[start, end]. - indices_
in_ time_ range - Indices of points whose GPS time falls within
[start, end].