use crate::{point::Error, Result};
use std::fmt;
const TIME_FORMATS: &[u8] = &[1, 3, 4, 5, 6, 7, 8, 9, 10];
const COLOR_FORMATS: &[u8] = &[2, 3, 5, 7, 8, 10];
const WAVEFORM_FORMATS: &[u8] = &[4, 5, 9, 10];
const NIR_FORMATS: &[u8] = &[8, 10];
const IS_COMPRESSED_MASK: u8 = 0x80;
fn is_point_format_compressed(point_format_id: u8) -> bool {
point_format_id & IS_COMPRESSED_MASK == IS_COMPRESSED_MASK
}
fn point_format_id_compressed_to_uncompressd(point_format_id: u8) -> u8 {
point_format_id & 0x3f
}
fn point_format_id_uncompressed_to_compressed(point_format_id: u8) -> u8 {
point_format_id | 0x80
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Format {
pub has_gps_time: bool,
pub has_color: bool,
pub is_extended: bool,
pub has_waveform: bool,
pub has_nir: bool,
pub extra_bytes: u16,
pub is_compressed: bool,
}
#[allow(clippy::len_without_is_empty)]
impl Format {
pub fn new(n: u8) -> Result<Format> {
let is_compressed = is_point_format_compressed(n);
if n > 10 && !is_compressed {
Err(Error::InvalidPointFormatNumber(n))
} else {
let n = point_format_id_compressed_to_uncompressd(n);
Ok(Format {
has_gps_time: TIME_FORMATS.contains(&n),
has_color: COLOR_FORMATS.contains(&n),
has_waveform: WAVEFORM_FORMATS.contains(&n),
has_nir: NIR_FORMATS.contains(&n),
is_extended: n >= 6,
extra_bytes: 0,
is_compressed,
})
}
}
pub fn extend(&mut self) {
self.has_gps_time = true;
self.is_extended = true;
}
pub fn len(&self) -> u16 {
let mut len = if self.is_extended { 22 } else { 20 } + self.extra_bytes;
if self.has_gps_time {
len += 8;
}
if self.has_color {
len += 6;
}
if self.has_nir {
len += 2;
}
if self.has_waveform {
len += 29;
}
len
}
pub fn to_u8(&self) -> Result<u8> {
if !cfg!(feature = "laz") && self.is_compressed {
Err(Error::InvalidPointFormat(*self))
} else if self.is_extended {
if self.has_gps_time {
if self.has_color {
if self.has_nir {
if self.has_waveform {
Ok(10)
} else {
Ok(8)
}
} else if self.has_waveform {
Err(Error::InvalidPointFormat(*self))
} else {
Ok(7)
}
} else if self.has_nir {
Err(Error::InvalidPointFormat(*self))
} else if self.has_waveform {
Ok(9)
} else {
Ok(6)
}
} else {
Err(Error::InvalidPointFormat(*self))
}
} else if self.has_nir {
Err(Error::InvalidPointFormat(*self))
} else if self.has_waveform {
if self.has_gps_time {
if self.has_color {
Ok(5)
} else {
Ok(4)
}
} else {
Err(Error::InvalidPointFormat(*self))
}
} else {
let mut n = if self.has_gps_time { 1 } else { 0 };
if self.has_color {
n += 2;
}
Ok(n)
}
}
pub(crate) fn to_writable_u8(self) -> Result<u8> {
self.to_u8().map(|id| {
if self.is_compressed {
point_format_id_uncompressed_to_compressed(id)
} else {
id
}
})
}
}
impl fmt::Display for Format {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Ok(n) = self.to_u8() {
write!(f, "point format {n}")
} else {
write!(f, "point format that does not map onto a code: {self:?}")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! format {
($name:ident, $n:expr_2021, $expected:expr_2021, $len:expr_2021) => {
mod $name {
use crate::point::Format;
#[test]
fn new() {
assert_eq!($expected, Format::new($n).unwrap());
}
#[test]
fn len() {
assert_eq!($len, Format::new($n).unwrap().len());
}
#[test]
fn to_u8() {
assert_eq!($n, Format::new($n).unwrap().to_u8().unwrap());
}
}
};
}
format!(format_0, 0, Format::default(), 20);
format!(
format_1,
1,
Format {
has_gps_time: true,
..Default::default()
},
28
);
format!(
format_2,
2,
Format {
has_color: true,
..Default::default()
},
26
);
format!(
format_3,
3,
Format {
has_gps_time: true,
has_color: true,
..Default::default()
},
34
);
format!(
format_4,
4,
Format {
has_gps_time: true,
has_waveform: true,
..Default::default()
},
57
);
format!(
format_5,
5,
Format {
has_gps_time: true,
has_color: true,
has_waveform: true,
..Default::default()
},
63
);
format!(
format_6,
6,
Format {
has_gps_time: true,
is_extended: true,
..Default::default()
},
30
);
format!(
format_7,
7,
Format {
has_gps_time: true,
has_color: true,
is_extended: true,
..Default::default()
},
36
);
format!(
format_8,
8,
Format {
has_gps_time: true,
has_color: true,
has_nir: true,
is_extended: true,
..Default::default()
},
38
);
format!(
format_9,
9,
Format {
has_gps_time: true,
has_waveform: true,
is_extended: true,
..Default::default()
},
59
);
format!(
format_10,
10,
Format {
has_gps_time: true,
has_color: true,
has_nir: true,
has_waveform: true,
is_extended: true,
..Default::default()
},
67
);
#[test]
fn waveform_without_gps_time() {
let format = Format {
has_waveform: true,
..Default::default()
};
assert!(format.to_u8().is_err());
}
#[test]
fn extended_without_gps_time() {
let format = Format {
is_extended: true,
..Default::default()
};
assert!(format.to_u8().is_err());
}
#[test]
fn nir_without_extended() {
let format = Format {
has_nir: true,
..Default::default()
};
assert!(format.to_u8().is_err());
}
#[test]
fn nir_without_color() {
let format = Format {
is_extended: true,
has_nir: true,
..Default::default()
};
assert!(format.to_u8().is_err());
}
#[test]
fn extra_bytes() {
let format = Format {
extra_bytes: 1,
..Default::default()
};
assert_eq!(21, format.len());
}
#[test]
fn is_compressed() {
let format = Format {
is_compressed: true,
..Default::default()
};
if cfg!(feature = "laz") {
assert!(format.to_u8().is_ok());
} else {
assert!(format.to_u8().is_err());
}
}
}