copc_streaming/lib.rs
1//! Async streaming reader for [COPC](https://copc.io/) (Cloud-Optimized Point Cloud) files.
2//!
3//! COPC organises LAS/LAZ point cloud data in a spatial octree so that clients can
4//! fetch only the regions they need. This crate reads COPC files incrementally through
5//! the [`ByteSource`] trait — any random-access backend (local files, HTTP range
6//! requests, in-memory buffers) works out of the box.
7//!
8//! # Quick start
9//!
10//! ```rust,ignore
11//! use copc_streaming::{Aabb, CopcStreamingReader, FileSource};
12//!
13//! let mut reader = CopcStreamingReader::open(
14//! FileSource::open("points.copc.laz")?,
15//! ).await?;
16//!
17//! // One call: loads hierarchy, fetches chunks, filters points by bounds.
18//! let points = reader.query_points(&my_query_box).await?;
19//! // each `las::Point` has .x, .y, .z, .gps_time, .color, etc.
20//! ```
21//!
22//! # With LOD control
23//!
24//! ```rust,ignore
25//! // Load points with at most 0.5 m between samples.
26//! let level = reader.copc_info().level_for_resolution(0.5);
27//! let points = reader.query_points_to_level(&my_query_box, level).await?;
28//! ```
29//!
30//! # Fast path: select only the fields you need
31//!
32//! The [`query_points`](CopcStreamingReader::query_points) family decodes
33//! every field and materializes `las::Point` values. For hot paths,
34//! [`query_chunks`](CopcStreamingReader::query_chunks) returns [`Chunk`]s
35//! with zero-copy column access and lets you pick which LAZ layers to
36//! decode at all. Omitted layers skip arithmetic decoding entirely.
37//!
38//! ```rust,ignore
39//! use copc_streaming::{CopcStreamingReader, FileSource, Fields};
40//!
41//! let level = reader.copc_info().level_for_resolution(0.5);
42//! let chunks = reader
43//! .query_chunks_to_level(&my_query_box, level, Fields::Z | Fields::RGB)
44//! .await?;
45//!
46//! for chunk in &chunks {
47//! // `unwrap` is safe — we asked for Z and RGB so the chunk has them.
48//! let indices = chunk.indices_in_bounds(&my_query_box).unwrap();
49//! let rgb = chunk.rgb().unwrap();
50//! let positions = chunk.positions().unwrap();
51//! for (pos, rgb) in positions.zip(rgb) {
52//! // ...
53//! }
54//! }
55//! ```
56//!
57//! # Low-level access
58//!
59//! For full control over hierarchy loading and fetch parallelism, combine
60//! [`load_hierarchy_for_bounds_to_level`](CopcStreamingReader::load_hierarchy_for_bounds_to_level),
61//! [`visible_keys`](CopcStreamingReader::visible_keys), and
62//! [`fetch_chunk`](CopcStreamingReader::fetch_chunk):
63//!
64//! ```rust,ignore
65//! reader.load_hierarchy_for_bounds_to_level(&my_query_box, level).await?;
66//! for key in reader.visible_keys(&my_query_box, Some(level)) {
67//! let chunk = reader.fetch_chunk(&key, Fields::Z | Fields::GPS_TIME).await?;
68//! let times = chunk.gps_time().unwrap();
69//! // ... drive your own parallelism / cancellation / prioritization
70//! }
71//! ```
72//!
73//! # Hierarchy loading
74//!
75//! The COPC hierarchy is stored as a tree of *pages*. Each page contains metadata
76//! for a group of octree nodes (typically several levels deep) plus pointers to
77//! child pages covering deeper subtrees.
78//!
79//! [`CopcStreamingReader::open`] reads the LAS header, COPC info, and the **root
80//! hierarchy page**. This gives you the coarse octree nodes immediately (often
81//! levels 0–3, depending on the file). Any subtrees stored in separate pages
82//! are tracked as *pending pages* — they haven't been fetched yet.
83//!
84//! You then control when and which deeper pages are loaded:
85//!
86//! - [`load_hierarchy_for_bounds`](CopcStreamingReader::load_hierarchy_for_bounds) —
87//! load only pages whose subtree intersects a bounding box. Call this when the
88//! camera moves or a spatial query arrives.
89//! - [`load_hierarchy_for_bounds_to_level`](CopcStreamingReader::load_hierarchy_for_bounds_to_level) —
90//! same, but stops at a maximum octree level. Use with
91//! [`CopcInfo::level_for_resolution`] for LOD control.
92//! - [`load_pending_pages`](CopcStreamingReader::load_pending_pages) — fetch the
93//! next batch of pending pages (all of them). Useful when you don't need spatial
94//! filtering and just want to go one level deeper.
95//! - [`load_all_hierarchy`](CopcStreamingReader::load_all_hierarchy) — convenience
96//! to pull every remaining page in one go.
97//! - [`children`](CopcStreamingReader::children) — list loaded children of a node.
98//! Returns only children already in the cache; if deeper pages haven't been
99//! loaded yet this may return fewer than exist in the file.
100//! - [`has_pending_pages`](CopcStreamingReader::has_pending_pages) — check if there
101//! are still unloaded pages.
102//!
103//! # Custom byte sources
104//!
105//! Implement [`ByteSource`] to read from any backend. The trait requires only
106//! `read_range(offset, length)` and `size()`. A default `read_ranges` implementation
107//! issues sequential reads — override it for backends that support parallel fetches
108//! (e.g. HTTP/2 multiplexing).
109//!
110//! Built-in implementations: [`FileSource`] (local files), `Vec<u8>` and `&[u8]`
111//! (in-memory).
112//!
113//! Futures returned by `ByteSource` are *not* required to be `Send`, so the crate
114//! works in single-threaded runtimes and WASM environments.
115
116mod byte_source;
117mod chunk;
118mod error;
119mod fields;
120mod file_source;
121mod header;
122mod hierarchy;
123mod reader;
124mod types;
125
126pub use byte_source::ByteSource;
127pub use chunk::Chunk;
128pub use error::CopcError;
129pub use fields::Fields;
130pub use file_source::FileSource;
131pub use header::{CopcHeader, CopcInfo};
132pub use hierarchy::{HierarchyCache, HierarchyEntry};
133pub use reader::CopcStreamingReader;
134pub use types::{Aabb, VoxelKey};
135
136// Re-exports of the handful of `las` types that appear in this crate's
137// public API. Users who only consume the types we return don't need to
138// add `las` as a direct dependency.
139pub use las::{Point, PointData};