1use std::collections::HashMap;
7use std::io::Cursor;
8
9use byteorder::{LittleEndian, ReadBytesExt};
10use copc_streaming::{ByteSource, Chunk, CopcStreamingReader, Fields, VoxelKey};
11
12use crate::TemporalError;
13use crate::gps_time::GpsTime;
14use crate::temporal_index::NodeTemporalEntry;
15
16#[derive(Debug, Clone)]
18pub struct TemporalHeader {
19 pub version: u32,
21 pub stride: u32,
23 pub node_count: u32,
25 pub page_count: u32,
27 pub root_page_offset: u64,
29 pub root_page_size: u32,
31}
32
33#[derive(Debug, Clone)]
34struct PendingPage {
35 offset: u64,
36 size: u32,
37 subtree_time_min: f64,
38 subtree_time_max: f64,
39}
40
41pub struct TemporalCache {
43 header: Option<TemporalHeader>,
44 entries: HashMap<VoxelKey, NodeTemporalEntry>,
45 pending_pages: Vec<PendingPage>,
46 stride: u32,
47}
48
49impl Default for TemporalCache {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl TemporalCache {
56 pub fn new() -> Self {
58 Self {
59 header: None,
60 entries: HashMap::new(),
61 pending_pages: Vec::new(),
62 stride: 0,
63 }
64 }
65
66 pub async fn from_reader<S: ByteSource>(
71 reader: &CopcStreamingReader<S>,
72 ) -> Result<Option<Self>, TemporalError> {
73 let mut cache = Self::new();
74 let found = cache
75 .load_header(reader.source(), reader.evlr_offset(), reader.evlr_count())
76 .await?;
77 if !found {
78 return Ok(None);
79 }
80 cache.load_root_page(reader.source()).await?;
81 Ok(Some(cache))
82 }
83
84 pub async fn load_header(
87 &mut self,
88 source: &impl ByteSource,
89 evlr_offset: u64,
90 evlr_count: u32,
91 ) -> Result<bool, TemporalError> {
92 let mut pos = evlr_offset;
93
94 for _ in 0..evlr_count {
95 let hdr_data = source.read_range(pos, 60).await?;
96 let mut r = Cursor::new(hdr_data.as_slice());
97
98 r.set_position(2);
100 let mut user_id = [0u8; 16];
101 std::io::Read::read_exact(&mut r, &mut user_id)?;
102 let record_id = r.read_u16::<LittleEndian>()?;
103 let data_length = r.read_u64::<LittleEndian>()?;
104
105 let data_start = pos + 60;
106
107 let uid_end = user_id.iter().position(|&b| b == 0).unwrap_or(16);
108 let uid_str = std::str::from_utf8(&user_id[..uid_end]).unwrap_or("");
109
110 if uid_str == "copc_temporal" && record_id == 1000 {
111 let header_data = source.read_range(data_start, 32).await?;
112 let header = parse_temporal_header(&header_data)?;
113 self.stride = header.stride;
114 self.header = Some(header);
115 return Ok(true);
116 }
117
118 pos = data_start + data_length;
119 }
120
121 Ok(false)
122 }
123
124 pub async fn load_root_page(&mut self, source: &impl ByteSource) -> Result<(), TemporalError> {
126 let header = self.header.as_ref().ok_or(TemporalError::TruncatedHeader)?;
127
128 let data = source
129 .read_range(header.root_page_offset, header.root_page_size as u64)
130 .await?;
131 self.parse_page(&data)?;
132 Ok(())
133 }
134
135 pub async fn load_pages_for_time_range(
137 &mut self,
138 source: &impl ByteSource,
139 start: GpsTime,
140 end: GpsTime,
141 ) -> Result<(), TemporalError> {
142 loop {
143 let matching: Vec<PendingPage> = self
144 .pending_pages
145 .iter()
146 .filter(|p| p.subtree_time_max >= start.0 && p.subtree_time_min <= end.0)
147 .cloned()
148 .collect();
149
150 if matching.is_empty() {
151 break;
152 }
153
154 self.pending_pages
155 .retain(|p| !(p.subtree_time_max >= start.0 && p.subtree_time_min <= end.0));
156
157 let ranges: Vec<_> = matching.iter().map(|p| (p.offset, p.size as u64)).collect();
158 let results = source.read_ranges(&ranges).await?;
159
160 for data in &results {
161 self.parse_page(data)?;
162 }
163 }
164
165 Ok(())
166 }
167
168 pub async fn load_all_pages(&mut self, source: &impl ByteSource) -> Result<(), TemporalError> {
170 while !self.pending_pages.is_empty() {
171 let pages: Vec<PendingPage> = self.pending_pages.drain(..).collect();
172 let ranges: Vec<_> = pages.iter().map(|p| (p.offset, p.size as u64)).collect();
173 let results = source.read_ranges(&ranges).await?;
174
175 for data in &results {
176 self.parse_page(data)?;
177 }
178 }
179 Ok(())
180 }
181
182 pub async fn query(
188 &mut self,
189 source: &impl ByteSource,
190 start: GpsTime,
191 end: GpsTime,
192 ) -> Result<Vec<&NodeTemporalEntry>, TemporalError> {
193 self.load_pages_for_time_range(source, start, end).await?;
194 Ok(self.nodes_in_range(start, end))
195 }
196
197 pub fn get(&self, key: &VoxelKey) -> Option<&NodeTemporalEntry> {
199 self.entries.get(key)
200 }
201
202 pub fn nodes_in_range(&self, start: GpsTime, end: GpsTime) -> Vec<&NodeTemporalEntry> {
204 self.entries
205 .values()
206 .filter(|e| e.overlaps(start, end))
207 .collect()
208 }
209
210 pub fn stride(&self) -> u32 {
212 self.stride
213 }
214
215 pub fn header(&self) -> Option<&TemporalHeader> {
217 self.header.as_ref()
218 }
219
220 pub fn len(&self) -> usize {
222 self.entries.len()
223 }
224
225 pub fn is_empty(&self) -> bool {
227 self.entries.is_empty()
228 }
229
230 pub fn iter(&self) -> impl Iterator<Item = (&VoxelKey, &NodeTemporalEntry)> {
232 self.entries.iter()
233 }
234
235 pub async fn query_chunks<S: ByteSource>(
283 &mut self,
284 reader: &mut CopcStreamingReader<S>,
285 bounds: &copc_streaming::Aabb,
286 start: GpsTime,
287 end: GpsTime,
288 fields: Fields,
289 ) -> Result<Vec<(Chunk, std::ops::Range<u32>)>, TemporalError> {
290 reader
291 .load_hierarchy_for_bounds(bounds)
292 .await
293 .map_err(TemporalError::Copc)?;
294
295 self.load_pages_for_time_range(reader.source(), start, end)
296 .await?;
297
298 let root_bounds = reader.copc_info().root_bounds();
299 let stride = self.stride;
300
301 let matches: Vec<_> = self
302 .nodes_in_range(start, end)
303 .into_iter()
304 .filter(|e| e.key.bounds(&root_bounds).intersects(bounds))
305 .filter_map(|e| {
306 let hier = reader.get(&e.key)?;
307 let range = e.estimate_point_range(start, end, stride, hier.point_count);
308 if range.is_empty() {
309 return None;
310 }
311 Some((e.key, range))
312 })
313 .collect();
314
315 let keys: Vec<copc_streaming::VoxelKey> = matches.iter().map(|(k, _)| *k).collect();
316 let chunks = reader
317 .fetch_chunks(&keys, fields)
318 .await
319 .map_err(TemporalError::Copc)?;
320 let out: Vec<_> = chunks
321 .into_iter()
322 .zip(matches.into_iter().map(|(_, range)| range))
323 .collect();
324 Ok(out)
325 }
326
327 pub async fn query_chunks_by_time<S: ByteSource>(
334 &mut self,
335 reader: &mut CopcStreamingReader<S>,
336 start: GpsTime,
337 end: GpsTime,
338 fields: Fields,
339 ) -> Result<Vec<(Chunk, std::ops::Range<u32>)>, TemporalError> {
340 reader
341 .load_all_hierarchy()
342 .await
343 .map_err(TemporalError::Copc)?;
344
345 self.load_pages_for_time_range(reader.source(), start, end)
346 .await?;
347
348 let stride = self.stride;
349
350 let matches: Vec<_> = self
351 .nodes_in_range(start, end)
352 .into_iter()
353 .filter_map(|e| {
354 let hier = reader.get(&e.key)?;
355 let range = e.estimate_point_range(start, end, stride, hier.point_count);
356 if range.is_empty() {
357 return None;
358 }
359 Some((e.key, range))
360 })
361 .collect();
362
363 let keys: Vec<copc_streaming::VoxelKey> = matches.iter().map(|(k, _)| *k).collect();
364 let chunks = reader
365 .fetch_chunks(&keys, fields)
366 .await
367 .map_err(TemporalError::Copc)?;
368 let out: Vec<_> = chunks
369 .into_iter()
370 .zip(matches.into_iter().map(|(_, range)| range))
371 .collect();
372 Ok(out)
373 }
374
375 pub async fn query_points<S: ByteSource>(
396 &mut self,
397 reader: &mut CopcStreamingReader<S>,
398 bounds: &copc_streaming::Aabb,
399 start: GpsTime,
400 end: GpsTime,
401 ) -> Result<Vec<las::Point>, TemporalError> {
402 let chunks_with_ranges = self
403 .query_chunks(reader, bounds, start, end, Fields::ALL)
404 .await?;
405
406 let mut all_points = Vec::new();
407 for (chunk, range) in chunks_with_ranges {
408 let start_idx = range.start as usize;
412 let end_idx = (range.end as usize).min(chunk.point_count());
413 let Some(times) = chunk.cloud().gps_time().map(|it| it.collect::<Vec<f64>>()) else {
414 continue;
416 };
417 let xs: Vec<f64> = chunk.cloud().x().collect();
418 let ys: Vec<f64> = chunk.cloud().y().collect();
419 let zs: Vec<f64> = chunk.cloud().z().collect();
420 let mut matching = Vec::with_capacity(end_idx - start_idx);
421 for i in start_idx..end_idx {
422 let t = times[i];
423 if t < start.0 || t > end.0 {
424 continue;
425 }
426 let (x, y, z) = (xs[i], ys[i], zs[i]);
427 if x < bounds.min[0]
428 || x > bounds.max[0]
429 || y < bounds.min[1]
430 || y > bounds.max[1]
431 || z < bounds.min[2]
432 || z > bounds.max[2]
433 {
434 continue;
435 }
436 matching.push(i as u32);
437 }
438 all_points.extend(chunk.points_at(&matching)?);
439 }
440 Ok(all_points)
441 }
442
443 pub async fn query_points_by_time<S: ByteSource>(
451 &mut self,
452 reader: &mut CopcStreamingReader<S>,
453 start: GpsTime,
454 end: GpsTime,
455 ) -> Result<Vec<las::Point>, TemporalError> {
456 let chunks_with_ranges = self
457 .query_chunks_by_time(reader, start, end, Fields::ALL)
458 .await?;
459
460 let mut all_points = Vec::new();
461 for (chunk, range) in chunks_with_ranges {
462 let start_idx = range.start as usize;
463 let end_idx = (range.end as usize).min(chunk.point_count());
464 let Some(times) = chunk.cloud().gps_time().map(|it| it.collect::<Vec<f64>>()) else {
465 continue;
467 };
468 let mut matching = Vec::with_capacity(end_idx - start_idx);
469 for (offset, &t) in times[start_idx..end_idx].iter().enumerate() {
470 if t >= start.0 && t <= end.0 {
471 matching.push((start_idx + offset) as u32);
472 }
473 }
474 all_points.extend(chunk.points_at(&matching)?);
475 }
476 Ok(all_points)
477 }
478
479 fn parse_page(&mut self, data: &[u8]) -> Result<(), TemporalError> {
480 let mut r = Cursor::new(data);
481
482 while (r.position() as usize) < data.len() {
483 if r.position() as usize + 20 > data.len() {
484 break;
485 }
486
487 let level = r.read_i32::<LittleEndian>()?;
488 let x = r.read_i32::<LittleEndian>()?;
489 let y = r.read_i32::<LittleEndian>()?;
490 let z = r.read_i32::<LittleEndian>()?;
491 let sample_count = r.read_u32::<LittleEndian>()?;
492
493 let key = VoxelKey { level, x, y, z };
494
495 if sample_count == 0 {
496 let child_offset = r.read_u64::<LittleEndian>()?;
498 let child_size = r.read_u32::<LittleEndian>()?;
499 let time_min = r.read_f64::<LittleEndian>()?;
500 let time_max = r.read_f64::<LittleEndian>()?;
501
502 self.pending_pages.push(PendingPage {
503 offset: child_offset,
504 size: child_size,
505 subtree_time_min: time_min,
506 subtree_time_max: time_max,
507 });
508 } else {
509 let mut samples = Vec::with_capacity(sample_count as usize);
510 for _ in 0..sample_count {
511 samples.push(GpsTime(r.read_f64::<LittleEndian>()?));
512 }
513
514 self.entries
515 .insert(key, NodeTemporalEntry::new(key, samples));
516 }
517 }
518
519 Ok(())
520 }
521}
522
523fn parse_temporal_header(data: &[u8]) -> Result<TemporalHeader, TemporalError> {
524 if data.len() < 32 {
525 return Err(TemporalError::TruncatedHeader);
526 }
527 let mut r = Cursor::new(data);
528 let version = r.read_u32::<LittleEndian>()?;
529 if version != 1 {
530 return Err(TemporalError::UnsupportedVersion(version));
531 }
532 let stride = r.read_u32::<LittleEndian>()?;
533 if stride < 1 {
534 return Err(TemporalError::InvalidStride(stride));
535 }
536 let node_count = r.read_u32::<LittleEndian>()?;
537 let page_count = r.read_u32::<LittleEndian>()?;
538 let root_page_offset = r.read_u64::<LittleEndian>()?;
539 let root_page_size = r.read_u32::<LittleEndian>()?;
540 let _reserved = r.read_u32::<LittleEndian>()?;
541
542 Ok(TemporalHeader {
543 version,
544 stride,
545 node_count,
546 page_count,
547 root_page_offset,
548 root_page_size,
549 })
550}