las 0.8.0

Read and write point clouds stored in the ASPRS las file format.
Documentation
//! Defines raw las points and some enums required to handle the various point formats.

use crate::point::{Classification, Error, Format, ScanDirection};
use std::io::{Read, Write};
use crate::{Color, Result};

const SCAN_ANGLE_SCALE_FACTOR: f32 = 0.006;
const OVERLAP_CLASSIFICATION_CODE: u8 = 12;

/// A raw point.
///
/// The documentation for struct members is taken directly from the las 1.4 spec.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Point {
    /// The X, Y, and Z values are stored as long integers.
    ///
    /// The X, Y, and Z values are used in conjunction with the scale values and the offset values
    /// to determine the coordinate for each point as described in the Public Header Block section.
    pub x: i32,
    #[allow(missing_docs)]
    pub y: i32,
    #[allow(missing_docs)]
    pub z: i32,

    /// The intensity value is the integer representation of the pulse return magnitude.
    ///
    /// This value is optional and system specific. However, it should always be included if
    /// available. Intensity, when included, is always normalized to a 16 bit, unsigned value by
    /// multiplying the value by 65,536/(intensity dynamic range of the sensor). For example, if
    /// the dynamic range of the sensor is 10 bits, the scaling value would be (65,536/1,024). If
    /// intensity is not included, this value must be set to zero. This normalization is required
    /// to ensure that data from different sensors can be correctly merged.
    pub intensity: u16,

    /// Flags can either be one or two bytes.
    ///
    /// # One byte
    ///
    /// Please note that the following four fields (Return Number, Number of Returns, Scan
    /// Direction Flag and Edge of Flight Line) are bit fields within a single byte.
    ///
    /// ## Return Number
    ///
    /// The Return Number is the pulse return number for a given output pulse. A given output laser
    /// pulse can have many returns, and they must be marked in sequence of return. The first
    /// return will have a Return Number of one, the second a Return Number of two, and so on up to
    /// five returns.
    ///
    /// ## Number of Returns (given pulse)
    ///
    /// The Number of Returns is the total number of returns for a given pulse. For example, a
    /// laser data point may be return two (Return Number) within a total number of five returns.
    ///
    /// ## Scan Direction Flag
    ///
    /// The Scan Direction Flag denotes the direction at which the scanner mirror was traveling at
    /// the time of the output pulse. A bit value of 1 is a positive scan direction, and a bit
    /// value of 0 is a negative scan direction (where positive scan direction is a scan moving
    /// from the left side of the in-track direction to the right side and negative the opposite).
    ///
    /// ## Edge of Flight Line
    ///
    /// The Edge of Flight Line data bit has a value of 1 only when the point is at the end of a
    /// scan. It is the last point on a given scan line before it changes direction.
    ///
    /// # Two bytes
    ///
    /// Note that the following five fields (Return Number, Number of Returns, Classification
    /// Flags, Scan Direction Flag and Edge of Flight Line) are bit fields, encoded into two bytes.
    /// ## Return Number
    ///
    /// The Return Number is the pulse return number for a given output pulse. A given output laser
    /// pulse can have many returns, and they must be marked in sequence of return. The first
    /// return will have a Return Number of one, the second a Return Number of two, and so on up to
    /// fifteen returns. The Return Number must be between 1 and the Number of Returns, inclusive.
    ///
    /// ## Number of Returns (given pulse)
    ///
    /// The Number of Returns is the total number of returns for a given pulse. For example, a
    /// laser data point may be return two (Return Number) within a total number of up to fifteen
    /// returns.
    ///
    /// ## Classification Flags
    ///
    /// Classification flags are used to indicate special characteristics associated with the
    /// point. The bit definitions are:
    ///
    /// | Bit | Field name | Description |
    /// | --- | ---------- | ----------- |
    /// | 0 | Synthetic | If set then this point was created by a technique other than LIDAR collection such as digitized from a photogrammetric stereo model or by traversing a waveform. |
    /// | 1 | Key-point | If set, this point is considered to be a model key-point and thus generally should not be withheld in a thinning algorithm. |
    /// | 2 | Withheld | If set, this point should not be included in processing (synonymous with Deleted). |
    /// | 3 | Overlap | If set, this point is within the overlap region of two or more swaths or takes. Setting this bit is not mandatory (unless, of course, it is mandated by a particular delivery specification) but allows Classification of overlap points to be preserved. |
    ///
    /// Note that these bits are treated as flags and can be set or cleared in any combination. For
    /// example, a point with bits 0 and 1 both set to one and the Classification field set to 2
    /// would be a ground point that had been synthetically collected and marked as a model
    /// key-point.
    ///
    /// ## Scanner Channel
    ///
    /// Scanner Channel is used to indicate the channel (scanner head) of a multi- channel system.
    /// Channel 0 is used for single scanner systems. Up to four channels are supported (0-3).
    ///
    /// ## Scan Direction Flag
    ///
    /// The Scan Direction Flag denotes the direction at which the scanner mirror was traveling at
    /// the time of the output pulse. A bit value of 1 is a positive scan direction, and a bit
    /// value of 0 is a negative scan direction (where positive scan direction is a scan moving
    /// from the left side of the in-track direction to the right side and negative the opposite).
    ///
    /// ## Edge of Flight Line
    ///
    /// The Edge of Flight Line data bit has a value of 1 only when the point is at the end of a
    /// scan. It is the last point on a given scan line before it changes direction or the mirror
    /// facet changes. Note that this field has no meaning for 360° Field of View scanners (such as
    /// Mobile LIDAR scanners) and should not be set.
    ///
    /// # Classification
    ///
    /// This field also holds the “class” attributes of a point.
    ///
    /// If a point has never been classified, this byte must be set to zero. In some point formats,
    /// the format for classification is a bit encoded field with the lower five bits used for the
    /// class and the three high bits used for flags. In others, the whole byte is used for
    /// classes.
    ///
    /// # Bit field encoding for point data record types 0 to 5
    ///
    /// | Bit | Field name | Description |
    /// | --- | ---------- | ----------- |
    /// | 0:4 | Classification | Standard ASPRS classification from 0 - 31 as defined in the classification table for legacy point formats |
    /// | 5 | Synthetic | If set then this point was created by a technique other than LIDAR collection such as digitized from a photogrammetric stereo model or by traversing a waveform. |
    /// | 6 | Key-point | If set, this point is considered to be a model key-point and thus generally should not be withheld in a thinning algorithm. |
    /// | 7 | Withheld | If set, this point should not be included in processing (synonymous with Deleted). |
    ///
    /// # ASPRS standard LiDAR point classes for point data record types 0 to 5
    ///
    /// | Classification value | Meaning |
    /// | -------------------- | ------- |
    /// | 0 | Created, never classified |
    /// | 1 | Unclassified |
    /// | 2 | Ground |
    /// | 3 | Low vegetation |
    /// | 4 | Medium vegetation |
    /// | 5 | High vegetation |
    /// | 6 | Building |
    /// | 7 | Low point (noise) |
    /// | 8 | Model key-point (mass point) |
    /// | 9 | Water |
    /// | 10 | Reserved |
    /// | 11 | Reserved |
    /// | 12 | Overlap points |
    /// | 13-31 | Reserved |
    ///
    /// # ASPRS standard LiDAR point classes for point data record types 6 to 10
    ///
    /// | Classification value | Meaning |
    /// | -------------------- | ------- |
    /// | 0 | Created, never classified |
    /// | 1 | Unclassified |
    /// | 2 | Ground |
    /// | 3 | Low vegetation |
    /// | 4 | Medium vegetation |
    /// | 5 | High vegetation |
    /// | 6 | Building |
    /// | 7 | Low point (noise) |
    /// | 8 | Model key-point (mass point) |
    /// | 9 | Water |
    /// | 10 | Rail |
    /// | 11 | Road surface |
    /// | 12 | Overlap points |
    /// | 13 | Wire - guard (shield) |
    /// | 14 | Wire - conductor (phase) |
    /// | 15 | Transmission tower |
    /// | 16 | Wire-structure connector (e.g. insulator) |
    /// | 17 | Bridge deck |
    /// | 17 | High noise |
    /// | 19-63 | Reserved |
    /// | 64-255 | Userdefinable |
    pub flags: Flags,

    /// The scan angle can be stored as rank or scaled.
    ///
    /// # Rank
    ///
    /// The Scan Angle Rank is a signed one-byte number with a valid range from - 90 to +90.
    ///
    /// The Scan Angle Rank is the angle (rounded to the nearest integer in the absolute value
    /// sense) at which the laser point was output from the laser system including the roll of the
    /// aircraft. The scan angle is within 1 degree of accuracy from +90 to –90 degrees. The scan
    /// angle is an angle based on 0 degrees being nadir, and –90 degrees to the left side of the
    /// aircraft in the direction of flight.
    ///
    /// # Scaled
    ///
    /// The Scan Angle is a signed short that represents the rotational position of the emitted
    /// laser pulse with respect to the vertical of the coordinate system of the data. Down in the
    /// data coordinate system is the 0.0 position. Each increment represents 0.006 degrees.
    /// Counter- Clockwise rotation, as viewed from the rear of the sensor, facing in the
    /// along-track (positive trajectory) direction, is positive. The maximum value in the positive
    /// sense is 30,000 (180 degrees which is up in the coordinate system of the data). The maximum
    /// value in the negative direction is -30.000 which is also directly up.
    pub scan_angle: ScanAngle,

    /// This field may be used at the user’s discretion.
    pub user_data: u8,

    /// This value indicates the file from which this point originated.
    ///
    /// Valid values for this field are 1 to 65,535 inclusive with zero being used for a special
    /// case discussed below. The numerical value corresponds to the File Source ID from which this
    /// point originated. Zero is reserved as a convenience to system implementers. A Point Source
    /// ID of zero implies that this point originated in this file. This implies that processing
    /// software should set the Point Source ID equal to the File Source ID of the file containing
    /// this point at some time during processing.
    pub point_source_id: u16,

    /// The GPS Time is the double floating point time tag value at which the point was acquired.
    ///
    /// It is GPS Week Time if the Global Encoding low bit is clear and Adjusted Standard GPS Time
    /// if the Global Encoding low bit is set (see Global Encoding in the Public Header Block
    /// description).
    pub gps_time: Option<f64>,

    /// The red, green, and blue image channels associated with this point.
    ///
    /// The Red, Green, Blue values should always be normalized to 16 bit values. For example, when
    /// encoding an 8 bit per channel pixel, multiply each channel value by 256 prior to storage in
    /// these fields. This normalization allows color values from different camera bit depths to be
    /// accurately merged.
    pub color: Option<Color>,

    #[allow(missing_docs)]
    pub waveform: Option<Waveform>,

    /// The NIR (near infrared) channel value associated with this point.
    pub nir: Option<u16>,

    #[allow(missing_docs)]
    pub extra_bytes: Vec<u8>,
}

#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[allow(missing_docs)]
pub struct Waveform {
    /// This value plus 99 is the Record ID of the Waveform Packet Descriptor and indicates the
    /// User Defined Record that describes the waveform packet associated with this LIDAR point.
    ///
    /// Up to 255 different User Defined Records which describe the waveform packet are supported.
    /// A value of zero indicates that there is no waveform data associated with this LIDAR point
    /// record.
    pub wave_packet_descriptor_index: u8,

    /// The waveform packet data are stored in the LAS file in an Extended Variable Length Record
    /// or in an auxiliary WPD file.
    ///
    /// The Byte Offset represents the location of the start of this LIDAR points’ waveform packet
    /// within the waveform data variable length record (or external file) relative to the
    /// beginning of the Waveform Packet Data header. The absolute location of the beginning of
    /// this waveform packet relative to the beginning of the file is given by:
    ///
    /// > Start of Waveform Data Packet Record + Byte offset to Waveform Packet Data
    ///
    /// for waveform packets stored within the LAS file and
    ///
    /// > Byte offset to Waveform Packet Data
    ///
    /// for data stored in an auxiliary file
    pub byte_offset_to_waveform_data: u64,

    /// The size, in bytes, of the waveform packet associated with this return.
    ///
    /// Note that each waveform can be of a different size (even those with the same Waveform
    /// Packet Descriptor index) due to packet compression. Also note that waveform packets can be
    /// located only via the Byte offset to Waveform Packet Data value since there is no
    /// requirement that records be stored sequentially.
    pub waveform_packet_size_in_bytes: u32,

    /// The offset in picoseconds (10-12) from the first digitized value to the location within the
    /// waveform packet that the associated return pulse was detected.
    pub return_point_waveform_location: f32,

    /// These parameters define a parametric line equation for extrapolating points along the associated waveform.
    ///
    /// The position along the wave is given by:
    ///
    /// X = X0 + X(t)
    ///
    /// Y = Y0 + Y(t)
    ///
    /// Z = Z0 + Z(t)
    ///
    /// where X, Y and Z are the spatial position of the derived point, X0, Y0, Z0 are the position
    /// of the “anchor” point (the X, Y, Z locations from this point’s data record) and t is the
    /// time, in picoseconds, relative to the anchor point (i.e. t = zero at the anchor point). The
    /// units of X, Y and Z are the units of the coordinate systems of the LAS data. If the
    /// coordinate system is geographic, the horizontal units are decimal degrees and the vertical
    /// units are meters.
    pub x_t: f32,
    #[allow(missing_docs)]
    pub y_t: f32,
    #[allow(missing_docs)]
    pub z_t: f32,
}

/// Scan angle can be stored as a i8 (rank) or i16 (scaled).
#[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub enum ScanAngle {
    Rank(i8),
    Scaled(i16),
}

/// These flags hold information about point classification, return number, and more.
///
/// In point formats zero through five, two bytes are used to hold all of the information. Point
/// formats six through ten use an extra byte, to enable more return numbers, more classifications,
/// and more.
///
/// This structure captures those alternatives and provides an API to convert between the two
/// types. Two-byte flags can always be transformed to three-byte flags, but going from three-bytes
/// to two-bytes can fail if information would be lost.
///
/// ```
/// use las::raw::point::Flags;
/// let two_byte = Flags::TwoByte(0b00001001, 1);
/// assert_eq!(Flags::ThreeByte(0b00010001, 0, 1), two_byte.into());
///
/// // Two-byte flags can't handle this large of return numbers.
/// let three_byte = Flags::ThreeByte(0b10001000, 0, 1);
/// assert!(three_byte.to_two_bytes().is_err());
/// ```
#[derive(Clone, Copy, Debug)]
pub enum Flags {
    /// Two byte flags, used for point formats zero through five.
    TwoByte(u8, u8),
    /// Three byte flags, used for point formats six through ten.
    ThreeByte(u8, u8, u8),
}

impl Point {
    /// Reads a raw point.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::io::{Seek, SeekFrom};
    /// use std::fs::File;
    /// use las::raw::Point;
    /// use las::point::Format;
    /// let mut file = File::open("tests/data/autzen.las").unwrap();
    /// file.seek(SeekFrom::Start(1994)).unwrap();
    /// let point = Point::read_from(file, &Format::new(1).unwrap()).unwrap();
    /// ```
    #[allow(clippy::field_reassign_with_default)]
    pub fn read_from<R: Read>(mut read: R, format: &Format) -> Result<Point> {
        use byteorder::{LittleEndian, ReadBytesExt};
        use crate::utils;

        let mut point = Point::default();
        point.x = read.read_i32::<LittleEndian>()?;
        point.y = read.read_i32::<LittleEndian>()?;
        point.z = read.read_i32::<LittleEndian>()?;
        point.intensity = read.read_u16::<LittleEndian>()?;
        point.flags = if format.is_extended {
            Flags::ThreeByte(read.read_u8()?, read.read_u8()?, read.read_u8()?)
        } else {
            Flags::TwoByte(read.read_u8()?, read.read_u8()?)
        };
        if format.is_extended {
            point.user_data = read.read_u8()?;
            point.scan_angle = ScanAngle::Scaled(read.read_i16::<LittleEndian>()?);
        } else {
            point.scan_angle = ScanAngle::Rank(read.read_i8()?);
            point.user_data = read.read_u8()?;
        };
        point.point_source_id = read.read_u16::<LittleEndian>()?;
        point.gps_time = if format.has_gps_time {
            utils::some_or_none_if_zero(read.read_f64::<LittleEndian>()?)
        } else {
            None
        };
        point.color = if format.has_color {
            let red = read.read_u16::<LittleEndian>()?;
            let green = read.read_u16::<LittleEndian>()?;
            let blue = read.read_u16::<LittleEndian>()?;
            Some(Color::new(red, green, blue))
        } else {
            None
        };
        point.waveform = if format.has_waveform {
            Some(Waveform::read_from(&mut read)?)
        } else {
            None
        };
        point.nir = if format.has_nir {
            utils::some_or_none_if_zero(read.read_u16::<LittleEndian>()?)
        } else {
            None
        };
        point.extra_bytes.resize(format.extra_bytes as usize, 0);
        read.read_exact(&mut point.extra_bytes)?;
        Ok(point)
    }

    /// Writes a raw pont.
    ///
    /// # Examples
    ///
    /// `Write` implements `WriteRawPoint`.
    ///
    /// ```
    /// use std::io::Cursor;
    /// use las::raw::Point;
    /// use las::point::Format;
    /// let mut cursor = Cursor::new(Vec::new());
    /// let point = Point::default();
    /// point.write_to(cursor, &Format::default()).unwrap();
    /// ```
    pub fn write_to<W: Write>(&self, mut write: W, format: &Format) -> Result<()> {
        use byteorder::{LittleEndian, WriteBytesExt};
        assert_eq!(format.extra_bytes as usize, self.extra_bytes.len());

        write.write_i32::<LittleEndian>(self.x)?;
        write.write_i32::<LittleEndian>(self.y)?;
        write.write_i32::<LittleEndian>(self.z)?;
        write.write_u16::<LittleEndian>(self.intensity)?;
        if format.is_extended {
            let (a, b, c) = self.flags.into();
            write.write_u8(a)?;
            write.write_u8(b)?;
            write.write_u8(c)?;
        } else {
            let (a, b) = self.flags.to_two_bytes()?;
            write.write_u8(a)?;
            write.write_u8(b)?;
        }
        if format.is_extended {
            write.write_u8(self.user_data)?;
            write.write_i16::<LittleEndian>(self.scan_angle.into())?;
        } else {
            write.write_i8(self.scan_angle.into())?;
            write.write_u8(self.user_data)?;
        }
        write.write_u16::<LittleEndian>(self.point_source_id)?;
        if format.has_gps_time {
            write.write_f64::<LittleEndian>(self.gps_time.unwrap_or(0.0))?;
        }
        if format.has_color {
            let color = self.color.unwrap_or_default();
            write.write_u16::<LittleEndian>(color.red)?;
            write.write_u16::<LittleEndian>(color.green)?;
            write.write_u16::<LittleEndian>(color.blue)?;
        }
        if format.has_nir {
            write.write_u16::<LittleEndian>(self.nir.unwrap_or(0))?;
        }
        if format.has_waveform {
            self.waveform.unwrap_or_default().write_to(&mut write)?;
        }
        write.write_all(&self.extra_bytes)?;
        Ok(())
    }
}

impl Waveform {
    fn read_from<R: Read>(mut read: R) -> Result<Waveform> {
        use byteorder::{LittleEndian, ReadBytesExt};
        Ok(Waveform {
            wave_packet_descriptor_index: read.read_u8()?,
            byte_offset_to_waveform_data: read.read_u64::<LittleEndian>()?,
            waveform_packet_size_in_bytes: read.read_u32::<LittleEndian>()?,
            return_point_waveform_location: read.read_f32::<LittleEndian>()?,
            x_t: read.read_f32::<LittleEndian>()?,
            y_t: read.read_f32::<LittleEndian>()?,
            z_t: read.read_f32::<LittleEndian>()?,
        })
    }

    fn write_to<W: Write>(&self, mut write: W) -> Result<()> {
        use byteorder::{LittleEndian, WriteBytesExt};
        write.write_u8(self.wave_packet_descriptor_index)?;
        write.write_u64::<LittleEndian>(self.byte_offset_to_waveform_data)?;
        write.write_u32::<LittleEndian>(self.waveform_packet_size_in_bytes)?;
        write.write_f32::<LittleEndian>(self.return_point_waveform_location)?;
        write.write_f32::<LittleEndian>(self.x_t)?;
        write.write_f32::<LittleEndian>(self.y_t)?;
        write.write_f32::<LittleEndian>(self.z_t)?;
        Ok(())
    }
}

impl Flags {
    /// Returns the return number.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::raw::point::Flags;
    /// assert_eq!(1, Flags::TwoByte(1, 0).return_number());
    /// assert_eq!(1, Flags::ThreeByte(1, 0, 0).return_number());
    /// ```
    pub fn return_number(&self) -> u8 {
        match *self {
            Flags::TwoByte(a, _) => a & 7,
            Flags::ThreeByte(a, _, _) => a & 15,
        }
    }

    /// Returns the number of returns.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::raw::point::Flags;
    /// assert_eq!(1, Flags::TwoByte(8, 0).number_of_returns());
    /// assert_eq!(1, Flags::ThreeByte(16, 0, 0).number_of_returns());
    /// ```
    pub fn number_of_returns(&self) -> u8 {
        match *self {
            Flags::TwoByte(a, _) => a >> 3 & 7,
            Flags::ThreeByte(a, _, _) => a >> 4 & 15,
        }
    }

    /// Returns the scan direction from these flags.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::raw::point::Flags;
    /// use las::point::ScanDirection;
    /// assert_eq!(ScanDirection::LeftToRight, Flags::TwoByte(0b01000000, 0).scan_direction());
    /// assert_eq!(ScanDirection::LeftToRight, Flags::ThreeByte(0, 0b01000000, 0).scan_direction());
    /// ```
    pub fn scan_direction(&self) -> ScanDirection {
        let n = match *self {
            Flags::TwoByte(a, _) => a,
            Flags::ThreeByte(_, b, _) => b,
        };
        if (n >> 6) & 1 == 1 {
            ScanDirection::LeftToRight
        } else {
            ScanDirection::RightToLeft
        }
    }

    /// Returns whether this point is synthetic.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::raw::point::Flags;
    /// assert!(Flags::TwoByte(0, 0b00100000).is_synthetic());
    /// assert!(Flags::ThreeByte(0, 1, 0).is_synthetic());
    /// ```
    pub fn is_synthetic(&self) -> bool {
        match *self {
            Flags::TwoByte(_, b) => (b >> 5) & 1 == 1,
            Flags::ThreeByte(_, b, _) => b & 1 == 1,
        }
    }

    /// Returns whether this point is a key point.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::raw::point::Flags;
    /// assert!(Flags::TwoByte(0, 0b01000000).is_key_point());
    /// assert!(Flags::ThreeByte(0, 2, 0).is_key_point());
    /// ```
    pub fn is_key_point(&self) -> bool {
        match *self {
            Flags::TwoByte(_, b) => (b >> 6) & 1 == 1,
            Flags::ThreeByte(_, b, _) => b & 2 == 2,
        }
    }

    /// Returns whether this point is withheld.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::raw::point::Flags;
    /// assert!(Flags::TwoByte(0, 0b10000000).is_withheld());
    /// assert!(Flags::ThreeByte(0, 4, 0).is_withheld());
    /// ```
    pub fn is_withheld(&self) -> bool {
        match *self {
            Flags::TwoByte(_, b) => (b >> 7) & 1 == 1,
            Flags::ThreeByte(_, b, _) => b & 4 == 4,
        }
    }

    /// Returns whether this point is overlap.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::raw::point::Flags;
    /// assert!(Flags::TwoByte(0, 12).is_overlap());
    /// assert!(Flags::ThreeByte(0, 8, 0).is_overlap());
    /// ```
    pub fn is_overlap(&self) -> bool {
        match *self {
            Flags::TwoByte(_, b) => b & 0b1111 == OVERLAP_CLASSIFICATION_CODE,
            Flags::ThreeByte(_, b, _) => b & 8 == 8,
        }
    }

    /// Returns the scanner channel.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::raw::point::Flags;
    /// assert_eq!(0, Flags::TwoByte(0, 0).scanner_channel());
    /// assert_eq!(3, Flags::ThreeByte(0, 0b00110000, 0).scanner_channel());
    /// ```
    pub fn scanner_channel(&self) -> u8 {
        match *self {
            Flags::TwoByte(_, _) => 0,
            Flags::ThreeByte(_, b, _) => (b >> 4) & 3,
        }
    }

    /// Is this point the edge of a flight line?
    ///
    /// # Examples
    ///
    /// ```
    /// use las::raw::point::Flags;
    /// assert!(Flags::TwoByte(128, 0).is_edge_of_flight_line());
    /// assert!(Flags::ThreeByte(0, 128, 0).is_edge_of_flight_line());
    /// ```
    pub fn is_edge_of_flight_line(&self) -> bool {
        let n = match *self {
            Flags::TwoByte(a, _) => a,
            Flags::ThreeByte(_, b, _) => b,
        };
        (n >> 7) == 1
    }

    /// Converts these flags into two bytes.
    ///
    /// If these are two byte flags, no problem. However, if these are three byte flags,
    /// information could be lost — in that case, we error.
    ///
    /// # Example
    ///
    /// ```
    /// use las::raw::point::Flags;
    /// assert_eq!((1, 2), Flags::TwoByte(1, 2).to_two_bytes().unwrap());
    /// assert!(Flags::ThreeByte(0b00001000, 0, 0).to_two_bytes().is_err());
    /// ```
    pub fn to_two_bytes(&self) -> Result<(u8, u8)> {
        match *self {
            Flags::TwoByte(a, b) => Ok((a, b)),
            Flags::ThreeByte(_, _, c) => {
                if self.return_number() > 7 {
                    Err(Error::ReturnNumber {
                        return_number: self.return_number(),
                        version: None,
                    }
                    .into())
                } else if self.number_of_returns() > 7 {
                    Err(Error::ReturnNumber {
                        return_number: self.number_of_returns(),
                        version: None,
                    }
                    .into())
                } else if c > 31 {
                    Err(Error::Classification(c).into())
                } else if self.scanner_channel() > 0 {
                    Err(Error::ScannerChannel(self.scanner_channel()).into())
                } else {
                    let mut a = (self.number_of_returns() << 3) + self.return_number();
                    if self.scan_direction() == ScanDirection::LeftToRight {
                        a += 64;
                    }
                    if self.is_edge_of_flight_line() {
                        a += 128;
                    }
                    let mut b = if self.is_overlap() {
                        OVERLAP_CLASSIFICATION_CODE
                    } else {
                        c
                    };
                    if self.is_synthetic() {
                        b += 32;
                    }
                    if self.is_key_point() {
                        b += 64;
                    }
                    if self.is_withheld() {
                        b += 128;
                    }
                    Ok((a, b))
                }
            }
        }
    }

    /// Converts these flags to a classification.
    ///
    /// Throws an error of the classifiction is 12 (overlap points), because we don't have an
    /// overlap points class in this library. See the `las::point::Classification` documentation
    /// for more information.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::raw::point::Flags;
    /// use las::point::Classification;
    /// assert_eq!(Classification::Ground, Flags::TwoByte(0, 2).to_classification().unwrap());
    /// assert_eq!(Classification::Ground, Flags::ThreeByte(0, 0, 2).to_classification().unwrap());
    /// assert!(Flags::TwoByte(0, 12).to_classification().is_err());
    /// assert!(Flags::ThreeByte(0, 0, 12).to_classification().is_err());
    /// ```
    pub fn to_classification(&self) -> Result<Classification> {
        match *self {
            Flags::TwoByte(_, b) => Classification::new(b & 0b0001_1111),
            Flags::ThreeByte(_, _, c) => Classification::new(c),
        }
    }

    /// Clears any overlap classes in these flags.
    ///
    /// # Examples
    ///
    /// ```
    /// use las::raw::point::Flags;
    ///
    /// let mut flags = Flags::TwoByte(0, 12);
    /// flags.clear_overlap_class();
    /// assert_eq!(Flags::TwoByte(0, 1), flags);
    ///
    /// let mut flags = Flags::ThreeByte(0, 0, 12);
    /// flags.clear_overlap_class();
    /// assert_eq!(Flags::ThreeByte(0, 8, 1), flags);
    /// ```
    pub fn clear_overlap_class(&mut self) {
        match *self {
            Flags::TwoByte(_, ref mut b) => {
                if *b & 0b1_1111 == OVERLAP_CLASSIFICATION_CODE {
                    *b = (*b & 0b1110_0000) + u8::from(Classification::Unclassified);
                }
            }
            Flags::ThreeByte(_, ref mut b, ref mut c) => {
                if *c == OVERLAP_CLASSIFICATION_CODE {
                    *b |= 8;
                    *c = u8::from(Classification::Unclassified);
                }
            }
        }
    }
}

impl Default for Flags {
    fn default() -> Flags {
        Flags::TwoByte(0, 0)
    }
}

impl From<Flags> for (u8, u8, u8) {
    fn from(flags: Flags) -> (u8, u8, u8) {
        match flags {
            Flags::TwoByte(_, b) => {
                let return_number = flags.return_number();
                let number_of_returns = flags.number_of_returns();
                ((number_of_returns << 4) + return_number, 0, b)
            }
            Flags::ThreeByte(a, b, c) => (a, b, c),
        }
    }
}

impl PartialEq for Flags {
    #[allow(clippy::many_single_char_names)]
    fn eq(&self, other: &Flags) -> bool {
        let (a, b, c) = (*self).into();
        let (d, e, f) = (*other).into();
        a == d && b == e && c == f
    }
}

impl Default for ScanAngle {
    fn default() -> ScanAngle {
        ScanAngle::Rank(0)
    }
}

impl From<ScanAngle> for i8 {
    fn from(scan_angle: ScanAngle) -> i8 {
        match scan_angle {
            ScanAngle::Rank(n) => n,
            ScanAngle::Scaled(_) => f32::from(scan_angle).round() as i8,
        }
    }
}

impl From<ScanAngle> for i16 {
    fn from(scan_angle: ScanAngle) -> i16 {
        match scan_angle {
            ScanAngle::Rank(n) => ScanAngle::from(f32::from(n)).into(),
            ScanAngle::Scaled(n) => n,
        }
    }
}

impl From<ScanAngle> for f32 {
    fn from(scan_angle: ScanAngle) -> f32 {
        match scan_angle {
            ScanAngle::Rank(n) => f32::from(n),
            ScanAngle::Scaled(n) => f32::from(n) * SCAN_ANGLE_SCALE_FACTOR,
        }
    }
}

impl From<f32> for ScanAngle {
    fn from(n: f32) -> ScanAngle {
        ScanAngle::Scaled((n / SCAN_ANGLE_SCALE_FACTOR) as i16)
    }
}

impl PartialEq for ScanAngle {
    fn eq(&self, other: &ScanAngle) -> bool {
        f32::from(*self) == f32::from(*other)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    macro_rules! roundtrip {
        ($name:ident, $format:expr) => {
            mod $name {
                #[test]
                fn roundtrip() {
                    use super::*;
                    use std::io::Cursor;

                    let mut format = Format::new($format).unwrap();
                    format.extra_bytes = 1;
                    let mut point = Point::default();
                    point.extra_bytes = vec![42];
                    if format.has_color {
                        point.color = Some(Color::new(0, 0, 0));
                    }
                    if format.has_waveform {
                        point.waveform = Some(Waveform::default());
                    }
                    let mut cursor = Cursor::new(Vec::new());
                    point.write_to(&mut cursor, &format).unwrap();
                    cursor.set_position(0);
                    assert_eq!(point, Point::read_from(cursor, &format).unwrap());
                }
            }
        };
    }

    roundtrip!(format_0, 0);
    roundtrip!(format_1, 1);
    roundtrip!(format_2, 2);
    roundtrip!(format_3, 3);
    roundtrip!(format_4, 4);
    roundtrip!(format_5, 5);
    roundtrip!(format_6, 6);
    roundtrip!(format_7, 7);
    roundtrip!(format_8, 8);
    roundtrip!(format_9, 9);
    roundtrip!(format_10, 10);

    #[test]
    fn return_number() {
        assert_eq!((0, 0, 0), Flags::TwoByte(0, 0).into());
        assert_eq!((1, 0, 0), Flags::TwoByte(1, 0).into());
        assert_eq!((7, 0, 0), Flags::TwoByte(7, 0).into());
        assert_eq!((0u8, 0), Flags::ThreeByte(0, 0, 0).to_two_bytes().unwrap());
        assert_eq!((1u8, 0), Flags::ThreeByte(1, 0, 0).to_two_bytes().unwrap());
        assert_eq!((7u8, 0), Flags::ThreeByte(7, 0, 0).to_two_bytes().unwrap());
        assert!(Flags::ThreeByte(8, 0, 0).to_two_bytes().is_err());
        assert!(Flags::ThreeByte(15, 0, 0).to_two_bytes().is_err());
    }

    #[test]
    fn number_of_returns() {
        assert_eq!((0, 0, 0), Flags::TwoByte(0, 0).into());
        assert_eq!((16, 0, 0), Flags::TwoByte(8, 0).into());
        assert_eq!((0b0111_0000, 0, 0), Flags::TwoByte(0b0011_1000, 0).into());
        assert_eq!((0u8, 0), Flags::ThreeByte(0, 0, 0).to_two_bytes().unwrap());
        assert_eq!(
            (0b0000_1000u8, 0),
            Flags::ThreeByte(0b0001_0000, 0, 0).to_two_bytes().unwrap()
        );
        assert_eq!(
            (0b0011_1000u8, 0),
            Flags::ThreeByte(0b0111_0000, 0, 0).to_two_bytes().unwrap()
        );
        assert!(Flags::ThreeByte(0b1000_0000, 0, 0).to_two_bytes().is_err());
        assert!(Flags::ThreeByte(0b1111_0000, 0, 0).to_two_bytes().is_err());
    }

    #[test]
    fn scan_angle() {
        assert_eq!(-90i8, ScanAngle::Scaled(-15_000).into());
        assert_eq!(90i8, ScanAngle::Scaled(15_000).into());
        assert_eq!(-15_000i16, ScanAngle::Rank(-90).into());
        assert_eq!(15_000i16, ScanAngle::Rank(90).into());
    }

    #[test]
    fn is_synthetic() {
        assert!(!Flags::TwoByte(0, 0).is_synthetic());
        assert!(Flags::TwoByte(0, 0b0010_0000).is_synthetic());
        assert!(!Flags::ThreeByte(0, 0, 0).is_synthetic());
        assert!(Flags::ThreeByte(0, 1, 0).is_synthetic());
        assert_eq!((0, 32), Flags::ThreeByte(0, 1, 0).to_two_bytes().unwrap());
    }

    #[test]
    fn is_key_point() {
        assert!(!Flags::TwoByte(0, 0).is_key_point());
        assert!(Flags::TwoByte(0, 0b0100_0000).is_key_point());
        assert!(!Flags::ThreeByte(0, 0, 0).is_key_point());
        assert!(Flags::ThreeByte(0, 2, 0).is_key_point());
        assert_eq!((0, 64), Flags::ThreeByte(0, 2, 0).to_two_bytes().unwrap());
    }

    #[test]
    fn is_withheld() {
        assert!(!Flags::TwoByte(0, 0).is_withheld());
        assert!(Flags::TwoByte(0, 0b1000_0000).is_withheld());
        assert!(!Flags::ThreeByte(0, 0, 0).is_withheld());
        assert!(Flags::ThreeByte(0, 4, 0).is_withheld());
        assert_eq!((0, 128), Flags::ThreeByte(0, 4, 0).to_two_bytes().unwrap());
    }

    #[test]
    fn is_overlap() {
        assert!(!Flags::TwoByte(0, 0).is_overlap());
        assert!(Flags::TwoByte(0, OVERLAP_CLASSIFICATION_CODE).is_overlap());
        assert!(!Flags::ThreeByte(0, 0, 0).is_overlap());
        assert!(Flags::ThreeByte(0, 8, 0).is_overlap());
        assert_eq!(
            (0, OVERLAP_CLASSIFICATION_CODE),
            Flags::ThreeByte(0, 8, 0).to_two_bytes().unwrap()
        );
    }

    #[test]
    fn scanner_channel() {
        assert_eq!(0, Flags::TwoByte(0, 0).scanner_channel());
        assert_eq!(0, Flags::ThreeByte(0, 0, 0).scanner_channel());
        assert_eq!(1, Flags::ThreeByte(0, 0b0001_0000, 0).scanner_channel());
        assert_eq!(3, Flags::ThreeByte(0, 0b0011_0000, 0).scanner_channel());
        assert!(Flags::ThreeByte(0, 0b1_0000, 0).to_two_bytes().is_err());
    }

    #[test]
    fn scan_direction() {
        assert_eq!(
            ScanDirection::RightToLeft,
            Flags::TwoByte(0, 0).scan_direction()
        );
        assert_eq!(
            ScanDirection::LeftToRight,
            Flags::TwoByte(0b0100_0000, 0).scan_direction()
        );
        assert_eq!(
            ScanDirection::RightToLeft,
            Flags::ThreeByte(0, 0, 0).scan_direction()
        );
        assert_eq!(
            ScanDirection::LeftToRight,
            Flags::ThreeByte(0, 0b0100_0000, 0).scan_direction()
        );
    }

    #[test]
    fn is_edge_of_flight_line() {
        assert!(!Flags::TwoByte(0, 0).is_edge_of_flight_line());
        assert!(Flags::TwoByte(0b1000_0000, 0).is_edge_of_flight_line());
        assert!(!Flags::ThreeByte(0, 0, 0).is_edge_of_flight_line());
        assert!(Flags::ThreeByte(0, 0b1000_0000, 0).is_edge_of_flight_line());
        assert_eq!(
            (128, 0),
            Flags::ThreeByte(0, 128, 0).to_two_bytes().unwrap()
        );
    }

    #[test]
    fn classification() {
        assert_eq!((0, 1), Flags::ThreeByte(0, 0, 1).to_two_bytes().unwrap());
        assert!(Flags::ThreeByte(0, 0, 32).to_two_bytes().is_err());
    }
}