use std::io::{Read, Seek, SeekFrom, Write};
use std::ops::Index;
use std::slice::SliceIndex;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use crate::compressors::IntegerCompressorBuilder;
use crate::decoders::ArithmeticDecoder;
use crate::decompressors::IntegerDecompressorBuilder;
use crate::encoders::ArithmeticEncoder;
use crate::{LasZipError, LazVlr};
const POINT_COUNT_CONTEXT: u32 = 0;
const BYTE_COUNT_CONTEXT: u32 = 1;
#[derive(Copy, Clone, Debug, Default)]
pub struct ChunkTableEntry {
pub point_count: u64,
pub byte_count: u64,
}
#[derive(Default, Debug, Clone)]
pub struct ChunkTable(Vec<ChunkTableEntry>);
impl ChunkTable {
pub const OFFSET_SIZE: usize = 8;
pub fn read_from<R: Read + Seek>(mut src: R, vlr: &LazVlr) -> crate::Result<Self> {
if vlr.uses_variable_size_chunks() {
ChunkTable::read_as_variably_sized(&mut src)
} else {
ChunkTable::read_as_fixed_size(&mut src, vlr.chunk_size().into())
}
}
pub fn write_to<W: Write>(&self, mut dst: W, vlr: &LazVlr) -> std::io::Result<()> {
self.write(&mut dst, vlr.uses_variable_size_chunks())
}
fn read_as_variably_sized<R: Read + Seek>(mut src: R) -> crate::Result<Self> {
let (data_start, chunk_table_start) =
Self::read_offset(&mut src)?.ok_or(LasZipError::MissingChunkTable)?;
src.seek(SeekFrom::Start(chunk_table_start))?;
let chunk_table = Self::read(&mut src, true)?;
src.seek(SeekFrom::Start(data_start + 8))?;
Ok(chunk_table)
}
fn read_as_fixed_size<R: Read + Seek>(mut src: R, point_count: u64) -> crate::Result<Self> {
let (data_start, chunk_table_start) =
Self::read_offset(&mut src)?.ok_or(LasZipError::MissingChunkTable)?;
src.seek(SeekFrom::Start(chunk_table_start))?;
let mut chunk_table = Self::read(&mut src, false)?;
src.seek(SeekFrom::Start(data_start + 8))?;
for entry in &mut chunk_table.0 {
entry.point_count = point_count;
}
Ok(chunk_table)
}
pub(crate) fn read_offset<R: Read + Seek>(src: &mut R) -> std::io::Result<Option<(u64, u64)>> {
let current_pos = src.seek(SeekFrom::Current(0))?;
let mut offset_to_chunk_table = src.read_i64::<LittleEndian>()?;
if offset_to_chunk_table <= current_pos as i64 {
src.seek(SeekFrom::End(-8))?;
offset_to_chunk_table = src.read_i64::<LittleEndian>()?;
if offset_to_chunk_table <= current_pos as i64 {
return Ok(None);
}
}
Ok(Some((current_pos, offset_to_chunk_table as u64)))
}
pub fn read<R: Read + Seek>(
mut src: &mut R,
contains_point_count: bool,
) -> std::io::Result<Self> {
let _version = src.read_u32::<LittleEndian>()?;
let number_of_chunks = src.read_u32::<LittleEndian>()?;
if number_of_chunks == 0 {
return Ok(Self::default());
}
let mut decompressor = IntegerDecompressorBuilder::new()
.bits(32)
.contexts(2)
.build_initialized();
let mut decoder = ArithmeticDecoder::new(&mut src);
decoder.read_init_bytes()?;
let mut chunk_table = ChunkTable::with_capacity(number_of_chunks as usize);
let mut previous_entry = ChunkTableEntry::default();
for _ in 1..=number_of_chunks {
let mut current_entry = ChunkTableEntry {
point_count: 0,
byte_count: 0,
};
if contains_point_count {
current_entry.point_count = u64::from_le(decompressor.decompress(
&mut decoder,
previous_entry.point_count as i32,
POINT_COUNT_CONTEXT,
)? as u64);
}
current_entry.byte_count = u64::from_le(decompressor.decompress(
&mut decoder,
previous_entry.byte_count as i32,
BYTE_COUNT_CONTEXT,
)? as u64);
chunk_table.0.push(current_entry);
previous_entry = current_entry;
}
Ok(chunk_table)
}
pub(crate) fn chunk_of_point(&self, point_idx: u64) -> Option<(usize, u64)> {
let mut chunk_of_point = 0usize;
let mut start_of_chunk = 0;
let mut tmp_count = 0;
for entry in &self.0 {
tmp_count += entry.point_count;
if tmp_count > point_idx {
break;
}
start_of_chunk += entry.byte_count;
chunk_of_point += 1;
}
if point_idx >= tmp_count {
None
} else {
Some((chunk_of_point, start_of_chunk))
}
}
pub(crate) fn chunk_position(&self, chunk_index: usize) -> Option<u64> {
if chunk_index > self.len() {
None
} else {
Some(
self.0[0..chunk_index]
.iter()
.map(|entry| entry.byte_count)
.sum(),
)
}
}
pub(crate) fn write<W: Write>(
&self,
mut dst: &mut W,
write_point_count: bool,
) -> std::io::Result<()> {
dst.write_u32::<LittleEndian>(0)?;
dst.write_u32::<LittleEndian>(self.len() as u32)?;
if self.is_empty() {
return Ok(());
}
let mut encoder = ArithmeticEncoder::new(&mut dst);
let mut compressor = IntegerCompressorBuilder::new()
.bits(32)
.contexts(2)
.build_initialized();
let mut previous_entry = ChunkTableEntry::default();
for current_entry in &self.0 {
if write_point_count {
compressor.compress(
&mut encoder,
previous_entry.point_count as i32,
current_entry.point_count as i32,
POINT_COUNT_CONTEXT,
)?;
previous_entry.point_count = current_entry.point_count;
}
compressor.compress(
&mut encoder,
previous_entry.byte_count as i32,
current_entry.byte_count as i32,
BYTE_COUNT_CONTEXT,
)?;
previous_entry.byte_count = current_entry.byte_count;
}
encoder.done()?;
Ok(())
}
pub fn with_capacity(capacity: usize) -> Self {
let vec = Vec::<ChunkTableEntry>::with_capacity(capacity);
Self { 0: vec }
}
pub fn push(&mut self, entry: ChunkTableEntry) {
self.0.push(entry);
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn pop(&mut self) -> Option<ChunkTableEntry> {
self.0.pop()
}
pub fn extend(&mut self, other: &ChunkTable) {
self.0.extend(&other.0)
}
pub fn truncate(&mut self, len: usize) {
self.0.truncate(len);
}
}
impl AsRef<[ChunkTableEntry]> for ChunkTable {
fn as_ref(&self) -> &[ChunkTableEntry] {
&self.0
}
}
impl<'a> IntoIterator for &'a ChunkTable {
type Item = <std::slice::Iter<'a, ChunkTableEntry> as Iterator>::Item;
type IntoIter = std::slice::Iter<'a, ChunkTableEntry>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<I> Index<I> for ChunkTable
where
I: SliceIndex<[ChunkTableEntry]>,
{
type Output = <I as SliceIndex<[ChunkTableEntry]>>::Output;
fn index(&self, index: I) -> &Self::Output {
&self.0[index]
}
}
pub(super) fn update_chunk_table_offset<W: Write + Seek>(
dst: &mut W,
offset_pos: SeekFrom,
) -> std::io::Result<()> {
let start_of_chunk_table_pos = dst.seek(SeekFrom::Current(0))?;
dst.seek(offset_pos)?;
dst.write_i64::<LittleEndian>(start_of_chunk_table_pos as i64)?;
dst.seek(SeekFrom::Start(start_of_chunk_table_pos))?;
Ok(())
}