1use std::io::Cursor;
7
8use byteorder::{LittleEndian, ReadBytesExt};
9use las::raw;
10use laz::LazVlr;
11
12use crate::error::CopcError;
13use crate::types::Aabb;
14
15pub struct CopcHeader {
20 pub(crate) las_header: las::Header,
21 pub(crate) copc_info: CopcInfo,
22 pub(crate) laz_vlr: LazVlr,
23 pub(crate) evlr_offset: u64,
24 pub(crate) evlr_count: u32,
25}
26
27impl CopcHeader {
28 pub fn las_header(&self) -> &las::Header {
30 &self.las_header
31 }
32
33 pub fn copc_info(&self) -> &CopcInfo {
35 &self.copc_info
36 }
37
38 pub fn laz_vlr(&self) -> &LazVlr {
40 &self.laz_vlr
41 }
42
43 pub fn evlr_offset(&self) -> u64 {
45 self.evlr_offset
46 }
47
48 pub fn evlr_count(&self) -> u32 {
50 self.evlr_count
51 }
52}
53
54#[derive(Debug, Clone)]
56pub struct CopcInfo {
57 pub center: [f64; 3],
58 pub halfsize: f64,
59 pub spacing: f64,
60 pub root_hier_offset: u64,
61 pub root_hier_size: u64,
62 pub gpstime_minimum: f64,
63 pub gpstime_maximum: f64,
64}
65
66impl CopcInfo {
67 pub fn root_bounds(&self) -> Aabb {
69 Aabb {
70 min: [
71 self.center[0] - self.halfsize,
72 self.center[1] - self.halfsize,
73 self.center[2] - self.halfsize,
74 ],
75 max: [
76 self.center[0] + self.halfsize,
77 self.center[1] + self.halfsize,
78 self.center[2] + self.halfsize,
79 ],
80 }
81 }
82
83 fn parse(data: &[u8]) -> Result<Self, CopcError> {
84 if data.len() < 160 {
85 return Err(CopcError::CopcInfoNotFound);
86 }
87 let mut r = Cursor::new(data);
88 let center_x = r.read_f64::<LittleEndian>()?;
89 let center_y = r.read_f64::<LittleEndian>()?;
90 let center_z = r.read_f64::<LittleEndian>()?;
91 let halfsize = r.read_f64::<LittleEndian>()?;
92 let spacing = r.read_f64::<LittleEndian>()?;
93 let root_hier_offset = r.read_u64::<LittleEndian>()?;
94 let root_hier_size = r.read_u64::<LittleEndian>()?;
95 let gpstime_minimum = r.read_f64::<LittleEndian>()?;
96 let gpstime_maximum = r.read_f64::<LittleEndian>()?;
97 Ok(CopcInfo {
98 center: [center_x, center_y, center_z],
99 halfsize,
100 spacing,
101 root_hier_offset,
102 root_hier_size,
103 gpstime_minimum,
104 gpstime_maximum,
105 })
106 }
107}
108
109pub(crate) fn parse_header(data: &[u8]) -> Result<CopcHeader, CopcError> {
113 let mut cursor = Cursor::new(data);
114
115 let raw_header = raw::Header::read_from(&mut cursor)?;
117
118 let evlr_offset = raw_header
119 .evlr
120 .as_ref()
121 .map_or(0, |e| e.start_of_first_evlr);
122 let evlr_count = raw_header.evlr.as_ref().map_or(0, |e| e.number_of_evlrs);
123 let number_of_vlrs = raw_header.number_of_variable_length_records;
124 let header_size = raw_header.header_size;
125
126 let mut builder = las::Builder::new(raw_header)?;
128
129 cursor.set_position(header_size as u64);
131
132 let mut copc_info = None;
133 let mut laz_vlr = None;
134
135 for _ in 0..number_of_vlrs {
136 let raw_vlr = raw::Vlr::read_from(&mut cursor, false)?;
137 let vlr = las::Vlr::new(raw_vlr);
138
139 match (vlr.user_id.as_str(), vlr.record_id) {
140 ("copc", 1) => {
141 copc_info = Some(CopcInfo::parse(&vlr.data)?);
142 }
143 ("laszip encoded", 22204) => {
144 laz_vlr = Some(LazVlr::read_from(vlr.data.as_slice())?);
145 }
146 _ => {}
147 }
148
149 builder.vlrs.push(vlr);
150 }
151
152 let las_header = builder.into_header()?;
153 let copc_info = copc_info.ok_or(CopcError::CopcInfoNotFound)?;
154 let laz_vlr = laz_vlr.ok_or(CopcError::LazVlrNotFound)?;
155
156 Ok(CopcHeader {
157 las_header,
158 copc_info,
159 laz_vlr,
160 evlr_offset,
161 evlr_count,
162 })
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_copc_info_root_bounds() {
171 let info = CopcInfo {
172 center: [100.0, 200.0, 10.0],
173 halfsize: 50.0,
174 spacing: 1.0,
175 root_hier_offset: 0,
176 root_hier_size: 0,
177 gpstime_minimum: 0.0,
178 gpstime_maximum: 0.0,
179 };
180 let b = info.root_bounds();
181 assert_eq!(b.min, [50.0, 150.0, -40.0]);
182 assert_eq!(b.max, [150.0, 250.0, 60.0]);
183 }
184}