use crate::{point::Format, raw, raw::point::Layout, Point, Result, Transform, Vector};
use std::io::Cursor;
#[derive(Clone, Debug)]
pub struct PointData {
bytes: Vec<u8>,
format: Format,
transforms: Vector<Transform>,
layout: Layout,
}
#[derive(Clone, Debug, Default)]
pub struct PointDataBuilder {
format: Format,
transforms: Vector<Transform>,
}
impl PointDataBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_format(mut self, format: Format) -> Self {
self.format = format;
self
}
pub fn with_transforms(mut self, transforms: Vector<Transform>) -> Self {
self.transforms = transforms;
self
}
pub fn for_header(self, header: &crate::Header) -> Self {
self.with_format(*header.point_format())
.with_transforms(*header.transforms())
}
pub fn build(self) -> PointData {
let layout = Layout::for_format(&self.format);
PointData {
bytes: Vec::new(),
format: self.format,
transforms: self.transforms,
layout,
}
}
pub fn build_from_bytes(self, bytes: Vec<u8>) -> Result<PointData> {
let layout = Layout::for_format(&self.format);
if layout.record_len == 0 || !bytes.len().is_multiple_of(layout.record_len) {
return Err(crate::Error::InvalidByteBufferLength {
len: bytes.len(),
record_len: layout.record_len,
});
}
Ok(PointData {
bytes,
format: self.format,
transforms: self.transforms,
layout,
})
}
pub fn build_from_points<I>(self, points: I) -> Result<PointData>
where
I: IntoIterator<Item = Point>,
{
let layout = Layout::for_format(&self.format);
let iter = points.into_iter();
let (lower, _) = iter.size_hint();
let mut bytes = Vec::with_capacity(lower * layout.record_len);
for point in iter {
let raw = point.into_raw(&self.transforms)?;
raw.write_to(&mut bytes, &self.format)?;
}
Ok(PointData {
bytes,
format: self.format,
transforms: self.transforms,
layout,
})
}
}
impl PointData {
pub fn len(&self) -> usize {
self.bytes
.len()
.checked_div(self.layout.record_len)
.unwrap_or(0)
}
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
pub fn format(&self) -> &Format {
&self.format
}
pub fn transforms(&self) -> &Vector<Transform> {
&self.transforms
}
pub fn raw_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn record_len(&self) -> usize {
self.layout.record_len
}
pub fn points(&self) -> PointDataIter<'_> {
PointDataIter {
cursor: Cursor::new(&self.bytes),
format: &self.format,
transforms: &self.transforms,
remaining: self.len(),
}
}
pub fn resize_for(&mut self, n: usize) -> &mut [u8] {
let new_len = n.checked_mul(self.layout.record_len).expect("overflow");
self.bytes.resize(new_len, 0u8);
&mut self.bytes
}
pub(crate) fn take_bytes_mut(&mut self) -> &mut Vec<u8> {
&mut self.bytes
}
pub fn x_raw(&self) -> impl Iterator<Item = i32> + '_ {
self.i32_column(0)
}
pub fn y_raw(&self) -> impl Iterator<Item = i32> + '_ {
self.i32_column(4)
}
pub fn z_raw(&self) -> impl Iterator<Item = i32> + '_ {
self.i32_column(8)
}
pub fn x(&self) -> impl Iterator<Item = f64> + '_ {
let t = self.transforms.x;
self.x_raw().map(move |n| t.direct(n))
}
pub fn y(&self) -> impl Iterator<Item = f64> + '_ {
let t = self.transforms.y;
self.y_raw().map(move |n| t.direct(n))
}
pub fn z(&self) -> impl Iterator<Item = f64> + '_ {
let t = self.transforms.z;
self.z_raw().map(move |n| t.direct(n))
}
pub fn intensity(&self) -> impl Iterator<Item = u16> + '_ {
self.u16_column(12)
}
pub fn classification(&self) -> impl Iterator<Item = u8> + '_ {
let is_extended = self.format.is_extended;
self.records().map(move |rec| {
if is_extended {
rec[16]
} else {
rec[15] & 0b0001_1111
}
})
}
pub fn return_number(&self) -> impl Iterator<Item = u8> + '_ {
let is_extended = self.format.is_extended;
self.records().map(move |rec| {
if is_extended {
rec[14] & 15
} else {
rec[14] & 7
}
})
}
pub fn number_of_returns(&self) -> impl Iterator<Item = u8> + '_ {
let is_extended = self.format.is_extended;
self.records().map(move |rec| {
if is_extended {
(rec[14] >> 4) & 15
} else {
(rec[14] >> 3) & 7
}
})
}
pub fn scan_angle_degrees(&self) -> impl Iterator<Item = f32> + '_ {
let is_extended = self.format.is_extended;
let sa_off = self.layout.scan_angle;
self.records().map(move |rec| {
if is_extended {
let raw = i16::from_le_bytes([rec[sa_off], rec[sa_off + 1]]);
f32::from(raw) * 0.006
} else {
f32::from(rec[sa_off] as i8)
}
})
}
pub fn user_data(&self) -> impl Iterator<Item = u8> + '_ {
let ud = self.layout.user_data;
self.records().map(move |rec| rec[ud])
}
pub fn point_source_id(&self) -> impl Iterator<Item = u16> + '_ {
let ps = self.layout.point_source_id;
self.records()
.map(move |rec| u16::from_le_bytes([rec[ps], rec[ps + 1]]))
}
pub fn gps_time(&self) -> Option<impl Iterator<Item = f64> + '_> {
let g = self.layout.gps_time?;
Some(self.records().map(move |rec| {
f64::from_le_bytes([
rec[g],
rec[g + 1],
rec[g + 2],
rec[g + 3],
rec[g + 4],
rec[g + 5],
rec[g + 6],
rec[g + 7],
])
}))
}
pub fn rgb(&self) -> Option<impl Iterator<Item = (u16, u16, u16)> + '_> {
let c = self.layout.rgb?;
Some(self.records().map(move |rec| {
(
u16::from_le_bytes([rec[c], rec[c + 1]]),
u16::from_le_bytes([rec[c + 2], rec[c + 3]]),
u16::from_le_bytes([rec[c + 4], rec[c + 5]]),
)
}))
}
pub fn nir(&self) -> Option<impl Iterator<Item = u16> + '_> {
let n = self.layout.nir?;
Some(
self.records()
.map(move |rec| u16::from_le_bytes([rec[n], rec[n + 1]])),
)
}
fn records(&self) -> impl Iterator<Item = &[u8]> + '_ {
self.bytes.chunks_exact(self.layout.record_len)
}
fn i32_column(&self, field_offset: usize) -> I32Column<'_> {
I32Column {
bytes: &self.bytes,
stride: self.layout.record_len,
field: field_offset,
pos: 0,
}
}
fn u16_column(&self, field_offset: usize) -> U16Column<'_> {
U16Column {
bytes: &self.bytes,
stride: self.layout.record_len,
field: field_offset,
pos: 0,
}
}
}
#[allow(missing_debug_implementations)]
pub struct PointDataIter<'a> {
cursor: Cursor<&'a [u8]>,
format: &'a Format,
transforms: &'a Vector<Transform>,
remaining: usize,
}
impl Iterator for PointDataIter<'_> {
type Item = Result<Point>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
self.remaining -= 1;
Some(
raw::Point::read_from(&mut self.cursor, self.format)
.map(|raw_point| Point::new(raw_point, self.transforms)),
)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl ExactSizeIterator for PointDataIter<'_> {}
#[derive(Debug)]
struct I32Column<'a> {
bytes: &'a [u8],
stride: usize,
field: usize,
pos: usize,
}
impl Iterator for I32Column<'_> {
type Item = i32;
fn next(&mut self) -> Option<i32> {
let start = self.pos + self.field;
if start + 4 > self.bytes.len() {
return None;
}
let v = i32::from_le_bytes([
self.bytes[start],
self.bytes[start + 1],
self.bytes[start + 2],
self.bytes[start + 3],
]);
self.pos += self.stride;
Some(v)
}
}
#[derive(Debug)]
struct U16Column<'a> {
bytes: &'a [u8],
stride: usize,
field: usize,
pos: usize,
}
impl Iterator for U16Column<'_> {
type Item = u16;
fn next(&mut self) -> Option<u16> {
let start = self.pos + self.field;
if start + 2 > self.bytes.len() {
return None;
}
let v = u16::from_le_bytes([self.bytes[start], self.bytes[start + 1]]);
self.pos += self.stride;
Some(v)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
point::Format,
raw::{
self,
point::{Flags, ScanAngle, Waveform},
},
Transform, Vector,
};
fn default_transforms() -> Vector<Transform> {
Vector {
x: Transform::default(),
y: Transform::default(),
z: Transform::default(),
}
}
fn empty_point_data(format: Format) -> PointData {
PointDataBuilder::new()
.with_format(format)
.with_transforms(default_transforms())
.build()
}
fn build_raw_point(format: &Format, i: i32) -> raw::Point {
let flags = if format.is_extended {
Flags::ThreeByte((2 << 4) | 1, (1 << 4) | 1, 5)
} else {
Flags::TwoByte((2 << 3) | 1, 3 | 0b0010_0000)
};
let scan_angle = if format.is_extended {
ScanAngle::Scaled(1500) } else {
ScanAngle::Rank(-12)
};
raw::Point {
x: i,
y: i + 1,
z: i + 2,
intensity: 1000 + i as u16,
flags,
scan_angle,
user_data: 7,
point_source_id: 42,
gps_time: if format.has_gps_time {
Some(1234.5 + f64::from(i))
} else {
None
},
color: if format.has_color {
Some(crate::Color {
red: 100 + i as u16,
green: 200 + i as u16,
blue: 300 + i as u16,
})
} else {
None
},
waveform: if format.has_waveform {
Some(Waveform::default())
} else {
None
},
nir: if format.has_nir { Some(4242) } else { None },
extra_bytes: vec![0u8; format.extra_bytes as usize],
}
}
fn build_points_for_format(format: Format) -> PointData {
let n = 5i32;
let mut buf: Vec<u8> = Vec::new();
for i in 0..n {
let rp = build_raw_point(&format, i);
rp.write_to(&mut buf, &format).unwrap();
}
let mut points = empty_point_data(format);
let slab = points.resize_for(n as usize);
slab.copy_from_slice(&buf);
points
}
fn check_format(format: Format) {
let points = build_points_for_format(format);
assert_eq!(points.len(), 5);
let xs_col: Vec<i32> = points.x_raw().collect();
let ys_col: Vec<i32> = points.y_raw().collect();
let zs_col: Vec<i32> = points.z_raw().collect();
let int_col: Vec<u16> = points.intensity().collect();
let cls_col: Vec<u8> = points.classification().collect();
let rn_col: Vec<u8> = points.return_number().collect();
let nr_col: Vec<u8> = points.number_of_returns().collect();
let sa_col: Vec<f32> = points.scan_angle_degrees().collect();
let ud_col: Vec<u8> = points.user_data().collect();
let ps_col: Vec<u16> = points.point_source_id().collect();
for (i, p) in points.points().enumerate() {
let p = p.unwrap();
assert_eq!(xs_col[i], i as i32);
assert_eq!(ys_col[i], i as i32 + 1);
assert_eq!(zs_col[i], i as i32 + 2);
assert_eq!(int_col[i], 1000 + i as u16);
assert_eq!(rn_col[i], 1);
assert_eq!(nr_col[i], 2);
assert_eq!(ud_col[i], 7);
assert_eq!(ps_col[i], 42);
assert_eq!(rn_col[i], p.return_number);
assert_eq!(nr_col[i], p.number_of_returns);
assert_eq!(ud_col[i], p.user_data);
assert_eq!(ps_col[i], p.point_source_id);
if format.is_extended {
assert_eq!(cls_col[i], 5);
assert!((sa_col[i] - 9.0).abs() < 1e-3);
} else {
assert_eq!(cls_col[i], 3);
assert!((sa_col[i] - (-12.0)).abs() < 1e-3);
}
if format.has_gps_time {
assert_eq!(p.gps_time, Some(1234.5 + f64::from(i as i32)));
} else {
assert_eq!(p.gps_time, None);
}
if format.has_color {
let c = p.color.unwrap();
assert_eq!(
(c.red, c.green, c.blue),
(100 + i as u16, 200 + i as u16, 300 + i as u16)
);
} else {
assert!(p.color.is_none());
}
if format.has_nir {
assert_eq!(p.nir, Some(4242));
} else {
assert_eq!(p.nir, None);
}
}
if format.has_gps_time {
let gps_col: Vec<f64> = points.gps_time().unwrap().collect();
let gps_row: Vec<f64> = points
.points()
.map(|p| p.unwrap().gps_time.unwrap())
.collect();
assert_eq!(gps_col, gps_row);
} else {
assert!(points.gps_time().is_none());
}
if format.has_color {
let rgb_col: Vec<(u16, u16, u16)> = points.rgb().unwrap().collect();
let rgb_row: Vec<(u16, u16, u16)> = points
.points()
.map(|p| {
let c = p.unwrap().color.unwrap();
(c.red, c.green, c.blue)
})
.collect();
assert_eq!(rgb_col, rgb_row);
}
if format.has_nir {
let nir_col: Vec<u16> = points.nir().unwrap().collect();
let nir_row: Vec<u16> = points.points().map(|p| p.unwrap().nir.unwrap()).collect();
assert_eq!(nir_col, nir_row);
}
}
#[test]
fn format_0() {
check_format(Format::new(0).unwrap());
}
#[test]
fn format_1() {
check_format(Format::new(1).unwrap());
}
#[test]
fn format_2() {
check_format(Format::new(2).unwrap());
}
#[test]
fn format_3() {
check_format(Format::new(3).unwrap());
}
#[test]
fn format_6() {
check_format(Format::new(6).unwrap());
}
#[test]
fn format_7() {
check_format(Format::new(7).unwrap());
}
#[test]
fn format_8() {
check_format(Format::new(8).unwrap());
}
#[test]
fn record_len_matches_format_len() {
for n in [0u8, 1, 2, 3, 6, 7, 8] {
let f = Format::new(n).unwrap();
let points = empty_point_data(f);
assert_eq!(points.record_len(), f.len() as usize, "format {n}");
}
}
#[test]
fn build_from_bytes_valid() {
let format = Format::new(1).unwrap();
let record_len = format.len() as usize;
let mut buf = Vec::new();
for i in 0..3 {
let rp = build_raw_point(&format, i);
rp.write_to(&mut buf, &format).unwrap();
}
assert_eq!(buf.len(), record_len * 3);
let points = PointDataBuilder::new()
.with_format(format)
.with_transforms(default_transforms())
.build_from_bytes(buf)
.unwrap();
assert_eq!(points.len(), 3);
let xs: Vec<i32> = points.x_raw().collect();
assert_eq!(xs, vec![0, 1, 2]);
}
#[test]
fn build_from_bytes_rejects_bad_length() {
let format = Format::new(0).unwrap();
let record_len = format.len() as usize;
let buf = vec![0u8; record_len * 2 + 1];
let result = PointDataBuilder::new()
.with_format(format)
.with_transforms(default_transforms())
.build_from_bytes(buf);
assert!(result.is_err());
}
#[test]
fn build_from_bytes_accepts_empty() {
let format = Format::new(0).unwrap();
let points = PointDataBuilder::new()
.with_format(format)
.with_transforms(default_transforms())
.build_from_bytes(Vec::new())
.unwrap();
assert!(points.is_empty());
}
#[test]
fn resize_for_and_fill() {
let format = Format::new(1).unwrap();
let mut points = empty_point_data(format);
let mut buf = Vec::new();
for i in 0..2 {
build_raw_point(&format, i)
.write_to(&mut buf, &format)
.unwrap();
}
let slab = points.resize_for(2);
slab.copy_from_slice(&buf);
assert_eq!(points.len(), 2);
let xs: Vec<i32> = points.x_raw().collect();
assert_eq!(xs, vec![0, 1]);
}
#[test]
fn points_matches_columns() {
let format = Format::new(7).unwrap();
let points = build_points_for_format(format);
let owned: Vec<Point> = points.points().map(Result::unwrap).collect();
let xs_col: Vec<f64> = points.x().collect();
let ints: Vec<u16> = points.intensity().collect();
assert_eq!(owned.len(), points.len());
for (i, p) in owned.iter().enumerate() {
assert!((p.x - xs_col[i]).abs() < 1e-12);
assert_eq!(p.intensity, ints[i]);
}
}
}