Skip to main content

copc_streaming/
reader.rs

1//! High-level streaming COPC reader.
2
3use crate::byte_source::ByteSource;
4use crate::chunk::{self, DecompressedChunk};
5use crate::error::CopcError;
6use crate::header::{self, CopcHeader, CopcInfo};
7use crate::hierarchy::{HierarchyCache, HierarchyEntry};
8use crate::types::VoxelKey;
9
10/// Async streaming COPC reader.
11///
12/// `open()` reads the LAS header, VLRs, and root hierarchy page.
13/// Deeper hierarchy pages and point chunks are loaded on demand.
14pub struct CopcStreamingReader<S: ByteSource> {
15    source: S,
16    header: CopcHeader,
17    hierarchy: HierarchyCache,
18}
19
20impl<S: ByteSource> CopcStreamingReader<S> {
21    /// Open a COPC file.
22    pub async fn open(source: S) -> Result<Self, CopcError> {
23        let size = source.size().await?.unwrap_or(65536);
24        let read_size = size.min(65536);
25        let data = source.read_range(0, read_size).await?;
26        let header = header::parse_header(&data)?;
27
28        let mut hierarchy = HierarchyCache::new();
29        hierarchy.load_root(&source, &header.copc_info).await?;
30
31        Ok(Self {
32            source,
33            header,
34            hierarchy,
35        })
36    }
37
38    // --- Header accessors ---
39
40    /// The parsed COPC file header.
41    pub fn header(&self) -> &CopcHeader {
42        &self.header
43    }
44
45    /// Shortcut for `header().copc_info()`.
46    pub fn copc_info(&self) -> &CopcInfo {
47        &self.header.copc_info
48    }
49
50    /// File offset where EVLRs start.
51    pub fn evlr_offset(&self) -> u64 {
52        self.header.evlr_offset
53    }
54
55    /// Number of EVLRs in the file.
56    pub fn evlr_count(&self) -> u32 {
57        self.header.evlr_count
58    }
59
60    /// The underlying byte source.
61    pub fn source(&self) -> &S {
62        &self.source
63    }
64
65    // --- Hierarchy queries ---
66
67    /// Look up a hierarchy entry by voxel key.
68    pub fn get(&self, key: &VoxelKey) -> Option<&HierarchyEntry> {
69        self.hierarchy.get(key)
70    }
71
72    /// Iterate all loaded hierarchy entries.
73    pub fn entries(&self) -> impl Iterator<Item = (&VoxelKey, &HierarchyEntry)> {
74        self.hierarchy.iter()
75    }
76
77    /// Return loaded child entries for a given node.
78    ///
79    /// Only returns children that are already in the hierarchy cache.
80    /// If deeper hierarchy pages haven't been loaded yet, this may
81    /// return fewer children than actually exist in the file.
82    pub fn children(&self, key: &VoxelKey) -> Vec<&HierarchyEntry> {
83        key.children()
84            .iter()
85            .filter_map(|child| self.hierarchy.get(child))
86            .collect()
87    }
88
89    /// Number of loaded hierarchy entries.
90    pub fn node_count(&self) -> usize {
91        self.hierarchy.len()
92    }
93
94    /// Whether there are hierarchy pages that haven't been loaded yet.
95    pub fn has_pending_pages(&self) -> bool {
96        self.hierarchy.has_pending_pages()
97    }
98
99    // --- Hierarchy loading ---
100
101    /// Load the next batch of pending hierarchy pages.
102    pub async fn load_pending_pages(&mut self) -> Result<(), CopcError> {
103        self.hierarchy.load_pending_pages(&self.source).await
104    }
105
106    /// Load all remaining hierarchy pages.
107    pub async fn load_all_hierarchy(&mut self) -> Result<(), CopcError> {
108        self.hierarchy
109            .load_all(&self.source, &self.header.copc_info)
110            .await
111    }
112
113    // --- Point data ---
114
115    /// Fetch and decompress a single point chunk.
116    pub async fn fetch_chunk(&self, key: &VoxelKey) -> Result<DecompressedChunk, CopcError> {
117        let entry = self
118            .hierarchy
119            .get(key)
120            .ok_or(CopcError::Io(std::io::Error::new(
121                std::io::ErrorKind::NotFound,
122                "node not in hierarchy",
123            )))?;
124        let point_record_length = self.header.las_header.point_format().len()
125            + self.header.las_header.point_format().extra_bytes;
126        chunk::fetch_and_decompress(
127            &self.source,
128            entry,
129            &self.header.laz_vlr,
130            point_record_length,
131        )
132        .await
133    }
134
135    /// Parse points from a decompressed chunk.
136    pub fn read_points(&self, chunk: &DecompressedChunk) -> Result<Vec<las::Point>, CopcError> {
137        chunk::read_points(chunk, &self.header.las_header)
138    }
139}