use std::io::{Seek, SeekFrom, Write};
use byteorder::{LittleEndian, WriteBytesExt};
use crate::laszip::chunk_table::{ChunkTable, ChunkTableEntry};
use crate::laszip::CompressorType;
use crate::record::RecordCompressor;
use crate::LasZipError;
use crate::laszip::{chunk_table, details, LazItem, LazVlr};
pub struct LasZipCompressor<'a, W: Write + Send + 'a> {
vlr: LazVlr,
record_compressor: Box<dyn RecordCompressor<W> + Send + Sync + 'a>,
start_pos: u64,
chunk_table: ChunkTable,
current_chunk_entry: ChunkTableEntry,
chunk_start_pos: u64,
}
impl<'a, W: Write + Seek + Send + Sync + 'a> LasZipCompressor<'a, W> {
pub fn new(output: W, vlr: LazVlr) -> crate::Result<Self> {
if vlr.compressor != CompressorType::PointWiseChunked
&& vlr.compressor != CompressorType::LayeredChunked
{
return Err(LasZipError::UnsupportedCompressorType(vlr.compressor));
}
let record_compressor = details::record_compressor_from_laz_items(&vlr.items(), output)?;
Ok(Self {
vlr,
record_compressor,
chunk_start_pos: 0,
start_pos: 0,
chunk_table: ChunkTable::default(),
current_chunk_entry: ChunkTableEntry::default(),
})
}
pub fn from_laz_items(output: W, items: Vec<LazItem>) -> crate::Result<Self> {
let vlr = LazVlr::from_laz_items(items);
Self::new(output, vlr)
}
pub fn compress_one(&mut self, input: &[u8]) -> std::io::Result<()> {
if self.chunk_start_pos == 0 {
self.reserve_offset_to_chunk_table()?;
}
if self.current_chunk_entry.point_count == self.vlr.chunk_size() as u64 {
self.finish_current_chunk_impl()?;
}
self.record_compressor.compress_next(&input)?;
self.current_chunk_entry.point_count += 1;
Ok(())
}
pub fn compress_many(&mut self, input: &[u8]) -> std::io::Result<()> {
for point in input.chunks_exact(self.vlr.items_size() as usize) {
self.compress_one(point)?;
}
Ok(())
}
pub fn compress_chunks<Chunks, Item>(&mut self, chunks: Chunks) -> std::io::Result<()>
where
Item: AsRef<[u8]>,
Chunks: IntoIterator<Item = Item>,
{
debug_assert!(self.vlr.uses_variable_size_chunks());
for chunks in chunks.into_iter() {
let chunk_points = chunks.as_ref();
self.compress_many(chunk_points)?;
self.finish_current_chunk_impl()?;
}
Ok(())
}
pub fn done(&mut self) -> std::io::Result<()> {
if self.chunk_start_pos == 0 {
self.reserve_offset_to_chunk_table()?;
}
self.record_compressor.done()?;
self.update_chunk_table()?;
let stream = self.record_compressor.get_mut();
chunk_table::update_chunk_table_offset(stream, SeekFrom::Start(self.start_pos))?;
self.chunk_table.write_to(stream, &self.vlr)?;
Ok(())
}
pub fn finish_current_chunk(&mut self) -> std::io::Result<()> {
debug_assert!(
self.vlr.uses_variable_size_chunks(),
"finish_current_chunk called on a file which is not in variable-size chunks mode"
);
self.finish_current_chunk_impl()
}
pub fn reserve_offset_to_chunk_table(&mut self) -> std::io::Result<()> {
debug_assert_eq!(self.chunk_start_pos, 0);
let stream = self.record_compressor.get_mut();
self.start_pos = stream.seek(SeekFrom::Current(0))?;
stream.write_i64::<LittleEndian>(-1)?;
self.chunk_start_pos = self.start_pos + std::mem::size_of::<i64>() as u64;
Ok(())
}
pub fn vlr(&self) -> &LazVlr {
&self.vlr
}
pub fn into_inner(self) -> W {
self.record_compressor.box_into_inner()
}
pub fn get_mut(&mut self) -> &mut W {
self.record_compressor.get_mut()
}
pub fn get(&self) -> &W {
self.record_compressor.get()
}
pub(crate) fn chunk_table_position_offset(&self) -> i64 {
self.start_pos as i64
}
pub(crate) fn chunk_table(&self) -> &ChunkTable {
&self.chunk_table
}
#[inline]
fn update_chunk_table(&mut self) -> std::io::Result<()> {
let current_pos = self
.record_compressor
.get_mut()
.seek(SeekFrom::Current(0))?;
self.current_chunk_entry.byte_count = current_pos - self.chunk_start_pos;
self.chunk_start_pos = current_pos;
self.chunk_table.push(self.current_chunk_entry);
Ok(())
}
#[inline]
fn finish_current_chunk_impl(&mut self) -> std::io::Result<()> {
self.record_compressor.done()?;
self.record_compressor.reset();
self.record_compressor
.set_fields_from(&self.vlr.items())
.unwrap();
self.update_chunk_table()?;
self.current_chunk_entry = ChunkTableEntry::default();
Ok(())
}
}
impl<'a, W: Write + Seek + Send + Sync + 'a> crate::LazCompressor for LasZipCompressor<'a, W> {
fn compress_one(&mut self, point: &[u8]) -> crate::Result<()> {
LasZipCompressor::compress_one(self, point)?;
Ok(())
}
fn compress_many(&mut self, points: &[u8]) -> crate::Result<()> {
LasZipCompressor::compress_many(self, points)?;
Ok(())
}
fn reserve_offset_to_chunk_table(&mut self) -> crate::Result<()> {
LasZipCompressor::reserve_offset_to_chunk_table(self)?;
Ok(())
}
fn done(&mut self) -> crate::Result<()> {
LasZipCompressor::done(self)?;
Ok(())
}
}
impl<'a, W: Write + Seek + Send + Sync + 'a> crate::LazCompressorWithInner<W>
for LasZipCompressor<'a, W>
{
fn into_inner(self) -> W {
self.into_inner()
}
fn inner(&self) -> &W {
self.get()
}
fn inner_mut(&mut self) -> &mut W {
self.get_mut()
}
}
pub fn compress_buffer<W: Write + Seek + Send + Sync>(
dst: &mut W,
uncompressed_points: &[u8],
laz_vlr: LazVlr,
) -> crate::Result<()> {
debug_assert_eq!(uncompressed_points.len() % laz_vlr.items_size() as usize, 0);
let mut compressor = LasZipCompressor::new(dst, laz_vlr)?;
compressor.compress_many(uncompressed_points)?;
compressor.done()?;
Ok(())
}