Skip to main content

las/header/
builder.rs

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/// Use this structure to build a [Header].
10#[derive(Clone, Debug, Default)]
11pub struct Builder {
12    /// The date of file creation.
13    pub date: Option<NaiveDate>,
14
15    /// The file source id, sometimes the flight line.
16    pub file_source_id: u16,
17
18    /// The software that created this file.
19    pub generating_software: String,
20
21    /// The type of gps time, either week or standard.
22    pub gps_time_type: GpsTimeType,
23
24    /// A globally unique identifier.
25    pub guid: Uuid,
26
27    /// Are the return numbers in this file synthetic?
28    pub has_synthetic_return_numbers: bool,
29
30    /// Does this file has a WKT CRS?
31    pub has_wkt_crs: bool,
32
33    /// Bytes after the header but before the vlrs.
34    pub padding: Vec<u8>,
35
36    /// The format that the points will be written in.
37    pub point_format: Format,
38
39    /// The bytes after the points but before any evlrs.
40    ///
41    /// Discouraged.
42    pub point_padding: Vec<u8>,
43
44    /// The system that generated the points.
45    pub system_identifier: String,
46
47    /// The scale and offset that will be used to convert coordinates to `i16`s to write in the
48    /// file.
49    pub transforms: Vector<Transform>,
50
51    /// The las version.
52    pub version: Version,
53
54    /// The bytes after the vlrs but before the points.
55    pub vlr_padding: Vec<u8>,
56
57    /// The variable length records.
58    pub vlrs: Vec<Vlr>,
59
60    /// The extended variable length records.
61    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    /// Creates a new builder from a raw header.
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// use las::Builder;
75    /// let builder = Builder::new(Default::default()).unwrap();
76    /// ```
77    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 => {} // pass
107            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    /// Builds a [Header].
163    ///
164    /// # Examples
165    ///
166    /// ```
167    /// use las::Builder;
168    /// let header = Builder::new(Default::default()).unwrap().into_header().unwrap();
169    /// ```
170    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        // TODO check waveforms
193        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    /// Returns the minimum supported version for this builder, as determined by its features.
247    ///
248    /// # Examples
249    ///
250    /// ```
251    /// use las::{Builder, Version};
252    ///
253    /// assert_eq!(Builder::default().minimum_supported_version().unwrap(), Version::new(1, 0));
254    /// ```
255    pub fn minimum_supported_version(&self) -> Option<Version> {
256        // TODO can we make a validity check that doesn't involve a full
257        // conversion into a header, without duplicating a lot of logic?
258        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    // TODO assert wkt properties
344
345    #[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}