las 0.9.11

Reads and writes point clouds stored in the ASPRS las file format.
Documentation
//! Roundtrip (write-read) tests for supported LAS versions and attributes.

extern crate chrono;
extern crate las;
extern crate uuid;

use las::{Builder, Point, Reader, Writer};
use std::io::Cursor;

pub fn roundtrip(builder: Builder, point: &Point, should_succeed: bool) {
    let header = if should_succeed {
        builder.into_header().unwrap()
    } else {
        assert!(builder.into_header().is_err());
        return;
    };
    let mut writer = Writer::new(Cursor::new(Vec::new()), header).unwrap();
    writer.write_point(point.clone()).unwrap();
    let header = writer.header().clone();
    let mut reader = Reader::new(writer.into_inner().unwrap()).unwrap();
    assert_eq!(*point, reader.read_point().unwrap().unwrap());
    assert!(reader.read_point().unwrap().is_none());
    assert_eq!(header, *reader.header());
}

macro_rules! roundtrip_point {
    ($name:ident, $modify_point:expr_2021) => {
        roundtrip_point!($name, $modify_point, 0);
    };
    ($name:ident, $modify_point:expr_2021, $min_version_minor:expr_2021) => {
        roundtrip_point!($name, $modify_point, $min_version_minor, |_f| {});
    };
    ($name:ident, $modify_point:expr_2021, $min_version_minor:expr_2021, $modify_point_format:expr_2021) => {
        #[test]
        fn $name() {
            use las::{point::Format, Builder, Point, Version};

            let version = super::version();
            let should_succeed = version >= Version::new(1, $min_version_minor);
            let mut point_format = Format::default();
            $modify_point_format(&mut point_format);
            let mut point = Point::default();
            $modify_point(&mut point);
            let mut builder = Builder::from(version);
            builder.point_format = point_format;
            crate::roundtrip(builder, &point, should_succeed);
        }
    };
}

macro_rules! roundtrip_builder {
    ($name:ident, $modify_builder:expr_2021) => {
        roundtrip_builder!($name, $modify_builder, 0);
    };
    ($name:ident, $modify_builder:expr_2021, $min_version_minor:expr_2021) => {
        #[test]
        fn $name() {
            use las::{Builder, Point, Version};

            let version = super::version();
            let should_succeed = version >= Version::new(1, $min_version_minor);
            let mut builder = Builder::from(version);
            $modify_builder(&mut builder);
            crate::roundtrip(builder, &Point::default(), should_succeed);
        }
    };
}

macro_rules! version {
    ($name:ident, $major:expr_2021, $minor:expr_2021) => {
        mod $name {
            use las::Version;

            fn version() -> Version {
                Version::new($major, $minor)
            }

            mod point {
                use las::{
                    point::{Classification, ScanDirection},
                    Color,
                };

                roundtrip_point!(xyz, |p: &mut Point| {
                    p.x = 1.;
                    p.y = 2.;
                    p.z = 3.;
                });
                roundtrip_point!(intensity, |p: &mut Point| p.intensity = 42);
                roundtrip_point!(return_number, |p: &mut Point| p.return_number = 2);
                roundtrip_point!(number_of_returns, |p: &mut Point| p.number_of_returns = 2);
                roundtrip_point!(scan_direction, |p: &mut Point| p.scan_direction =
                    ScanDirection::LeftToRight);
                roundtrip_point!(is_edge_of_flight_line, |p: &mut Point| p
                    .is_edge_of_flight_line =
                    true);
                roundtrip_point!(classification, |p: &mut Point| p.classification =
                    Classification::Ground);
                roundtrip_point!(is_synthetic, |p: &mut Point| p.is_synthetic = true);
                roundtrip_point!(is_key_point, |p: &mut Point| p.is_key_point = true);
                roundtrip_point!(is_withheld, |p: &mut Point| p.is_withheld = true);
                roundtrip_point!(is_overlap, |p: &mut Point| {
                    p.classification = Classification::Unclassified;
                    p.is_overlap = true;
                });
                roundtrip_point!(
                    scanner_channel,
                    |p: &mut Point| {
                        p.scanner_channel = 1;
                        p.gps_time = Some(42.);
                    },
                    4,
                    |f: &mut Format| f.extend()
                );
                roundtrip_point!(scan_angle_rank, |p: &mut Point| p.scan_angle = 3.);
                roundtrip_point!(user_data, |p: &mut Point| p.user_data = 42);
                roundtrip_point!(point_source_id, |p: &mut Point| p.point_source_id = 42);
                roundtrip_point!(
                    gps_time,
                    |p: &mut Point| p.gps_time = Some(42.),
                    0,
                    |f: &mut Format| f.has_gps_time = true
                );
                roundtrip_point!(
                    color,
                    |p: &mut Point| p.color = Some(Color {
                        red: 1,
                        green: 2,
                        blue: 3
                    }),
                    2,
                    |f: &mut Format| f.has_color = true
                );
                // TODO waveform
                roundtrip_point!(
                    nir,
                    |p: &mut Point| {
                        p.color = Some(Color {
                            red: 1,
                            green: 2,
                            blue: 3,
                        });
                        p.nir = Some(42);
                        p.gps_time = Some(42.);
                    },
                    4,
                    |f: &mut Format| {
                        f.extend();
                        f.has_color = true;
                        f.has_nir = true;
                    }
                );
                roundtrip_point!(
                    extra_bytes,
                    |p: &mut Point| p.extra_bytes = vec![42],
                    0,
                    |f: &mut Format| f.extra_bytes = 1
                );
            }

            mod builder {
                use chrono::NaiveDate;
                use las::{GpsTimeType, Vlr};
                use uuid::Uuid;

                roundtrip_builder!(file_source_id, |b: &mut Builder| b.file_source_id = 42, 1);
                roundtrip_builder!(
                    gps_time_type,
                    |b: &mut Builder| b.gps_time_type = GpsTimeType::Standard,
                    2
                );
                roundtrip_builder!(
                    has_synthetic_return_numbers,
                    |b: &mut Builder| b.has_synthetic_return_numbers = true,
                    3
                );
                roundtrip_builder!(guid, |b: &mut Builder| b.guid = Uuid::from_bytes([42; 16]));
                roundtrip_builder!(system_identifier, |b: &mut Builder| b.system_identifier =
                    "roundtrip test".to_string());
                roundtrip_builder!(generating_software, |b: &mut Builder| b
                    .generating_software =
                    "roundtrip test".to_string());
                roundtrip_builder!(date, |b: &mut Builder| b.date =
                    NaiveDate::from_ymd_opt(2017, 10, 30));
                roundtrip_builder!(transforms, |b: &mut Builder| {
                    use las::{Transform, Vector};

                    let transform = Transform {
                        scale: 0.1,
                        offset: -1.,
                    };
                    b.transforms = Vector {
                        x: transform,
                        y: transform,
                        z: transform,
                    };
                });
                roundtrip_builder!(vlrs, |b: &mut Builder| b.vlrs.push(Default::default()));
                roundtrip_builder!(
                    evlrs,
                    |b: &mut Builder| {
                        let mut vlr = Vlr::default();
                        vlr.data = vec![42; u16::MAX as usize + 1];
                        b.evlrs.push(vlr);
                    },
                    4
                );
                roundtrip_builder!(padding, |b: &mut Builder| b.padding =
                    b"You probably shouldn't do this".to_vec());
                roundtrip_builder!(vlr_padding, |b: &mut Builder| b.vlr_padding =
                    b"You probably shouldn't do this either".to_vec());
                roundtrip_builder!(
                    point_padding,
                    |b: &mut Builder| {
                        b.point_padding = vec![42];
                        b.evlrs.push(Vlr::default());
                    },
                    4
                );
            }
        }
    };
}

version!(las_1_0, 1, 0);
version!(las_1_1, 1, 1);
version!(las_1_2, 1, 2);
version!(las_1_3, 1, 3);
version!(las_1_4, 1, 4);