use crate::{raw, Bounds, Point, Vector};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use laz::record::{LayeredPointRecordDecompressor, RecordDecompressor};
use std::{
collections::HashMap,
io::{Cursor, Read, Seek, SeekFrom, Write},
};
pub const USER_ID: &str = "copc";
pub const DESCRIPTION: &str = "https://copc.io";
use crate::{Error, Header, Result, Vlr};
#[derive(Debug)]
pub struct CopcInfoVlr {
pub center_x: f64,
pub center_y: f64,
pub center_z: f64,
pub halfsize: f64,
pub spacing: f64,
root_hier_offset: u64,
root_hier_size: u64,
pub gpstime_minimum: f64,
pub gpstime_maximum: f64,
reserved: [u64; 11],
}
impl CopcInfoVlr {
pub const RECORD_ID: u16 = 1;
fn read_from<R: Read>(mut src: R) -> Result<Self> {
Ok(Self {
center_x: src.read_f64::<LittleEndian>()?,
center_y: src.read_f64::<LittleEndian>()?,
center_z: src.read_f64::<LittleEndian>()?,
halfsize: src.read_f64::<LittleEndian>()?,
spacing: src.read_f64::<LittleEndian>()?,
root_hier_offset: src.read_u64::<LittleEndian>()?,
root_hier_size: src.read_u64::<LittleEndian>()?,
gpstime_minimum: src.read_f64::<LittleEndian>()?,
gpstime_maximum: src.read_f64::<LittleEndian>()?,
reserved: {
let mut reserved = [0; 11];
for field in reserved.iter_mut() {
*field = src.read_u64::<LittleEndian>()?;
}
reserved
},
})
}
pub fn write_to<W: Write>(&self, dst: &mut W) -> Result<()> {
dst.write_f64::<LittleEndian>(self.center_x)?;
dst.write_f64::<LittleEndian>(self.center_y)?;
dst.write_f64::<LittleEndian>(self.center_z)?;
dst.write_f64::<LittleEndian>(self.halfsize)?;
dst.write_f64::<LittleEndian>(self.spacing)?;
dst.write_u64::<LittleEndian>(self.root_hier_offset)?;
dst.write_u64::<LittleEndian>(self.root_hier_size)?;
dst.write_f64::<LittleEndian>(self.gpstime_minimum)?;
dst.write_f64::<LittleEndian>(self.gpstime_maximum)?;
self.reserved
.into_iter()
.try_for_each(|i| dst.write_u64::<LittleEndian>(i))?;
Ok(())
}
}
impl TryFrom<&Vlr> for CopcInfoVlr {
type Error = Error;
fn try_from(value: &Vlr) -> Result<Self> {
Self::read_from::<&[u8]>(value.data.as_ref())
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct VoxelKey {
pub l: i32,
#[allow(missing_docs)]
pub x: i32,
#[allow(missing_docs)]
pub y: i32,
#[allow(missing_docs)]
pub z: i32,
}
impl VoxelKey {
pub fn child(&self, direction: i32) -> Result<Self> {
if !(0..8).contains(&direction) {
return Err(Error::InvalidDirection(direction));
}
Ok(Self {
l: self.l + 1,
x: (self.x << 1) | (direction & 0x1),
y: (self.y << 1) | ((direction >> 1) & 0x1),
z: (self.z << 1) | ((direction >> 2) & 0x1),
})
}
pub fn parent(&self) -> Self {
Self {
l: 0.max(self.l - 1),
x: self.x >> 1,
y: self.y >> 1,
z: self.z >> 1,
}
}
pub fn bounds(&self, copc_info: CopcInfoVlr) -> Bounds {
let root_min_x = copc_info.center_x - copc_info.halfsize;
let root_min_y = copc_info.center_y - copc_info.halfsize;
let root_min_z = copc_info.center_z - copc_info.halfsize;
let root_max_x = copc_info.center_x + copc_info.halfsize;
let root_max_y = copc_info.center_y + copc_info.halfsize;
let root_max_z = copc_info.center_z + copc_info.halfsize;
let root_size_x = root_max_x - root_min_x;
let root_size_y = root_max_y - root_min_y;
let root_size_z = root_max_z - root_min_z;
let voxel_size_x = root_size_x / (1 << self.l) as f64;
let voxel_size_y = root_size_y / (1 << self.l) as f64;
let voxel_size_z = root_size_z / (1 << self.l) as f64;
let voxel_min = Vector {
x: root_min_x + voxel_size_x * self.x as f64,
y: root_min_y + voxel_size_y * self.y as f64,
z: root_min_z + voxel_size_z * self.z as f64,
};
let voxel_max = Vector {
x: voxel_min.x + voxel_size_x,
y: voxel_min.y + voxel_size_y,
z: voxel_min.z + voxel_size_z,
};
Bounds {
min: voxel_min,
max: voxel_max,
}
}
pub const ROOT: Self = Self {
l: 0,
x: 0,
y: 0,
z: 0,
};
pub fn read_from<R: Read>(read: &mut R) -> Result<Self> {
Ok(Self {
l: read.read_i32::<LittleEndian>()?,
x: read.read_i32::<LittleEndian>()?,
y: read.read_i32::<LittleEndian>()?,
z: read.read_i32::<LittleEndian>()?,
})
}
fn write_to<W: Write>(&self, dst: &mut W) -> Result<()> {
dst.write_i32::<LittleEndian>(self.l)?;
dst.write_i32::<LittleEndian>(self.x)?;
dst.write_i32::<LittleEndian>(self.y)?;
dst.write_i32::<LittleEndian>(self.z)?;
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct Entry {
pub key: VoxelKey,
pub offset: u64,
pub byte_size: i32,
pub point_count: i32,
}
impl Entry {
fn read_from<R: Read>(read: &mut R) -> Result<Self> {
Ok(Self {
key: VoxelKey::read_from(read)?,
offset: read.read_u64::<LittleEndian>()?,
byte_size: read.read_i32::<LittleEndian>()?,
point_count: read.read_i32::<LittleEndian>()?,
})
}
fn write_to<W: Write>(&self, dst: &mut W) -> Result<()> {
self.key.write_to(dst)?;
dst.write_u64::<LittleEndian>(self.offset)?;
dst.write_i32::<LittleEndian>(self.byte_size)?;
dst.write_i32::<LittleEndian>(self.point_count)?;
Ok(())
}
fn is_referencing_page(&self) -> bool {
self.point_count == -1
}
}
#[derive(Debug)]
struct Page {
entries: Vec<Entry>,
}
impl Page {
fn read_from(mut data: &[u8]) -> Result<Self> {
Ok(Self {
entries: (0..data.len() / 32)
.map(|_| Entry::read_from(&mut data))
.collect::<Result<Vec<Entry>>>()?,
})
}
fn write_to<W: Write>(&self, dst: &mut W) -> Result<()> {
self.entries
.iter()
.try_for_each(|entry| entry.write_to(dst))?;
Ok(())
}
}
#[derive(Debug)]
pub struct CopcHierarchyVlr {
root: Page,
sub_pages: HashMap<VoxelKey, Page>,
}
impl CopcHierarchyVlr {
pub const RECORD_ID: u16 = 1000;
pub fn write_to<W: Write>(&self, dst: &mut W) -> Result<()> {
self.root.write_to(dst)?;
self.sub_pages
.iter()
.try_for_each(|(_, page)| page.write_to(dst))
}
pub fn read_from_with(vlr: &Vlr, copc_info: &CopcInfoVlr) -> Result<CopcHierarchyVlr> {
let root = Page::read_from(vlr.data[0..copc_info.root_hier_size as usize].as_ref())?;
let sub_pages = root
.entries
.iter()
.filter(|entry| entry.is_referencing_page())
.map(|entry| {
let start = (entry.offset - copc_info.root_hier_offset) as usize;
let end = start + entry.byte_size as usize;
Page::read_from(vlr.data[start..end].as_ref()).map(|p| (entry.key, p))
})
.collect::<Result<HashMap<VoxelKey, Page>>>()?;
Ok(CopcHierarchyVlr { root, sub_pages })
}
pub fn iter_entries(&self) -> EntryIterator<'_> {
EntryIterator::new(self.root.entries.iter().peekable(), &self.sub_pages)
}
}
#[derive(Debug)]
pub struct EntryIterator<'a> {
root_iter: std::iter::Peekable<std::slice::Iter<'a, Entry>>,
ref_iter: Option<std::slice::Iter<'a, Entry>>,
sub_pages: &'a HashMap<VoxelKey, Page>,
}
impl<'a> EntryIterator<'a> {
fn new(
root_iter: std::iter::Peekable<std::slice::Iter<'a, Entry>>,
sub_pages: &'a HashMap<VoxelKey, Page>,
) -> Self {
Self {
root_iter,
ref_iter: None,
sub_pages,
}
}
}
impl<'a> Iterator for EntryIterator<'a> {
type Item = Result<&'a Entry>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match (
&mut self.ref_iter,
self.root_iter
.peek()
.map(|entry| entry.is_referencing_page()),
) {
(None, Some(true)) => {
let next_entry = self.root_iter.next();
self.ref_iter = next_entry
.and_then(|entry| self.sub_pages.get(&entry.key))
.map(|page| page.entries.iter());
if self.ref_iter.is_none() {
return next_entry
.map(|entry| Err(Error::ReferencedPageMissingFromEvlr(*entry)));
}
}
(Some(ref_iter), _) => {
if let Some(entry) = ref_iter.next() {
return Some(Ok(entry));
} else {
self.ref_iter = None;
}
}
(None, Some(false)) => return self.root_iter.next().map(Ok),
(None, None) => return None,
}
}
}
}
impl Vlr {
pub fn is_copc_info(&self) -> bool {
self.user_id == USER_ID && self.record_id == CopcInfoVlr::RECORD_ID
}
}
impl Header {
pub fn copc_info_vlr(&self) -> Option<CopcInfoVlr> {
self.vlrs
.iter()
.find(|vlr| vlr.is_copc_info())
.and_then(|vlr| vlr.try_into().ok())
}
}
impl Vlr {
pub fn is_copc_hierarchy(&self) -> bool {
self.user_id == USER_ID && self.record_id == CopcHierarchyVlr::RECORD_ID
}
}
impl Header {
pub fn copc_hierarchy_evlr(&self) -> Option<CopcHierarchyVlr> {
let copc_info = self.copc_info_vlr()?;
self.evlrs()
.iter()
.find(|vlr| vlr.is_copc_hierarchy())
.and_then(|vlr| CopcHierarchyVlr::read_from_with(vlr, &copc_info).ok())
}
}
#[allow(missing_debug_implementations)]
pub struct CopcEntryReader<'a, R: Read + Seek> {
decompressor: LayeredPointRecordDecompressor<'a, R>,
buffer: Cursor<Vec<u8>>,
header: Header,
}
impl<R: Read + Seek> CopcEntryReader<'_, R> {
pub fn new(mut read: R) -> Result<Self> {
let header = Header::new(read.by_ref())?;
let mut decompressor = LayeredPointRecordDecompressor::new(read);
decompressor.set_fields_from(header.laz_vlr()?.items())?;
let buffer = Cursor::new(Vec::new());
Ok(Self {
decompressor,
buffer,
header,
})
}
pub fn hierarchy_entries(&self) -> Option<Vec<Entry>> {
self.header()
.copc_hierarchy_evlr()
.map(|vlr| vlr.iter_entries().filter_map(|e| e.ok().copied()).collect())
}
pub fn read_entry_points(&mut self, entry: &Entry, points: &mut Vec<Point>) -> Result<u64> {
let _off = self
.decompressor
.get_mut()
.seek(SeekFrom::Start(entry.offset))?;
points.reserve_exact(entry.point_count as usize);
let resize = usize::try_from(
entry.point_count as u64 * u64::from(self.header.point_format().len()),
)?;
self.buffer.get_mut().resize(resize, 0u8);
self.decompressor.decompress_many(self.buffer.get_mut())?;
self.buffer.set_position(0);
points.reserve(entry.point_count as usize);
for _ in 0..entry.point_count as usize {
let point = raw::Point::read_from(&mut self.buffer, self.header.point_format())
.map(|raw_point| Point::new(raw_point, self.header.transforms()))?;
points.push(point);
}
Ok(entry.point_count as u64)
}
pub fn header(&self) -> &Header {
&self.header
}
}
#[cfg(test)]
mod tests {
use super::{CopcInfoVlr, Result, VoxelKey};
use crate::{copc::CopcEntryReader, Bounds, Reader, Vector};
use std::{fs::File, io::BufReader};
#[test]
fn test_voxelkey() {
let vk = VoxelKey::ROOT;
let childs = (0..8)
.map(|dir| vk.child(dir))
.collect::<Result<Vec<_>>>()
.unwrap();
assert!(childs
.iter()
.map(|v| v.parent())
.all(|v| v.eq(&VoxelKey::ROOT)));
assert!(childs
.iter()
.map(|c| (
c,
(0..8).map(|dir| c.child(dir).unwrap()).collect::<Vec<_>>()
))
.all(|(p, childs)| childs.iter().all(|c| c.parent().eq(p))));
}
#[test]
fn test_vlr_copc_autzen() {
let reader = Reader::from_path("tests/data/autzen.copc.laz").expect("Cannot open reader");
let copcinfo = reader.header().copc_info_vlr().unwrap();
let copchier = reader.header().copc_hierarchy_evlr().unwrap();
assert!(copcinfo.root_hier_offset == 4336);
assert!(copcinfo.root_hier_size == 32);
assert!(copchier.root.entries[0].key == VoxelKey::ROOT);
}
#[test]
fn test_copc_entry_key_autzen() {
let file =
BufReader::new(File::open("tests/data/autzen.copc.laz").expect("Cannot open reader"));
let entry_reader = CopcEntryReader::new(file).unwrap();
let root_entry = entry_reader.hierarchy_entries().unwrap()[0];
assert_eq!(root_entry.key, VoxelKey::ROOT);
assert_eq!(root_entry.point_count, 107);
}
#[test]
fn test_copc_read_autzen() {
let copc_points = {
let file = BufReader::new(File::open("tests/data/autzen.copc.laz").unwrap());
let mut entry_reader = CopcEntryReader::new(file).unwrap();
let root_entry = entry_reader.hierarchy_entries().unwrap()[0];
let mut points = Vec::new();
let _p_num = entry_reader
.read_entry_points(&root_entry, &mut points)
.unwrap();
points
};
let mut laz_points = Vec::new();
let _pnum = Reader::from_path("tests/data/autzen.copc.laz")
.unwrap()
.read_all_points_into(&mut laz_points)
.unwrap();
assert!(laz_points
.iter()
.zip(copc_points)
.all(|(laz_point, copc_point)| laz_point.eq(&copc_point)));
}
#[test]
fn test_voxel_bounds() {
let copc_info = CopcInfoVlr {
center_x: 10.,
center_y: 10.,
center_z: 10.,
halfsize: 5.,
spacing: 1.,
root_hier_offset: 0,
root_hier_size: 0,
gpstime_minimum: 0.,
gpstime_maximum: 0.,
reserved: [0; 11],
};
let key = VoxelKey::ROOT.child(3).unwrap();
let bounds = key.bounds(copc_info);
assert_eq!(
bounds,
Bounds {
min: Vector {
x: 10.0,
y: 10.0,
z: 5.0
},
max: Vector {
x: 15.0,
y: 15.0,
z: 10.0
}
}
);
}
}