use std::io::{Read, Seek, SeekFrom};
use crate::errors::LasZipError::MissingChunkTable;
use crate::las::selective::DecompressionSelection;
use crate::record::RecordDecompressor;
use crate::LasZipError;
use crate::laszip::chunk_table::ChunkTable;
use crate::laszip::{details, CompressorType, LazVlr};
pub(super) struct SeekInfo {
pub(super) data_start: u64,
pub(super) chunk_table: ChunkTable,
}
impl SeekInfo {
pub(super) fn read_from<T: Read + Seek>(
mut source: &mut T,
vlr: &LazVlr,
) -> crate::Result<Self> {
let chunk_table = ChunkTable::read_from(&mut source, vlr)?;
let data_start = source.seek(SeekFrom::Current(0))?;
Ok(Self {
data_start,
chunk_table,
})
}
pub(super) fn offset_to_chunk_table(&self) -> u64 {
self.data_start
+ self
.chunk_table
.as_ref()
.iter()
.map(|e| e.byte_count)
.sum::<u64>()
}
}
pub struct LasZipDecompressor<'a, R: Read + Seek + 'a> {
vlr: LazVlr,
record_decompressor: Box<dyn RecordDecompressor<R> + Send + Sync + 'a>,
selection: DecompressionSelection,
seek_info: Option<SeekInfo>,
current_chunk: usize,
chunk_points_read: u64,
num_points_in_chunk: u64,
}
impl<'a, R: Read + Seek + Send + Sync + 'a> LasZipDecompressor<'a, R> {
pub fn new(source: R, vlr: LazVlr) -> crate::Result<Self> {
Self::selective(source, vlr, DecompressionSelection::all())
}
pub fn selective(
mut source: R,
vlr: LazVlr,
selection: DecompressionSelection,
) -> crate::Result<Self> {
let seek_info = match vlr.compressor {
CompressorType::PointWise => {
None
}
CompressorType::PointWiseChunked => {
let result = SeekInfo::read_from(&mut source, &vlr);
match (result, vlr.uses_variable_size_chunks()) {
(Ok(info), _) => Some(info),
(Err(_), false) => {
let mut tmp = [0u8; ChunkTable::OFFSET_SIZE];
source.read_exact(&mut tmp)?;
None
}
(Err(err), true) => {
return Err(err);
}
}
}
CompressorType::LayeredChunked => {
let seek_info = SeekInfo::read_from(&mut source, &vlr).ok();
if seek_info.is_none() {
let mut tmp = [0u8; ChunkTable::OFFSET_SIZE];
source.read_exact(&mut tmp)?;
}
seek_info
}
_ => {
return Err(LasZipError::UnsupportedCompressorType(vlr.compressor));
}
};
let mut record_decompressor =
details::record_decompressor_from_laz_items(&vlr.items(), source)?;
record_decompressor.set_selection(selection);
Ok(Self {
vlr,
record_decompressor,
selection,
seek_info,
current_chunk: 0,
chunk_points_read: 0,
num_points_in_chunk: 1,
})
}
pub fn decompress_one(&mut self, mut out: &mut [u8]) -> std::io::Result<()> {
if self.chunk_points_read == self.num_points_in_chunk {
self.reset_for_new_chunk();
self.current_chunk += 1;
}
self.record_decompressor.decompress_next(&mut out)?;
self.chunk_points_read += 1;
if self.chunk_points_read == 1 {
if self.vlr.uses_variable_size_chunks() {
self.num_points_in_chunk = match (&self.seek_info, self.vlr.compressor) {
(Some(seek_info), _) => seek_info.chunk_table[self.current_chunk].point_count,
(None, CompressorType::LayeredChunked) => {
self.record_decompressor.record_count()
}
(None, _) => {
panic!("Variable-size chunks, but no chunk table");
}
}
} else if self.vlr.compressor == CompressorType::PointWise {
self.num_points_in_chunk = u64::from(u32::MAX);
} else {
self.num_points_in_chunk = u64::from(self.vlr.chunk_size());
}
}
Ok(())
}
pub fn decompress_many(&mut self, out: &mut [u8]) -> std::io::Result<()> {
for point in out.chunks_exact_mut(self.vlr.items_size() as usize) {
self.decompress_one(point)?;
}
Ok(())
}
pub fn seek(&mut self, point_idx: u64) -> crate::Result<()> {
let SeekInfo {
data_start,
chunk_table,
} = self.seek_info.as_ref().ok_or(MissingChunkTable)?;
let chunk_info = chunk_table
.chunk_of_point(point_idx)
.map(|(idx, offset)| (idx, offset + data_start));
if let Some((chunk_of_point, start_of_chunk)) = chunk_info {
self.current_chunk = chunk_of_point as usize;
let delta = point_idx % chunk_table[self.current_chunk].point_count;
let seeked_point_belong_to_last_chunk = chunk_of_point == (chunk_table.len() - 1);
if seeked_point_belong_to_last_chunk
&& self.vlr.compressor != CompressorType::LayeredChunked
{
let mut tmp_out = vec![0u8; self.record_decompressor.record_size()];
self.get_mut().seek(SeekFrom::Start(start_of_chunk))?;
self.reset_for_new_chunk();
let offset_to_chunk_table = self
.seek_info
.as_ref()
.map(SeekInfo::offset_to_chunk_table)
.unwrap();
for _i in 0..delta {
self.decompress_one(&mut tmp_out)?;
let current_pos = self.get_mut().seek(SeekFrom::Current(0))?;
if current_pos >= offset_to_chunk_table {
self.get_mut().seek(SeekFrom::End(0))?;
return Ok(());
}
}
} else {
self.get_mut().seek(SeekFrom::Start(start_of_chunk))?;
self.reset_for_new_chunk();
let mut tmp_out = vec![0u8; self.record_decompressor.record_size()];
for _i in 0..delta {
self.decompress_one(&mut tmp_out)?;
}
}
} else {
self.record_decompressor.get_mut().seek(SeekFrom::End(0))?;
}
Ok(())
}
pub fn vlr(&self) -> &LazVlr {
&self.vlr
}
pub fn into_inner(self) -> R {
self.record_decompressor.box_into_inner()
}
pub fn get_mut(&mut self) -> &mut R {
self.record_decompressor.get_mut()
}
pub fn get(&self) -> &R {
self.record_decompressor.get()
}
#[inline(always)]
fn reset_for_new_chunk(&mut self) {
self.chunk_points_read = 0;
self.record_decompressor.reset();
self.record_decompressor
.set_fields_from(&self.vlr.items())
.unwrap();
self.record_decompressor.set_selection(self.selection);
}
}
impl<'a, R: Read + Seek + Send + Sync + 'a> crate::LazDecompressor for LasZipDecompressor<'a, R> {
fn decompress_one(&mut self, point: &mut [u8]) -> crate::Result<()> {
LasZipDecompressor::decompress_one(self, point)?;
Ok(())
}
fn decompress_many(&mut self, points: &mut [u8]) -> crate::Result<()> {
LasZipDecompressor::decompress_many(self, points)?;
Ok(())
}
fn seek(&mut self, index: u64) -> crate::Result<()> {
LasZipDecompressor::seek(self, index)?;
Ok(())
}
}
pub fn decompress_buffer(
compressed_points_data: &[u8],
decompressed_points: &mut [u8],
laz_vlr: LazVlr,
) -> crate::Result<()> {
debug_assert_eq!(decompressed_points.len() % laz_vlr.items_size() as usize, 0);
let src = std::io::Cursor::new(compressed_points_data);
LasZipDecompressor::new(src, laz_vlr).and_then(|mut decompressor| {
decompressor.decompress_many(decompressed_points)?;
Ok(())
})
}