use crate::{
point::{Classification, Format, ScanDirection},
Color, Error, Result,
};
use std::io::{Read, Write};
const SCAN_ANGLE_SCALE_FACTOR: f32 = 0.006;
const OVERLAP_CLASSIFICATION_CODE: u8 = 12;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Point {
pub x: i32,
#[allow(missing_docs)]
pub y: i32,
#[allow(missing_docs)]
pub z: i32,
pub intensity: u16,
pub flags: Flags,
pub scan_angle: ScanAngle,
pub user_data: u8,
pub point_source_id: u16,
pub gps_time: Option<f64>,
pub color: Option<Color>,
#[allow(missing_docs)]
pub waveform: Option<Waveform>,
pub nir: Option<u16>,
#[allow(missing_docs)]
pub extra_bytes: Vec<u8>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[allow(missing_docs)]
pub struct Waveform {
pub wave_packet_descriptor_index: u8,
pub byte_offset_to_waveform_data: u64,
pub waveform_packet_size_in_bytes: u32,
pub return_point_waveform_location: f32,
pub x_t: f32,
#[allow(missing_docs)]
pub y_t: f32,
#[allow(missing_docs)]
pub z_t: f32,
}
#[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub enum ScanAngle {
Rank(i8),
Scaled(i16),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Field {
X,
Y,
Z,
Intensity,
Flags,
UserData,
ScanAngle,
PointSourceId,
GpsTime,
Color,
Nir,
Waveform,
ExtraBytes,
}
impl Field {
pub(crate) fn size(self, format: &Format) -> usize {
match self {
Field::X | Field::Y | Field::Z => 4,
Field::Intensity | Field::PointSourceId | Field::Nir => 2,
Field::UserData => 1,
Field::Flags => {
if format.is_extended {
3
} else {
2
}
}
Field::ScanAngle => {
if format.is_extended {
2
} else {
1
}
}
Field::GpsTime => 8,
Field::Color => 6,
Field::Waveform => 29,
Field::ExtraBytes => format.extra_bytes as usize,
}
}
}
pub(crate) fn fields(format: &Format) -> impl Iterator<Item = Field> + '_ {
let (second, third) = if format.is_extended {
(Field::UserData, Field::ScanAngle)
} else {
(Field::ScanAngle, Field::UserData)
};
[
Some(Field::X),
Some(Field::Y),
Some(Field::Z),
Some(Field::Intensity),
Some(Field::Flags),
Some(second),
Some(third),
Some(Field::PointSourceId),
format.has_gps_time.then_some(Field::GpsTime),
format.has_color.then_some(Field::Color),
format.has_nir.then_some(Field::Nir),
format.has_waveform.then_some(Field::Waveform),
Some(Field::ExtraBytes),
]
.into_iter()
.flatten()
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Layout {
pub user_data: usize,
pub scan_angle: usize,
pub point_source_id: usize,
pub gps_time: Option<usize>,
pub rgb: Option<usize>,
pub nir: Option<usize>,
pub record_len: usize,
}
impl Layout {
pub(crate) fn for_format(format: &Format) -> Self {
let mut layout = Layout {
user_data: 0,
scan_angle: 0,
point_source_id: 0,
gps_time: None,
rgb: None,
nir: None,
record_len: 0,
};
let mut offset = 0usize;
for field in fields(format) {
match field {
Field::UserData => layout.user_data = offset,
Field::ScanAngle => layout.scan_angle = offset,
Field::PointSourceId => layout.point_source_id = offset,
Field::GpsTime => layout.gps_time = Some(offset),
Field::Color => layout.rgb = Some(offset),
Field::Nir => layout.nir = Some(offset),
Field::X
| Field::Y
| Field::Z
| Field::Intensity
| Field::Flags
| Field::Waveform
| Field::ExtraBytes => {}
}
offset += field.size(format);
}
layout.record_len = offset;
layout
}
}
#[derive(Clone, Copy, Debug)]
pub enum Flags {
TwoByte(u8, u8),
ThreeByte(u8, u8, u8),
}
impl Point {
pub fn read_from<R: Read>(mut read: R, format: &Format) -> Result<Point> {
use crate::utils;
use byteorder::{LittleEndian, ReadBytesExt};
let mut point = Point::default();
for field in fields(format) {
match field {
Field::X => point.x = read.read_i32::<LittleEndian>()?,
Field::Y => point.y = read.read_i32::<LittleEndian>()?,
Field::Z => point.z = read.read_i32::<LittleEndian>()?,
Field::Intensity => point.intensity = read.read_u16::<LittleEndian>()?,
Field::Flags => {
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()?)
};
}
Field::UserData => point.user_data = read.read_u8()?,
Field::ScanAngle => {
point.scan_angle = if format.is_extended {
ScanAngle::Scaled(read.read_i16::<LittleEndian>()?)
} else {
ScanAngle::Rank(read.read_i8()?)
};
}
Field::PointSourceId => point.point_source_id = read.read_u16::<LittleEndian>()?,
Field::GpsTime => point.gps_time = Some(read.read_f64::<LittleEndian>()?),
Field::Color => {
let red = read.read_u16::<LittleEndian>()?;
let green = read.read_u16::<LittleEndian>()?;
let blue = read.read_u16::<LittleEndian>()?;
point.color = Some(Color::new(red, green, blue));
}
Field::Nir => {
point.nir = utils::some_or_none_if_zero(read.read_u16::<LittleEndian>()?);
}
Field::Waveform => point.waveform = Some(Waveform::read_from(&mut read)?),
Field::ExtraBytes => {
point.extra_bytes.resize(format.extra_bytes as usize, 0);
read.read_exact(&mut point.extra_bytes)?;
}
}
}
Ok(point)
}
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());
for field in fields(format) {
match field {
Field::X => write.write_i32::<LittleEndian>(self.x)?,
Field::Y => write.write_i32::<LittleEndian>(self.y)?,
Field::Z => write.write_i32::<LittleEndian>(self.z)?,
Field::Intensity => write.write_u16::<LittleEndian>(self.intensity)?,
Field::Flags => {
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)?;
}
}
Field::UserData => write.write_u8(self.user_data)?,
Field::ScanAngle => {
if format.is_extended {
write.write_i16::<LittleEndian>(self.scan_angle.into())?;
} else {
write.write_i8(self.scan_angle.into())?;
}
}
Field::PointSourceId => write.write_u16::<LittleEndian>(self.point_source_id)?,
Field::GpsTime => {
write.write_f64::<LittleEndian>(self.gps_time.unwrap_or(0.0))?;
}
Field::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)?;
}
Field::Nir => write.write_u16::<LittleEndian>(self.nir.unwrap_or(0))?,
Field::Waveform => self.waveform.unwrap_or_default().write_to(&mut write)?,
Field::ExtraBytes => 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 {
pub fn return_number(&self) -> u8 {
match *self {
Flags::TwoByte(a, _) => a & 7,
Flags::ThreeByte(a, _, _) => a & 15,
}
}
pub fn number_of_returns(&self) -> u8 {
match *self {
Flags::TwoByte(a, _) => (a >> 3) & 7,
Flags::ThreeByte(a, _, _) => (a >> 4) & 15,
}
}
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
}
}
pub fn is_synthetic(&self) -> bool {
match *self {
Flags::TwoByte(_, b) => (b >> 5) & 1 == 1,
Flags::ThreeByte(_, b, _) => b & 1 == 1,
}
}
pub fn is_key_point(&self) -> bool {
match *self {
Flags::TwoByte(_, b) => (b >> 6) & 1 == 1,
Flags::ThreeByte(_, b, _) => b & 2 == 2,
}
}
pub fn is_withheld(&self) -> bool {
match *self {
Flags::TwoByte(_, b) => (b >> 7) & 1 == 1,
Flags::ThreeByte(_, b, _) => b & 4 == 4,
}
}
pub fn is_overlap(&self) -> bool {
match *self {
Flags::TwoByte(_, b) => b & 0b1111 == OVERLAP_CLASSIFICATION_CODE,
Flags::ThreeByte(_, b, _) => b & 8 == 8,
}
}
pub fn scanner_channel(&self) -> u8 {
match *self {
Flags::TwoByte(_, _) => 0,
Flags::ThreeByte(_, b, _) => (b >> 4) & 3,
}
}
pub fn is_edge_of_flight_line(&self) -> bool {
let n = match *self {
Flags::TwoByte(a, _) => a,
Flags::ThreeByte(_, b, _) => b,
};
(n >> 7) == 1
}
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,
})
} else if self.number_of_returns() > 7 {
Err(Error::ReturnNumber {
return_number: self.number_of_returns(),
version: None,
})
} else if c > 31 {
Err(Error::InvalidClassification(c))
} else if self.scanner_channel() > 0 {
Err(Error::InvalidScannerChannel(self.scanner_channel()))
} 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))
}
}
}
}
pub fn to_classification(&self) -> Result<Classification> {
match *self {
Flags::TwoByte(_, b) => Classification::new(b & 0b0001_1111),
Flags::ThreeByte(_, _, c) => Classification::new(c),
}
}
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_2021) => {
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_gps_time {
point.gps_time = Some(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, i8::from(ScanAngle::Scaled(-15_000)));
assert_eq!(90i8, i8::from(ScanAngle::Scaled(15_000)));
assert_eq!(-15_000i16, i16::from(ScanAngle::Rank(-90)));
assert_eq!(15_000i16, i16::from(ScanAngle::Rank(90)));
}
#[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());
}
}