pasture_io/las/
las_writer.rs

1#![allow(clippy::upper_case_acronyms)]
2use std::{fs::File, io::BufWriter, io::Seek, io::Write, path::Path};
3
4use anyhow::{Context, Result};
5use las_rs::Builder;
6use pasture_core::{containers::BorrowedBuffer, layout::PointLayout};
7
8use crate::{base::PointWriter, las::las_point_format_from_point_layout};
9
10use super::{path_is_compressed_las_file, RawLASWriter, RawLAZWriter};
11
12enum WriterVariant<T: Write + Seek + Send + 'static> {
13    LAS(RawLASWriter<T>),
14    LAZ(RawLAZWriter<T>),
15}
16
17/// `PointWriter` implementation for LAS/LAZ files.
18///
19/// *NOTE*: Due to the nature of the LAS file format, this file
20/// writer requires manual `flush` calls in order to actually write the LAS/LAZ data. Once you are done
21/// writing points, make sure to call `flush` so that the LAS header is updated correctly.
22pub struct LASWriter<T: Write + Seek + Send + 'static> {
23    writer: WriterVariant<T>,
24}
25
26impl<T: Write + Seek + Send + 'static> LASWriter<T> {
27    /// Creates a new `LASWriter` from the given `writer`. This uses a default-created LAS header for writing,
28    /// with an appropriate point format determined from the given `point_layout`. The LAS header uses a scale
29    /// of 0.001, which yields 1mm precision. LAS version 1.4 is used.
30    /// If `is_compressed` is set, the writer will write compressed `LAZ` files instead of `LAS` files.
31    pub fn from_writer_and_point_layout(
32        writer: T,
33        point_layout: &PointLayout,
34        is_compressed: bool,
35    ) -> Result<Self> {
36        // TODO Support writing extra bytes, for now they will be ignored
37        let point_format = las_point_format_from_point_layout(point_layout);
38        let mut header_builder = Builder::from((1, 4));
39        header_builder.point_format = point_format;
40        header_builder.transforms = las_rs::Vector {
41            x: las_rs::Transform {
42                offset: 0.0,
43                scale: 0.001,
44            },
45            y: las_rs::Transform {
46                offset: 0.0,
47                scale: 0.001,
48            },
49            z: las_rs::Transform {
50                offset: 0.0,
51                scale: 0.001,
52            },
53        };
54        let las_header = header_builder
55            .into_header()
56            .context("Could not default-create LAS header")?;
57        Self::from_writer_and_header(writer, las_header, is_compressed)
58    }
59
60    /// Creates a new `LASWriter` from the given writer and LAS header. If `is_compressed` is set,
61    /// the writer will write compressed `LAZ` files instead of `LAS` files.
62    pub fn from_writer_and_header(
63        writer: T,
64        header: las::Header,
65        is_compressed: bool,
66    ) -> Result<Self> {
67        let raw_writer: WriterVariant<T> = if is_compressed {
68            WriterVariant::LAZ(RawLAZWriter::from_write_and_header(writer, header)?)
69        } else {
70            WriterVariant::LAS(RawLASWriter::from_write_and_header(writer, header)?)
71        };
72        Ok(Self { writer: raw_writer })
73    }
74
75    /// Unwraps with LASWriter, returning the underlying write type `T`. All internal data is flushed before returning
76    /// the writer
77    pub fn into_inner(self) -> Result<T> {
78        match self.writer {
79            WriterVariant::LAS(writer) => writer.into_inner(),
80            WriterVariant::LAZ(writer) => writer.into_inner(),
81        }
82    }
83}
84
85impl LASWriter<BufWriter<File>> {
86    /// Creates a new `LASWriter` from the given path and LAS header
87    pub fn from_path_and_header<P: AsRef<Path>>(path: P, header: las::Header) -> Result<Self> {
88        let is_compressed = path_is_compressed_las_file(path.as_ref())?;
89        let writer = BufWriter::new(File::create(path)?);
90        Self::from_writer_and_header(writer, header, is_compressed)
91    }
92
93    /// Creates a new `LASWriter` from the given `path` and `point_layout`
94    pub fn from_path_and_point_layout<P: AsRef<Path>>(
95        path: P,
96        point_layout: &PointLayout,
97    ) -> Result<Self> {
98        let is_compressed = path_is_compressed_las_file(path.as_ref())?;
99        let writer = BufWriter::new(File::create(path)?);
100        Self::from_writer_and_point_layout(writer, point_layout, is_compressed)
101    }
102}
103
104impl<T: Write + Seek + Send + 'static> PointWriter for LASWriter<T> {
105    fn write<'a, B: BorrowedBuffer<'a>>(&mut self, points: &'a B) -> Result<()> {
106        match &mut self.writer {
107            WriterVariant::LAS(writer) => writer.write(points),
108            WriterVariant::LAZ(writer) => writer.write(points),
109        }
110    }
111
112    fn flush(&mut self) -> Result<()> {
113        match &mut self.writer {
114            WriterVariant::LAS(writer) => writer.flush(),
115            WriterVariant::LAZ(writer) => writer.flush(),
116        }
117    }
118
119    fn get_default_point_layout(&self) -> &PointLayout {
120        match &self.writer {
121            WriterVariant::LAS(writer) => writer.get_default_point_layout(),
122            WriterVariant::LAZ(writer) => writer.get_default_point_layout(),
123        }
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use std::{io::Cursor, path::PathBuf};
130
131    use las::{point::Format, Builder};
132    use pasture_core::{
133        containers::{BorrowedBufferExt, MakeBufferFromLayout, OwningBuffer, VectorBuffer},
134        layout::PointType,
135        nalgebra::Vector3,
136    };
137    use scopeguard::defer;
138
139    use crate::{
140        base::PointReader,
141        las::{
142            LASReader, LasPointFormat0, LasPointFormat1, LasPointFormat2, LasPointFormat3,
143            LasPointFormat4, LasPointFormat5,
144        },
145    };
146    use pasture_derive::PointType;
147
148    use super::*;
149
150    #[repr(C, packed)]
151    #[derive(Debug, Clone, Copy, PointType, bytemuck::AnyBitPattern, bytemuck::NoUninit)]
152    struct TestPoint {
153        #[pasture(BUILTIN_POSITION_3D)]
154        pub position: Vector3<f64>,
155        #[pasture(BUILTIN_COLOR_RGB)]
156        pub color: Vector3<u16>,
157    }
158
159    fn get_test_points_custom_format() -> Vec<TestPoint> {
160        vec![
161            TestPoint {
162                position: Vector3::new(1.0, 2.0, 3.0),
163                color: Vector3::new(128, 129, 130),
164            },
165            TestPoint {
166                position: Vector3::new(4.0, 5.0, 6.0),
167                color: Vector3::new(1024, 1025, 1026),
168            },
169        ]
170    }
171
172    fn get_test_points_las_format_0() -> Vec<LasPointFormat0> {
173        vec![
174            LasPointFormat0 {
175                classification: 1,
176                edge_of_flight_line: 0,
177                intensity: 1,
178                number_of_returns: 1,
179                point_source_id: 1,
180                position: Vector3::new(1.0, 1.0, 1.0),
181                return_number: 1,
182                scan_angle_rank: 1,
183                scan_direction_flag: 0,
184                user_data: 1,
185            },
186            LasPointFormat0 {
187                classification: 2,
188                edge_of_flight_line: 1,
189                intensity: 2,
190                number_of_returns: 2,
191                point_source_id: 2,
192                position: Vector3::new(2.0, 2.0, 2.0),
193                return_number: 2,
194                scan_angle_rank: 2,
195                scan_direction_flag: 1,
196                user_data: 2,
197            },
198        ]
199    }
200
201    fn get_test_points_las_format_1() -> Vec<LasPointFormat1> {
202        vec![
203            LasPointFormat1 {
204                classification: 1,
205                edge_of_flight_line: 0,
206                intensity: 1,
207                number_of_returns: 1,
208                point_source_id: 1,
209                position: Vector3::new(1.0, 1.0, 1.0),
210                return_number: 1,
211                scan_angle_rank: 1,
212                scan_direction_flag: 0,
213                user_data: 1,
214                gps_time: 1234.0,
215            },
216            LasPointFormat1 {
217                classification: 2,
218                edge_of_flight_line: 1,
219                intensity: 2,
220                number_of_returns: 2,
221                point_source_id: 2,
222                position: Vector3::new(2.0, 2.0, 2.0),
223                return_number: 2,
224                scan_angle_rank: 2,
225                scan_direction_flag: 1,
226                user_data: 2,
227                gps_time: 5678.0,
228            },
229        ]
230    }
231
232    fn get_test_points_las_format_2() -> Vec<LasPointFormat2> {
233        vec![
234            LasPointFormat2 {
235                classification: 1,
236                edge_of_flight_line: 0,
237                intensity: 1,
238                number_of_returns: 1,
239                point_source_id: 1,
240                position: Vector3::new(1.0, 1.0, 1.0),
241                return_number: 1,
242                scan_angle_rank: 1,
243                scan_direction_flag: 0,
244                user_data: 1,
245                color_rgb: Vector3::new(128, 129, 130),
246            },
247            LasPointFormat2 {
248                classification: 2,
249                edge_of_flight_line: 1,
250                intensity: 2,
251                number_of_returns: 2,
252                point_source_id: 2,
253                position: Vector3::new(2.0, 2.0, 2.0),
254                return_number: 2,
255                scan_angle_rank: 2,
256                scan_direction_flag: 1,
257                user_data: 2,
258                color_rgb: Vector3::new(1024, 1025, 1026),
259            },
260        ]
261    }
262
263    fn get_test_points_las_format_3() -> Vec<LasPointFormat3> {
264        vec![
265            LasPointFormat3 {
266                classification: 1,
267                edge_of_flight_line: 0,
268                intensity: 1,
269                number_of_returns: 1,
270                point_source_id: 1,
271                position: Vector3::new(1.0, 1.0, 1.0),
272                return_number: 1,
273                scan_angle_rank: 1,
274                scan_direction_flag: 0,
275                user_data: 1,
276                color_rgb: Vector3::new(128, 129, 130),
277                gps_time: 1234.0,
278            },
279            LasPointFormat3 {
280                classification: 2,
281                edge_of_flight_line: 1,
282                intensity: 2,
283                number_of_returns: 2,
284                point_source_id: 2,
285                position: Vector3::new(2.0, 2.0, 2.0),
286                return_number: 2,
287                scan_angle_rank: 2,
288                scan_direction_flag: 1,
289                user_data: 2,
290                color_rgb: Vector3::new(1024, 1025, 1026),
291                gps_time: 5678.0,
292            },
293        ]
294    }
295
296    fn get_test_points_las_format_4() -> Vec<LasPointFormat4> {
297        vec![
298            LasPointFormat4 {
299                classification: 1,
300                edge_of_flight_line: 0,
301                intensity: 1,
302                number_of_returns: 1,
303                point_source_id: 1,
304                position: Vector3::new(1.0, 1.0, 1.0),
305                return_number: 1,
306                scan_angle_rank: 1,
307                scan_direction_flag: 0,
308                user_data: 1,
309                gps_time: 1234.0,
310                byte_offset_to_waveform_data: 10,
311                return_point_waveform_location: 20.0,
312                wave_packet_descriptor_index: 30,
313                waveform_packet_size: 40,
314                waveform_parameters: Vector3::new(1.0, 2.0, 3.0),
315            },
316            LasPointFormat4 {
317                classification: 2,
318                edge_of_flight_line: 1,
319                intensity: 2,
320                number_of_returns: 2,
321                point_source_id: 2,
322                position: Vector3::new(2.0, 2.0, 2.0),
323                return_number: 2,
324                scan_angle_rank: 2,
325                scan_direction_flag: 1,
326                user_data: 2,
327                gps_time: 5678.0,
328                byte_offset_to_waveform_data: 11,
329                return_point_waveform_location: 22.0,
330                wave_packet_descriptor_index: 33,
331                waveform_packet_size: 44,
332                waveform_parameters: Vector3::new(4.0, 5.0, 6.0),
333            },
334        ]
335    }
336
337    fn get_test_points_las_format_5() -> Vec<LasPointFormat5> {
338        vec![
339            LasPointFormat5 {
340                classification: 1,
341                edge_of_flight_line: 0,
342                intensity: 1,
343                number_of_returns: 1,
344                point_source_id: 1,
345                position: Vector3::new(1.0, 1.0, 1.0),
346                return_number: 1,
347                scan_angle_rank: 1,
348                scan_direction_flag: 0,
349                user_data: 1,
350                gps_time: 1234.0,
351                color_rgb: Vector3::new(128, 129, 130),
352                byte_offset_to_waveform_data: 10,
353                return_point_waveform_location: 20.0,
354                wave_packet_descriptor_index: 30,
355                waveform_packet_size: 40,
356                waveform_parameters: Vector3::new(1.0, 2.0, 3.0),
357            },
358            LasPointFormat5 {
359                classification: 2,
360                edge_of_flight_line: 1,
361                intensity: 2,
362                number_of_returns: 2,
363                point_source_id: 2,
364                position: Vector3::new(2.0, 2.0, 2.0),
365                return_number: 2,
366                scan_angle_rank: 2,
367                scan_direction_flag: 1,
368                user_data: 2,
369                gps_time: 5678.0,
370                color_rgb: Vector3::new(1024, 1025, 1026),
371                byte_offset_to_waveform_data: 11,
372                return_point_waveform_location: 22.0,
373                wave_packet_descriptor_index: 33,
374                waveform_packet_size: 44,
375                waveform_parameters: Vector3::new(4.0, 5.0, 6.0),
376            },
377        ]
378    }
379
380    fn prepare_point_buffer<T: PointType + Clone + Copy>(test_points: &[T]) -> VectorBuffer {
381        test_points.iter().copied().collect()
382    }
383
384    #[test]
385    fn test_write_las_format_0() -> Result<()> {
386        let source_points = get_test_points_las_format_0();
387        let source_point_buffer = prepare_point_buffer(&source_points);
388
389        //Write, then read, then check for equality
390
391        let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
392        test_file_path.push("test_write_las_format_0.las");
393
394        defer! {
395            std::fs::remove_file(&test_file_path).expect("Removing test file failed!");
396        }
397
398        let mut las_header_builder = Builder::from((1, 4));
399        las_header_builder.point_format = Format::new(0)?;
400
401        {
402            let mut writer = LASWriter::from_path_and_header(
403                &test_file_path,
404                las_header_builder.into_header().unwrap(),
405            )?;
406            writer.write(&source_point_buffer)?;
407            writer.flush()?;
408        }
409
410        {
411            let mut reader = LASReader::from_path(&test_file_path, false)?;
412            let mut read_points_buffer =
413                VectorBuffer::new_from_layout(reader.get_default_point_layout().clone());
414            read_points_buffer.resize(source_points.len());
415            reader.read_into(&mut read_points_buffer, source_points.len())?;
416            let read_points: Vec<LasPointFormat0> = read_points_buffer.view().into_iter().collect();
417
418            assert_eq!(read_points, source_points);
419        }
420
421        Ok(())
422    }
423
424    #[test]
425    fn test_write_las_format_0_different_layout() -> Result<()> {
426        let source_points = get_test_points_custom_format();
427        let source_point_buffer = prepare_point_buffer(&source_points);
428
429        //Write, then read, then check for equality
430
431        let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
432        test_file_path.push("test_write_las_format_0_different_layout.las");
433
434        defer! {
435            std::fs::remove_file(&test_file_path).expect("Removing test file failed!");
436        }
437
438        let mut las_header_builder = Builder::from((1, 4));
439        las_header_builder.point_format = Format::new(0)?;
440
441        {
442            let mut writer = LASWriter::from_path_and_header(
443                &test_file_path,
444                las_header_builder.into_header().unwrap(),
445            )?;
446            writer.write(&source_point_buffer)?;
447            writer.flush()?;
448        }
449
450        {
451            let mut reader = LASReader::from_path(&test_file_path, false)?;
452            let mut read_points_buffer =
453                VectorBuffer::new_from_layout(reader.get_default_point_layout().clone());
454            read_points_buffer.resize(source_points.len());
455            reader.read_into(&mut read_points_buffer, source_points.len())?;
456            let read_points: Vec<LasPointFormat0> = read_points_buffer.view().into_iter().collect();
457
458            for (source, read) in source_points.iter().zip(read_points.iter()) {
459                assert_eq!(
460                    { source.position },
461                    { read.position },
462                    "Position of read point is different than of source point!"
463                );
464                assert_eq!(
465                    0, read.classification,
466                    "Classification of read point was not zero!"
467                );
468                assert_eq!(
469                    0, read.edge_of_flight_line,
470                    "Edge of flight line of read point was not false!"
471                );
472                assert_eq!(
473                    0,
474                    { read.intensity },
475                    "Intensity of read point was not zero!"
476                );
477                assert_eq!(
478                    0, read.number_of_returns,
479                    "Number of returns of read point was not zero!"
480                );
481                assert_eq!(
482                    0,
483                    { read.point_source_id },
484                    "Point source ID of read point was not zero!"
485                );
486                assert_eq!(
487                    0, read.return_number,
488                    "Return number of read point was not zero!"
489                );
490                assert_eq!(
491                    0, read.scan_angle_rank,
492                    "Scan angle rank of read point was not zero!"
493                );
494                assert_eq!(
495                    0, read.scan_direction_flag,
496                    "Scan direction flag of read point was not false!"
497                );
498                assert_eq!(0, read.user_data, "User data of read point was not zero!");
499            }
500        }
501
502        Ok(())
503    }
504
505    #[test]
506    fn test_write_las_format_1() -> Result<()> {
507        let source_points = get_test_points_las_format_1();
508        let source_point_buffer = prepare_point_buffer(&source_points);
509
510        //Write, then read, then check for equality
511
512        let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
513        test_file_path.push("test_write_las_format_1.las");
514
515        defer! {
516            std::fs::remove_file(&test_file_path).expect("Removing test file failed!");
517        }
518
519        let mut las_header_builder = Builder::from((1, 4));
520        las_header_builder.point_format = Format::new(1)?;
521
522        {
523            let mut writer = LASWriter::from_path_and_header(
524                &test_file_path,
525                las_header_builder.into_header().unwrap(),
526            )?;
527            writer.write(&source_point_buffer)?;
528            writer.flush()?;
529        }
530
531        {
532            let mut reader = LASReader::from_path(&test_file_path, false)?;
533            let mut read_points_buffer =
534                VectorBuffer::new_from_layout(reader.get_default_point_layout().clone());
535            read_points_buffer.resize(source_points.len());
536            reader.read_into(&mut read_points_buffer, source_points.len())?;
537            let read_points: Vec<LasPointFormat1> = read_points_buffer.view().into_iter().collect();
538
539            assert_eq!(read_points, source_points);
540        }
541
542        Ok(())
543    }
544
545    #[test]
546    fn test_write_las_format_1_different_layout() -> Result<()> {
547        let source_points = get_test_points_custom_format();
548        let source_point_buffer = prepare_point_buffer(&source_points);
549
550        //Write, then read, then check for equality
551
552        let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
553        test_file_path.push("test_write_las_format_1_different_layout.las");
554
555        defer! {
556            std::fs::remove_file(&test_file_path).expect("Removing test file failed!");
557        }
558
559        let mut las_header_builder = Builder::from((1, 4));
560        las_header_builder.point_format = Format::new(1)?;
561
562        {
563            let mut writer = LASWriter::from_path_and_header(
564                &test_file_path,
565                las_header_builder.into_header().unwrap(),
566            )?;
567            writer.write(&source_point_buffer)?;
568            writer.flush()?;
569        }
570
571        {
572            let mut reader = LASReader::from_path(&test_file_path, false)?;
573            let mut read_points_buffer =
574                VectorBuffer::new_from_layout(reader.get_default_point_layout().clone());
575            read_points_buffer.resize(source_points.len());
576            reader.read_into(&mut read_points_buffer, source_points.len())?;
577            let read_points: Vec<LasPointFormat1> = read_points_buffer.view().into_iter().collect();
578
579            for (source, read) in source_points.iter().zip(read_points.iter()) {
580                assert_eq!(
581                    { source.position },
582                    { read.position },
583                    "Position of read point is different than of source point!"
584                );
585                assert_eq!(
586                    0, read.classification,
587                    "Classification of read point was not zero!"
588                );
589                assert_eq!(
590                    0, read.edge_of_flight_line,
591                    "Edge of flight line of read point was not false!"
592                );
593                assert_eq!(
594                    0,
595                    { read.intensity },
596                    "Intensity of read point was not zero!"
597                );
598                assert_eq!(
599                    0, read.number_of_returns,
600                    "Number of returns of read point was not zero!"
601                );
602                assert_eq!(
603                    0,
604                    { read.point_source_id },
605                    "Point source ID of read point was not zero!"
606                );
607                assert_eq!(
608                    0, read.return_number,
609                    "Return number of read point was not zero!"
610                );
611                assert_eq!(
612                    0, read.scan_angle_rank,
613                    "Scan angle rank of read point was not zero!"
614                );
615                assert_eq!(
616                    0, read.scan_direction_flag,
617                    "Scan direction flag of read point was not false!"
618                );
619                assert_eq!(0, read.user_data, "User data of read point was not zero!");
620                assert_eq!(
621                    0.0,
622                    { read.gps_time },
623                    "GPS time of read point was not zero!"
624                );
625            }
626        }
627
628        Ok(())
629    }
630
631    #[test]
632    fn test_write_las_format_2() -> Result<()> {
633        let source_points = get_test_points_las_format_2();
634        let source_point_buffer = prepare_point_buffer(&source_points);
635
636        //Write, then read, then check for equality
637
638        let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
639        test_file_path.push("test_write_las_format_2.las");
640
641        defer! {
642            std::fs::remove_file(&test_file_path).expect("Removing test file failed!");
643        }
644
645        let mut las_header_builder = Builder::from((1, 4));
646        las_header_builder.point_format = Format::new(2)?;
647
648        {
649            let mut writer = LASWriter::from_path_and_header(
650                &test_file_path,
651                las_header_builder.into_header().unwrap(),
652            )?;
653            writer.write(&source_point_buffer)?;
654            writer.flush()?;
655        }
656
657        {
658            let mut reader = LASReader::from_path(&test_file_path, false)?;
659            let mut read_points_buffer =
660                VectorBuffer::new_from_layout(reader.get_default_point_layout().clone());
661            read_points_buffer.resize(source_points.len());
662            reader.read_into(&mut read_points_buffer, source_points.len())?;
663            let read_points: Vec<LasPointFormat2> = read_points_buffer.view().into_iter().collect();
664
665            assert_eq!(read_points, source_points);
666        }
667
668        Ok(())
669    }
670
671    #[test]
672    fn test_write_las_format_2_different_layout() -> Result<()> {
673        let source_points = get_test_points_custom_format();
674        let source_point_buffer = prepare_point_buffer(&source_points);
675
676        //Write, then read, then check for equality
677
678        let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
679        test_file_path.push("test_write_las_format_2_different_layout.las");
680
681        defer! {
682            std::fs::remove_file(&test_file_path).expect("Removing test file failed!");
683        }
684
685        let mut las_header_builder = Builder::from((1, 4));
686        las_header_builder.point_format = Format::new(2)?;
687
688        {
689            let mut writer = LASWriter::from_path_and_header(
690                &test_file_path,
691                las_header_builder.into_header().unwrap(),
692            )?;
693            writer.write(&source_point_buffer)?;
694            writer.flush()?;
695        }
696
697        {
698            let mut reader = LASReader::from_path(&test_file_path, false)?;
699            let mut read_points_buffer =
700                VectorBuffer::new_from_layout(reader.get_default_point_layout().clone());
701            read_points_buffer.resize(source_points.len());
702            reader.read_into(&mut read_points_buffer, source_points.len())?;
703            let read_points: Vec<LasPointFormat2> = read_points_buffer.view().into_iter().collect();
704
705            for (source, read) in source_points.iter().zip(read_points.iter()) {
706                assert_eq!(
707                    { source.position },
708                    { read.position },
709                    "Position of read point is different than of source point!"
710                );
711                assert_eq!(
712                    { source.color },
713                    { read.color_rgb },
714                    "Color of read point is different than of source point!"
715                );
716                assert_eq!(
717                    0, read.classification,
718                    "Classification of read point was not zero!"
719                );
720                assert_eq!(
721                    0, read.edge_of_flight_line,
722                    "Edge of flight line of read point was not false!"
723                );
724                assert_eq!(
725                    0,
726                    { read.intensity },
727                    "Intensity of read point was not zero!"
728                );
729                assert_eq!(
730                    0, read.number_of_returns,
731                    "Number of returns of read point was not zero!"
732                );
733                assert_eq!(
734                    0,
735                    { read.point_source_id },
736                    "Point source ID of read point was not zero!"
737                );
738                assert_eq!(
739                    0, read.return_number,
740                    "Return number of read point was not zero!"
741                );
742                assert_eq!(
743                    0, read.scan_angle_rank,
744                    "Scan angle rank of read point was not zero!"
745                );
746                assert_eq!(
747                    0, read.scan_direction_flag,
748                    "Scan direction flag of read point was not false!"
749                );
750                assert_eq!(0, read.user_data, "User data of read point was not zero!");
751            }
752        }
753
754        Ok(())
755    }
756
757    #[test]
758    fn test_write_las_format_3() -> Result<()> {
759        let source_points = get_test_points_las_format_3();
760        let source_point_buffer = prepare_point_buffer(&source_points);
761
762        //Write, then read, then check for equality
763
764        let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
765        test_file_path.push("test_write_las_format_3.las");
766
767        defer! {
768            std::fs::remove_file(&test_file_path).expect("Removing test file failed!");
769        }
770
771        let mut las_header_builder = Builder::from((1, 4));
772        las_header_builder.point_format = Format::new(3)?;
773
774        {
775            let mut writer = LASWriter::from_path_and_header(
776                &test_file_path,
777                las_header_builder.into_header().unwrap(),
778            )?;
779            writer.write(&source_point_buffer)?;
780            writer.flush()?;
781        }
782
783        {
784            let mut reader = LASReader::from_path(&test_file_path, false)?;
785            let mut read_points_buffer =
786                VectorBuffer::new_from_layout(reader.get_default_point_layout().clone());
787            read_points_buffer.resize(source_points.len());
788            reader.read_into(&mut read_points_buffer, source_points.len())?;
789            let read_points: Vec<LasPointFormat3> = read_points_buffer.view().into_iter().collect();
790
791            assert_eq!(read_points, source_points);
792        }
793
794        Ok(())
795    }
796
797    #[test]
798    fn test_write_las_format_3_different_layout() -> Result<()> {
799        let source_points = get_test_points_custom_format();
800        let source_point_buffer = prepare_point_buffer(&source_points);
801
802        //Write, then read, then check for equality
803
804        let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
805        test_file_path.push("test_write_las_format_3_different_layout.las");
806
807        defer! {
808            std::fs::remove_file(&test_file_path).expect("Removing test file failed!");
809        }
810
811        let mut las_header_builder = Builder::from((1, 4));
812        las_header_builder.point_format = Format::new(3)?;
813
814        {
815            let mut writer = LASWriter::from_path_and_header(
816                &test_file_path,
817                las_header_builder.into_header().unwrap(),
818            )?;
819            writer.write(&source_point_buffer)?;
820            writer.flush()?;
821        }
822
823        {
824            let mut reader = LASReader::from_path(&test_file_path, false)?;
825            let mut read_points_buffer =
826                VectorBuffer::new_from_layout(reader.get_default_point_layout().clone());
827            read_points_buffer.resize(source_points.len());
828            reader.read_into(&mut read_points_buffer, source_points.len())?;
829            let read_points: Vec<LasPointFormat3> = read_points_buffer.view().into_iter().collect();
830
831            for (source, read) in source_points.iter().zip(read_points.iter()) {
832                assert_eq!(
833                    { source.position },
834                    { read.position },
835                    "Position of read point is not equal to position of source point!"
836                );
837                assert_eq!(
838                    { source.color },
839                    { read.color_rgb },
840                    "Color of read point is not equal to color of source point!"
841                );
842                assert_eq!(
843                    0, read.classification,
844                    "Classification of read point was not zero!"
845                );
846                assert_eq!(
847                    0, read.edge_of_flight_line,
848                    "Edge of flight line of read point was not false!"
849                );
850                assert_eq!(
851                    0,
852                    { read.intensity },
853                    "Intensity of read point was not zero!"
854                );
855                assert_eq!(
856                    0, read.number_of_returns,
857                    "Number of returns of read point was not zero!"
858                );
859                assert_eq!(
860                    0,
861                    { read.point_source_id },
862                    "Point source ID of read point was not zero!"
863                );
864                assert_eq!(
865                    0, read.return_number,
866                    "Return number of read point was not zero!"
867                );
868                assert_eq!(
869                    0, read.scan_angle_rank,
870                    "Scan angle rank of read point was not zero!"
871                );
872                assert_eq!(
873                    0, read.scan_direction_flag,
874                    "Scan direction flag of read point was not false!"
875                );
876                assert_eq!(0, read.user_data, "User data of read point was not zero!");
877                assert_eq!(
878                    0.0,
879                    { read.gps_time },
880                    "GPS time of read point was not zero!"
881                );
882            }
883        }
884
885        Ok(())
886    }
887
888    #[test]
889    fn test_write_las_format_4() -> Result<()> {
890        let source_points = get_test_points_las_format_4();
891        let source_point_buffer = prepare_point_buffer(&source_points);
892
893        //Write, then read, then check for equality
894
895        let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
896        test_file_path.push("test_write_las_format_4.las");
897
898        defer! {
899            std::fs::remove_file(&test_file_path).expect("Removing test file failed!");
900        }
901
902        let mut las_header_builder = Builder::from((1, 4));
903        las_header_builder.point_format = Format::new(4)?;
904
905        {
906            let mut writer = LASWriter::from_path_and_header(
907                &test_file_path,
908                las_header_builder.into_header().unwrap(),
909            )?;
910            writer.write(&source_point_buffer)?;
911            writer.flush()?;
912        }
913
914        {
915            let mut reader = LASReader::from_path(&test_file_path, false)?;
916            let mut read_points_buffer =
917                VectorBuffer::new_from_layout(reader.get_default_point_layout().clone());
918            read_points_buffer.resize(source_points.len());
919            reader.read_into(&mut read_points_buffer, source_points.len())?;
920            let read_points: Vec<LasPointFormat4> = read_points_buffer.view().into_iter().collect();
921
922            assert_eq!(read_points, source_points);
923        }
924
925        Ok(())
926    }
927
928    #[test]
929    fn test_write_las_format_4_different_layout() -> Result<()> {
930        let source_points = get_test_points_custom_format();
931        let source_point_buffer = prepare_point_buffer(&source_points);
932
933        //Write, then read, then check for equality
934
935        let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
936        test_file_path.push("test_write_las_format_4_different_layout.las");
937
938        defer! {
939            std::fs::remove_file(&test_file_path).expect("Removing test file failed!");
940        }
941
942        let mut las_header_builder = Builder::from((1, 4));
943        las_header_builder.point_format = Format::new(4)?;
944
945        {
946            let mut writer = LASWriter::from_path_and_header(
947                &test_file_path,
948                las_header_builder.into_header().unwrap(),
949            )?;
950            writer.write(&source_point_buffer)?;
951            writer.flush()?;
952        }
953
954        {
955            let mut reader = LASReader::from_path(&test_file_path, false)?;
956            let mut read_points_buffer =
957                VectorBuffer::new_from_layout(reader.get_default_point_layout().clone());
958            read_points_buffer.resize(source_points.len());
959            reader.read_into(&mut read_points_buffer, source_points.len())?;
960            let read_points: Vec<LasPointFormat4> = read_points_buffer.view().into_iter().collect();
961
962            for (source, read) in source_points.iter().zip(read_points.iter()) {
963                assert_eq!(
964                    { source.position },
965                    { read.position },
966                    "Position of read point is different than of source point!"
967                );
968                assert_eq!(
969                    0, read.classification,
970                    "Classification of read point was not zero!"
971                );
972                assert_eq!(
973                    0, read.edge_of_flight_line,
974                    "Edge of flight line of read point was not false!"
975                );
976                assert_eq!(
977                    0,
978                    { read.intensity },
979                    "Intensity of read point was not zero!"
980                );
981                assert_eq!(
982                    0, read.number_of_returns,
983                    "Number of returns of read point was not zero!"
984                );
985                assert_eq!(
986                    0,
987                    { read.point_source_id },
988                    "Point source ID of read point was not zero!"
989                );
990                assert_eq!(
991                    0, read.return_number,
992                    "Return number of read point was not zero!"
993                );
994                assert_eq!(
995                    0, read.scan_angle_rank,
996                    "Scan angle rank of read point was not zero!"
997                );
998                assert_eq!(
999                    0, read.scan_direction_flag,
1000                    "Scan direction flag of read point was not false!"
1001                );
1002                assert_eq!(0, read.user_data, "User data of read point was not zero!");
1003                assert_eq!(
1004                    0,
1005                    { read.byte_offset_to_waveform_data },
1006                    "Byte offset to waveform data of read point was not zero!"
1007                );
1008                assert_eq!(
1009                    0.0,
1010                    { read.return_point_waveform_location },
1011                    "Return point waveform location of read point was not zero!"
1012                );
1013                assert_eq!(
1014                    0, read.wave_packet_descriptor_index,
1015                    "Wave packet descriptor index of read point was not zero!"
1016                );
1017                assert_eq!(
1018                    0,
1019                    { read.waveform_packet_size },
1020                    "Waveform packet size of read point was not zero!"
1021                );
1022                assert_eq!(
1023                    { Vector3::<f32>::new(0.0, 0.0, 0.0) },
1024                    { read.waveform_parameters },
1025                    "Waveform parameters of read point were not zero!"
1026                );
1027            }
1028        }
1029
1030        Ok(())
1031    }
1032
1033    #[test]
1034    fn test_write_las_format_5() -> Result<()> {
1035        let source_points = get_test_points_las_format_5();
1036        let source_point_buffer = prepare_point_buffer(&source_points);
1037
1038        //Write, then read, then check for equality
1039
1040        let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1041        test_file_path.push("test_write_las_format_5.las");
1042
1043        defer! {
1044            std::fs::remove_file(&test_file_path).expect("Removing test file failed!");
1045        }
1046
1047        let mut las_header_builder = Builder::from((1, 4));
1048        las_header_builder.point_format = Format::new(5)?;
1049
1050        {
1051            let mut writer = LASWriter::from_path_and_header(
1052                &test_file_path,
1053                las_header_builder.into_header().unwrap(),
1054            )?;
1055            writer.write(&source_point_buffer)?;
1056            writer.flush()?;
1057        }
1058
1059        {
1060            let mut reader = LASReader::from_path(&test_file_path, false)?;
1061            let mut read_points_buffer =
1062                VectorBuffer::new_from_layout(reader.get_default_point_layout().clone());
1063            read_points_buffer.resize(source_points.len());
1064            reader.read_into(&mut read_points_buffer, source_points.len())?;
1065            let read_points: Vec<LasPointFormat5> = read_points_buffer.view().into_iter().collect();
1066
1067            assert_eq!(read_points, source_points);
1068        }
1069
1070        Ok(())
1071    }
1072
1073    #[test]
1074    fn test_write_las_format_5_different_layout() -> Result<()> {
1075        let source_points = get_test_points_custom_format();
1076        let source_point_buffer = prepare_point_buffer(&source_points);
1077
1078        //Write, then read, then check for equality
1079
1080        let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1081        test_file_path.push("test_write_las_format_5_different_layout.las");
1082
1083        defer! {
1084            std::fs::remove_file(&test_file_path).expect("Removing test file failed!");
1085        }
1086
1087        let mut las_header_builder = Builder::from((1, 4));
1088        las_header_builder.point_format = Format::new(5)?;
1089
1090        {
1091            let mut writer = LASWriter::from_path_and_header(
1092                &test_file_path,
1093                las_header_builder.into_header().unwrap(),
1094            )?;
1095            writer.write(&source_point_buffer)?;
1096            writer.flush()?;
1097        }
1098
1099        {
1100            let mut reader = LASReader::from_path(&test_file_path, false)?;
1101            let mut read_points_buffer =
1102                VectorBuffer::new_from_layout(reader.get_default_point_layout().clone());
1103            read_points_buffer.resize(source_points.len());
1104            reader.read_into(&mut read_points_buffer, source_points.len())?;
1105            let read_points: Vec<LasPointFormat5> = read_points_buffer.view().into_iter().collect();
1106
1107            for (source, read) in source_points.iter().zip(read_points.iter()) {
1108                assert_eq!(
1109                    { source.position },
1110                    { read.position },
1111                    "Position of read point is different than of source point!"
1112                );
1113                assert_eq!(
1114                    { source.color },
1115                    { read.color_rgb },
1116                    "Colors of read point are different from colors in source point!"
1117                );
1118                assert_eq!(
1119                    0, read.classification,
1120                    "Classification of read point was not zero!"
1121                );
1122                assert_eq!(
1123                    0, read.edge_of_flight_line,
1124                    "Edge of flight line of read point was not false!"
1125                );
1126                assert_eq!(
1127                    0,
1128                    { read.intensity },
1129                    "Intensity of read point was not zero!"
1130                );
1131                assert_eq!(
1132                    0, read.number_of_returns,
1133                    "Number of returns of read point was not zero!"
1134                );
1135                assert_eq!(
1136                    0,
1137                    { read.point_source_id },
1138                    "Point source ID of read point was not zero!"
1139                );
1140                assert_eq!(
1141                    0, read.return_number,
1142                    "Return number of read point was not zero!"
1143                );
1144                assert_eq!(
1145                    0, read.scan_angle_rank,
1146                    "Scan angle rank of read point was not zero!"
1147                );
1148                assert_eq!(
1149                    0, read.scan_direction_flag,
1150                    "Scan direction flag of read point was not false!"
1151                );
1152                assert_eq!(0, read.user_data, "User data of read point was not zero!");
1153                assert_eq!(
1154                    0.0,
1155                    { read.gps_time },
1156                    "GPS time of read point was not zero!"
1157                );
1158                assert_eq!(
1159                    0,
1160                    { read.byte_offset_to_waveform_data },
1161                    "Byte offset to waveform data of read point was not zero!"
1162                );
1163                assert_eq!(
1164                    0.0,
1165                    { read.return_point_waveform_location },
1166                    "Return point waveform location of read point was not zero!"
1167                );
1168                assert_eq!(
1169                    0, read.wave_packet_descriptor_index,
1170                    "Wave packet descriptor index of read point was not zero!"
1171                );
1172                assert_eq!(
1173                    0,
1174                    { read.waveform_packet_size },
1175                    "Waveform packet size of read point was not zero!"
1176                );
1177                assert_eq!(
1178                    { Vector3::<f32>::new(0.0, 0.0, 0.0) },
1179                    { read.waveform_parameters },
1180                    "Waveform parameters of read point were not zero!"
1181                );
1182            }
1183        }
1184
1185        Ok(())
1186    }
1187
1188    #[test]
1189    fn test_las_writer_into_inner() -> Result<()> {
1190        let source_points = get_test_points_las_format_0();
1191        let source_point_buffer = prepare_point_buffer(&source_points);
1192
1193        let mut cursor = Cursor::new(Vec::<u8>::new());
1194
1195        let mut las_header_builder = Builder::from((1, 4));
1196        las_header_builder.point_format = Format::new(0)?;
1197
1198        {
1199            let mut writer = LASWriter::from_writer_and_header(
1200                cursor,
1201                las_header_builder.into_header().unwrap(),
1202                false,
1203            )?;
1204            writer.write(&source_point_buffer)?;
1205
1206            cursor = writer.into_inner()?;
1207        }
1208
1209        // Assert that some bytes have been written. We could assert the exact number, but that might depend on implementation details
1210        // like padding that we don't really care about
1211        let vec = cursor.into_inner();
1212        assert!(!vec.is_empty());
1213
1214        Ok(())
1215    }
1216
1217    #[test]
1218    fn test_laz_writer_into_inner() -> Result<()> {
1219        let source_points = get_test_points_las_format_0();
1220        let source_point_buffer = prepare_point_buffer(&source_points);
1221
1222        let mut cursor = Cursor::new(Vec::<u8>::new());
1223
1224        let mut las_header_builder = Builder::from((1, 4));
1225        las_header_builder.point_format = Format::new(0)?;
1226
1227        {
1228            let mut writer = LASWriter::from_writer_and_header(
1229                cursor,
1230                las_header_builder.into_header().unwrap(),
1231                true,
1232            )?;
1233            writer.write(&source_point_buffer)?;
1234
1235            cursor = writer.into_inner()?;
1236        }
1237
1238        // Assert that some bytes have been written. We could assert the exact number, but that might depend on implementation details
1239        // like padding that we don't really care about
1240        let vec = cursor.into_inner();
1241        assert!(!vec.is_empty());
1242
1243        Ok(())
1244    }
1245}