Skip to main content

copc_temporal/
lib.rs

1//! Reader for the [COPC Temporal Index Extension](https://github.com/360-geo/copc/blob/master/copc-temporal/docs/temporal-index-spec.md).
2//!
3//! When a COPC file contains data from multiple survey passes over the same area,
4//! a spatial query alone returns points from *every* pass that touched that region.
5//! The temporal index extension adds per-node GPS time metadata so that clients can
6//! filter by time **before** decompressing any point data.
7//!
8//! This crate reads the temporal index incrementally via [`ByteSource`], matching
9//! the streaming design of [`copc_streaming`].
10//!
11//! # Quick start
12//!
13//! ```rust,ignore
14//! use copc_streaming::{Aabb, CopcStreamingReader, FileSource};
15//! use copc_temporal::{GpsTime, TemporalCache};
16//!
17//! let mut reader = CopcStreamingReader::open(
18//!     FileSource::open("survey.copc.laz")?,
19//! ).await?;
20//!
21//! let mut temporal = match TemporalCache::from_reader(&reader).await? {
22//!     Some(t) => t,
23//!     None => return Ok(()), // no temporal index in this file
24//! };
25//!
26//! let start = GpsTime(1_000_000.0);
27//! let end   = GpsTime(1_000_010.0);
28//!
29//! // One call: loads hierarchy + temporal pages, fetches chunks,
30//! // filters by both bounds and time.
31//! let points = temporal.query_points(
32//!     &mut reader, &my_query_box, start, end,
33//! ).await?;
34//! ```
35//!
36//! # Low-level access
37//!
38//! For full control over page loading and chunk processing, use the building
39//! blocks directly:
40//!
41//! ```rust,ignore
42//! // Load only temporal pages that overlap the time range.
43//! temporal.load_pages_for_time_range(reader.source(), start, end).await?;
44//!
45//! // Find matching nodes, estimate point ranges, fetch chunks yourself.
46//! for entry in temporal.nodes_in_range(start, end) {
47//!     let range = entry.estimate_point_range(
48//!         start, end, temporal.stride(), hier.point_count,
49//!     );
50//!     // ...
51//! }
52//! ```
53//!
54//! # How it works
55//!
56//! [`TemporalCache::from_reader`] loads the header and root page.
57//! [`TemporalCache::query_points`] then loads the relevant hierarchy and
58//! temporal pages, fetches matching chunks, and returns only the points
59//! that fall inside both the bounding box and time window.
60//!
61//! For time-only queries (no spatial filter), use
62//! [`TemporalCache::query_points_by_time`].
63//!
64//! For advanced use cases you can call [`TemporalCache::load_pages_for_time_range`]
65//! and [`TemporalCache::nodes_in_range`] separately, or
66//! [`TemporalCache::load_all_pages`] to fetch the entire index at once.
67
68mod error;
69mod gps_time;
70mod temporal_cache;
71mod temporal_index;
72mod vlr;
73
74pub use error::TemporalError;
75pub use gps_time::GpsTime;
76pub use temporal_cache::{TemporalCache, TemporalHeader};
77pub use temporal_index::NodeTemporalEntry;
78
79// Re-export copc-streaming types that temporal consumers will need.
80pub use copc_streaming::{Aabb, ByteSource, VoxelKey};
81
82/// Filter points to only those whose GPS time falls within `[start, end]`.
83///
84/// Points without a GPS time are excluded. Use after
85/// [`CopcStreamingReader::read_points_range`](copc_streaming::CopcStreamingReader::read_points_range)
86/// to trim points at the edges of an estimated temporal range.
87pub fn filter_points_by_time(
88    points: Vec<las::Point>,
89    start: GpsTime,
90    end: GpsTime,
91) -> Vec<las::Point> {
92    points
93        .into_iter()
94        .filter(|p| p.gps_time.is_some_and(|t| t >= start.0 && t <= end.0))
95        .collect()
96}