1use crate::{
2 header::Error, point::Format, raw, Bounds, GpsTimeType, Header, Result, Transform, Vector,
3 Version, Vlr,
4};
5use chrono::NaiveDate;
6use std::{cmp::Ordering, collections::HashMap};
7use uuid::Uuid;
8
9#[derive(Clone, Debug, Default)]
11pub struct Builder {
12 pub date: Option<NaiveDate>,
14
15 pub file_source_id: u16,
17
18 pub generating_software: String,
20
21 pub gps_time_type: GpsTimeType,
23
24 pub guid: Uuid,
26
27 pub has_synthetic_return_numbers: bool,
29
30 pub has_wkt_crs: bool,
32
33 pub padding: Vec<u8>,
35
36 pub point_format: Format,
38
39 pub point_padding: Vec<u8>,
43
44 pub system_identifier: String,
46
47 pub transforms: Vector<Transform>,
50
51 pub version: Version,
53
54 pub vlr_padding: Vec<u8>,
56
57 pub vlrs: Vec<Vlr>,
59
60 pub evlrs: Vec<Vlr>,
62
63 number_of_points_by_return: HashMap<u8, u64>,
64 number_of_points: u64,
65 bounds: Bounds,
66}
67
68impl Builder {
69 pub fn new(raw_header: raw::Header) -> Result<Builder> {
78 use crate::utils::AsLasStr;
79
80 let number_of_points = if raw_header.number_of_point_records > 0 {
81 u64::from(raw_header.number_of_point_records)
82 } else {
83 raw_header
84 .large_file
85 .map(|l| l.number_of_point_records)
86 .unwrap_or(0)
87 };
88 let number_of_points_by_return =
89 if raw_header.number_of_points_by_return.iter().any(|&n| n > 0) {
90 number_of_points_hash_map(&raw_header.number_of_points_by_return)
91 } else {
92 raw_header
93 .large_file
94 .map(|f| number_of_points_hash_map(&f.number_of_points_by_return))
95 .unwrap_or_default()
96 };
97 let mut point_format = Format::new(raw_header.point_data_record_format)?;
98 let n = point_format.len();
99 match raw_header.point_data_record_length.cmp(&n) {
100 Ordering::Less => {
101 return Err(Error::PointDataRecordLengthTooLarge {
102 format: point_format,
103 len: raw_header.point_data_record_length,
104 })
105 }
106 Ordering::Equal => {} Ordering::Greater => point_format.extra_bytes = raw_header.point_data_record_length - n,
108 }
109 Ok(Builder {
110 date: NaiveDate::from_yo_opt(
111 i32::from(raw_header.file_creation_year),
112 u32::from(raw_header.file_creation_day_of_year),
113 ),
114 point_padding: Vec::new(),
115 evlrs: Vec::new(),
116 file_source_id: raw_header.file_source_id,
117 generating_software: raw_header
118 .generating_software
119 .as_ref()
120 .as_las_string_lossy(),
121 gps_time_type: raw_header.global_encoding.into(),
122 guid: Uuid::from_bytes(raw_header.guid),
123 has_synthetic_return_numbers: raw_header.global_encoding & 8 == 8,
124 has_wkt_crs: raw_header.global_encoding & 16 == 16,
125 padding: raw_header.padding,
126 point_format,
127 system_identifier: raw_header.system_identifier.as_ref().as_las_string_lossy(),
128 transforms: Vector {
129 x: Transform {
130 scale: raw_header.x_scale_factor,
131 offset: raw_header.x_offset,
132 },
133 y: Transform {
134 scale: raw_header.y_scale_factor,
135 offset: raw_header.y_offset,
136 },
137 z: Transform {
138 scale: raw_header.z_scale_factor,
139 offset: raw_header.z_offset,
140 },
141 },
142 version: raw_header.version,
143 vlr_padding: Vec::new(),
144 vlrs: Vec::new(),
145 bounds: Bounds {
146 min: Vector {
147 x: raw_header.min_x,
148 y: raw_header.min_y,
149 z: raw_header.min_z,
150 },
151 max: Vector {
152 x: raw_header.max_x,
153 y: raw_header.max_y,
154 z: raw_header.max_z,
155 },
156 },
157 number_of_points,
158 number_of_points_by_return,
159 })
160 }
161
162 pub fn into_header(mut self) -> Result<Header> {
171 use crate::{
172 feature::{Evlrs, FileSourceId, GpsStandardTime, SyntheticReturnNumbers},
173 raw::POINT_DATA_START_SIGNATURE,
174 };
175
176 let n = self.vlr_padding.len();
177 if self.version.requires_point_data_start_signature()
178 && (n < 2 || self.vlr_padding[n - 2..] != POINT_DATA_START_SIGNATURE)
179 {
180 self.vlr_padding.extend(&POINT_DATA_START_SIGNATURE);
181 }
182 if self.file_source_id != 0 {
183 self.version.verify_support_for::<FileSourceId>()?;
184 }
185 if self.has_synthetic_return_numbers {
186 self.version
187 .verify_support_for::<SyntheticReturnNumbers>()?;
188 }
189 if self.gps_time_type.is_standard() {
190 self.version.verify_support_for::<GpsStandardTime>()?;
191 }
192 if !self.version.supports_point_format(self.point_format) {
194 return Err(Error::UnsupportedFormat {
195 version: self.version,
196 format: self.point_format,
197 });
198 }
199 let mut vlrs = Vec::new();
200 let mut evlrs = Vec::new();
201 for evlr in self.evlrs {
202 if self.version.supports::<Evlrs>() || evlr.has_large_data() {
203 evlrs.push(evlr);
204 } else {
205 log::warn!("moving Evlr to Vlr because version does not support Evlrs: user_id={}, record_id={}, description={}", evlr.user_id, evlr.record_id, evlr.description);
206 vlrs.push(evlr);
207 }
208 }
209 for vlr in self.vlrs {
210 if vlr.has_large_data() {
211 evlrs.push(vlr);
212 } else {
213 vlrs.push(vlr);
214 }
215 }
216 if !evlrs.is_empty() {
217 self.version.verify_support_for::<Evlrs>()?;
218 } else if !self.point_padding.is_empty() {
219 return Err(Error::PointPaddingNotAllowed);
220 }
221 let header = Header {
222 bounds: self.bounds,
223 date: self.date,
224 evlrs,
225 file_source_id: self.file_source_id,
226 generating_software: self.generating_software,
227 gps_time_type: self.gps_time_type,
228 guid: self.guid,
229 has_synthetic_return_numbers: self.has_synthetic_return_numbers,
230 has_wkt_crs: self.has_wkt_crs || self.point_format.is_extended,
231 number_of_points: self.number_of_points,
232 number_of_points_by_return: self.number_of_points_by_return,
233 padding: self.padding,
234 point_format: self.point_format,
235 point_padding: self.point_padding,
236 start_of_first_evlr: None,
237 system_identifier: self.system_identifier,
238 transforms: self.transforms,
239 version: self.version,
240 vlr_padding: self.vlr_padding,
241 vlrs,
242 };
243 Ok(header)
244 }
245
246 pub fn minimum_supported_version(&self) -> Option<Version> {
256 for minor in [0, 1, 2, 3, 4] {
259 let mut builder = self.clone();
260 builder.version.minor = minor;
261 if builder.into_header().is_ok() {
262 return Some(Version::new(1, minor));
263 }
264 }
265 None
266 }
267}
268
269impl<V: Into<Version>> From<V> for Builder {
270 fn from(version: V) -> Builder {
271 Builder {
272 version: version.into(),
273 ..Default::default()
274 }
275 }
276}
277
278impl From<Header> for Builder {
279 fn from(header: Header) -> Builder {
280 Builder {
281 bounds: header.bounds,
282 date: header.date,
283 evlrs: header.evlrs,
284 file_source_id: header.file_source_id,
285 generating_software: header.generating_software,
286 gps_time_type: header.gps_time_type,
287 guid: header.guid,
288 has_synthetic_return_numbers: header.has_synthetic_return_numbers,
289 has_wkt_crs: header.has_wkt_crs,
290 number_of_points: header.number_of_points,
291 number_of_points_by_return: header.number_of_points_by_return,
292 padding: header.padding,
293 point_format: header.point_format,
294 point_padding: header.point_padding,
295 system_identifier: header.system_identifier,
296 transforms: header.transforms,
297 version: header.version,
298 vlr_padding: header.vlr_padding,
299 vlrs: header.vlrs,
300 }
301 }
302}
303
304fn number_of_points_hash_map<T: Copy + Into<u64>>(slice: &[T]) -> HashMap<u8, u64> {
305 assert!(slice.len() < u8::MAX as usize);
306 slice
307 .iter()
308 .enumerate()
309 .filter_map(|(i, &n)| {
310 if n.into() > 0 {
311 Some((i as u8 + 1, n.into()))
312 } else {
313 None
314 }
315 })
316 .collect()
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn no_day_no_date() {
325 let raw_header = raw::Header {
326 file_creation_day_of_year: 0,
327 ..Default::default()
328 };
329 let builder = Builder::new(raw_header).unwrap();
330 assert!(builder.date.is_none());
331 }
332
333 #[test]
334 fn no_year_no_date() {
335 let raw_header = raw::Header {
336 file_creation_year: 0,
337 ..Default::default()
338 };
339 let builder = Builder::new(raw_header).unwrap();
340 assert!(builder.date.is_none());
341 }
342
343 #[test]
346 fn evlr_downgrade() {
347 let mut builder = Builder::from((1, 2));
348 builder.evlrs.push(Vlr::default());
349 let header = builder.into_header().unwrap();
350 assert_eq!(1, header.vlrs().len());
351 assert_eq!(0, header.evlrs().len());
352 }
353
354 #[test]
355 fn evlr_upgrade() {
356 let mut builder = Builder::from((1, 4));
357 let vlr = Vlr {
358 data: vec![0; u16::MAX as usize + 1],
359 ..Default::default()
360 };
361 builder.vlrs.push(vlr);
362 let header = builder.into_header().unwrap();
363 assert_eq!(0, header.vlrs().len());
364 assert_eq!(1, header.evlrs().len());
365 }
366
367 #[test]
368 fn point_padding_no_evlrs() {
369 let mut builder = Builder::from((1, 4));
370 builder.point_padding = vec![0];
371 assert!(builder.into_header().is_err());
372 }
373
374 #[test]
375 fn point_data_start_signature() {
376 let mut builder = Builder::from((1, 0));
377 builder.vlr_padding = vec![42];
378 let header = builder.into_header().unwrap();
379 assert_eq!(vec![42, 0xCC, 0xDD], *header.vlr_padding());
380
381 let builder = Builder::from((1, 2));
382 assert!(builder.into_header().unwrap().vlr_padding().is_empty());
383 }
384}