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)]
56#[non_exhaustive]
57pub struct CopcInfo {
58 pub center: [f64; 3],
60 pub halfsize: f64,
62 pub spacing: f64,
64 pub root_hier_offset: u64,
66 pub root_hier_size: u64,
68 pub gpstime_minimum: f64,
70 pub gpstime_maximum: f64,
72}
73
74impl CopcInfo {
75 pub fn root_bounds(&self) -> Aabb {
77 Aabb {
78 min: [
79 self.center[0] - self.halfsize,
80 self.center[1] - self.halfsize,
81 self.center[2] - self.halfsize,
82 ],
83 max: [
84 self.center[0] + self.halfsize,
85 self.center[1] + self.halfsize,
86 self.center[2] + self.halfsize,
87 ],
88 }
89 }
90
91 pub fn level_for_resolution(&self, resolution: f64) -> i32 {
106 if resolution <= 0.0 || self.spacing <= 0.0 {
107 return 0;
108 }
109 (self.spacing / resolution).log2().ceil().max(0.0) as i32
110 }
111
112 fn parse(data: &[u8]) -> Result<Self, CopcError> {
113 if data.len() < 160 {
114 return Err(CopcError::CopcInfoNotFound);
115 }
116 let mut r = Cursor::new(data);
117 let center_x = r.read_f64::<LittleEndian>()?;
118 let center_y = r.read_f64::<LittleEndian>()?;
119 let center_z = r.read_f64::<LittleEndian>()?;
120 let halfsize = r.read_f64::<LittleEndian>()?;
121 let spacing = r.read_f64::<LittleEndian>()?;
122 let root_hier_offset = r.read_u64::<LittleEndian>()?;
123 let root_hier_size = r.read_u64::<LittleEndian>()?;
124 let gpstime_minimum = r.read_f64::<LittleEndian>()?;
125 let gpstime_maximum = r.read_f64::<LittleEndian>()?;
126 Ok(CopcInfo {
127 center: [center_x, center_y, center_z],
128 halfsize,
129 spacing,
130 root_hier_offset,
131 root_hier_size,
132 gpstime_minimum,
133 gpstime_maximum,
134 })
135 }
136}
137
138pub(crate) fn parse_header(data: &[u8]) -> Result<CopcHeader, CopcError> {
142 let mut cursor = Cursor::new(data);
143
144 let raw_header = raw::Header::read_from(&mut cursor)?;
146
147 let evlr_offset = raw_header
148 .evlr
149 .as_ref()
150 .map_or(0, |e| e.start_of_first_evlr);
151 let evlr_count = raw_header.evlr.as_ref().map_or(0, |e| e.number_of_evlrs);
152 let number_of_vlrs = raw_header.number_of_variable_length_records;
153 let header_size = raw_header.header_size;
154
155 let mut builder = las::Builder::new(raw_header)?;
157
158 cursor.set_position(header_size as u64);
160
161 let mut copc_info = None;
162 let mut laz_vlr = None;
163
164 for _ in 0..number_of_vlrs {
165 let raw_vlr = raw::Vlr::read_from(&mut cursor, false)?;
166 let vlr = las::Vlr::new(raw_vlr);
167
168 match (vlr.user_id.as_str(), vlr.record_id) {
169 ("copc", 1) => {
170 copc_info = Some(CopcInfo::parse(&vlr.data)?);
171 }
172 ("laszip encoded", 22204) => {
173 laz_vlr = Some(LazVlr::read_from(vlr.data.as_slice())?);
174 }
175 _ => {}
176 }
177
178 builder.vlrs.push(vlr);
179 }
180
181 let las_header = builder.into_header()?;
182 let copc_info = copc_info.ok_or(CopcError::CopcInfoNotFound)?;
183 let laz_vlr = laz_vlr.ok_or(CopcError::LazVlrNotFound)?;
184
185 Ok(CopcHeader {
186 las_header,
187 copc_info,
188 laz_vlr,
189 evlr_offset,
190 evlr_count,
191 })
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn test_copc_info_root_bounds() {
200 let info = CopcInfo {
201 center: [100.0, 200.0, 10.0],
202 halfsize: 50.0,
203 spacing: 1.0,
204 root_hier_offset: 0,
205 root_hier_size: 0,
206 gpstime_minimum: 0.0,
207 gpstime_maximum: 0.0,
208 };
209 let b = info.root_bounds();
210 assert_eq!(b.min, [50.0, 150.0, -40.0]);
211 assert_eq!(b.max, [150.0, 250.0, 60.0]);
212 }
213
214 #[test]
215 fn test_level_for_resolution() {
216 let info = CopcInfo {
217 center: [0.0, 0.0, 0.0],
218 halfsize: 500.0,
219 spacing: 10.0,
220 root_hier_offset: 0,
221 root_hier_size: 0,
222 gpstime_minimum: 0.0,
223 gpstime_maximum: 0.0,
224 };
225
226 assert_eq!(info.level_for_resolution(10.0), 0);
228 assert_eq!(info.level_for_resolution(5.0), 1);
230 assert_eq!(info.level_for_resolution(2.5), 2);
232 assert_eq!(info.level_for_resolution(1.0), 4);
234 assert_eq!(info.level_for_resolution(0.5), 5);
236 assert_eq!(info.level_for_resolution(20.0), 0);
238 assert_eq!(info.level_for_resolution(0.0), 0);
240 assert_eq!(info.level_for_resolution(-1.0), 0);
241 }
242}