use crate::laszip::details::record_decompressor_from_laz_items;
use crate::laszip::{ChunkTable, CompressorType};
use crate::{LasZipCompressor, LasZipError, LazCompressor, LazVlr};
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
pub(crate) fn prepare_compressor_for_appending<W, F, F2, Compressor>(
mut data: W,
vlr: LazVlr,
point_count: u64,
compressor_creator: F,
get_mut_dest_of_compressor: F2,
) -> crate::Result<(Compressor, ChunkTable)>
where
W: Write + Read + Seek + Sync,
F: FnOnce(W, LazVlr) -> crate::Result<Compressor>,
F2: FnOnce(&mut Compressor) -> &mut W,
Compressor: LazCompressor,
{
if vlr.compressor != CompressorType::PointWiseChunked
&& vlr.compressor != CompressorType::LayeredChunked
{
return Err(LasZipError::UnsupportedCompressorType(vlr.compressor));
}
let start_of_data = data.stream_position()?;
let mut chunk_table = ChunkTable::read_from(&mut data, &vlr)?;
let mut data_to_recompress = vec![];
if !chunk_table.is_empty() {
let (chunk_index, chunk_start_pos) = chunk_table
.chunk_of_point(point_count - 1)
.unwrap_or_else(|| {
(
chunk_table.len() - 1,
chunk_table.chunk_position(chunk_table.len() - 1).unwrap(),
)
});
data.seek(SeekFrom::Current(chunk_start_pos.try_into().unwrap()))?;
let points_before_chunk = chunk_table[..chunk_index]
.iter()
.map(|entry| entry.point_count)
.sum::<u64>();
let mut points_to_read = point_count - points_before_chunk;
if !vlr.uses_variable_size_chunks()
&& chunk_index == chunk_table.len() - 1
&& points_to_read == 0
{
points_to_read = u64::from(vlr.chunk_size());
}
if points_to_read > 0 {
let size_of_chunk = chunk_table[chunk_index].byte_count;
let mut chunk_data = vec![0u8; size_of_chunk as usize];
data.read_exact(&mut chunk_data)?;
let mut chunk_data = Cursor::new(chunk_data);
let mut decompressor =
record_decompressor_from_laz_items(vlr.items(), &mut chunk_data)?;
data_to_recompress.resize((points_to_read * vlr.items_size()) as usize, 0);
let byte_len = decompressor.decompress_until_end_of_file(&mut data_to_recompress)?;
data_to_recompress.resize(byte_len, 0);
}
chunk_table.truncate(chunk_index);
}
data.seek(SeekFrom::Start(start_of_data))?;
let mut compressor = compressor_creator(data, vlr)?;
compressor.reserve_offset_to_chunk_table()?;
let last_chunk_pos = chunk_table.chunk_position(chunk_table.len()).unwrap();
get_mut_dest_of_compressor(&mut compressor).seek(SeekFrom::Current(last_chunk_pos as i64))?;
if !data_to_recompress.is_empty() {
compressor.compress_many(&data_to_recompress)?;
}
Ok((compressor, chunk_table))
}
pub struct LasZipAppender<'a, W: Write + Send + 'a> {
saved_chunk_table: ChunkTable,
compressor: LasZipCompressor<'a, W>,
}
impl<'a, W> LasZipAppender<'a, W>
where
W: Read + Write + Seek + Send + Sync + 'a,
{
pub fn new(data: W, vlr: LazVlr, point_count: u64) -> crate::Result<Self> {
let (compressor, chunk_table) = prepare_compressor_for_appending(
data,
vlr,
point_count,
LasZipCompressor::new,
LasZipCompressor::get_mut,
)?;
Ok(Self {
saved_chunk_table: chunk_table,
compressor,
})
}
pub fn done(&mut self) -> crate::Result<()> {
self.compressor.done()?;
let pos = self.compressor.chunk_table_position_offset() as u64;
self.compressor.get_mut().seek(SeekFrom::Start(pos))?;
let (_, chunk_table_pos) = ChunkTable::read_offset(self.compressor.get_mut())?
.expect("Somehow, the chunk table was not written");
self.saved_chunk_table.extend(self.compressor.chunk_table());
let write_point_count = self.compressor.vlr().uses_variable_size_chunks();
let dest = self.compressor.get_mut();
dest.seek(SeekFrom::Start(chunk_table_pos))?;
self.saved_chunk_table.write(dest, write_point_count)?;
Ok(())
}
pub fn get_mut(&mut self) -> &mut W {
self.compressor.get_mut()
}
pub fn get(&self) -> &W {
self.compressor.get()
}
pub fn into_inner(self) -> W {
self.compressor.into_inner()
}
}
impl<'a, W> LasZipAppender<'a, W>
where
W: Write + Seek + Send + Sync + 'a,
{
pub fn compress_one(&mut self, input: &[u8]) -> std::io::Result<()> {
self.compressor.compress_one(input)
}
pub fn compress_many(&mut self, points: &[u8]) -> std::io::Result<()> {
self.compressor.compress_many(points)
}
pub fn compress_chunks<Chunks, Item>(&mut self, chunks: Chunks) -> std::io::Result<()>
where
Item: AsRef<[u8]> + Send,
Chunks: IntoIterator<Item = Item>,
{
self.compressor.compress_chunks(chunks)
}
pub fn finish_current_chunk(&mut self) -> std::io::Result<()> {
self.compressor.finish_current_chunk()
}
}