Skip to content

Commit

Permalink
refactor!: heavily reorganize reading
Browse files Browse the repository at this point in the history
  • Loading branch information
gadomski committed Aug 15, 2024
1 parent 18bbcb4 commit 96e456c
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 284 deletions.
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Several methods in `Reader` ([#89](https://github.com/gadomski/las-rs/pull/89))
- `Reader::read_points`, which returns a `Result<Option<Point>>`
- `Reader::read_points`
- `Reader::read_points_into`
- `Reader::read_all_points_into`

### Changed

- BREAKING: Conslidate errors to a single enum ([#87](https://github.com/gadomski/las-rs/pull/87))
- Reorganize reading, including removing the lifetime specifier on `Reader` ([#89](https://github.com/gadomski/las-rs/pull/89))
- Conslidate errors to a single enum ([#87](https://github.com/gadomski/las-rs/pull/87))

## Deprecated

- `Read` trait ([#88](https://github.com/gadomski/las-rs/pull/88))
- Many methods on `Reader` ([#89](https://github.com/gadomski/las-rs/pull/89))
- `read` in favor of `read_point`
- `read_n` in favor of `read_points`
- `read_n_into` in favor of `read_points_into`
- `read_all_points` in favor of `read_all_points_into`

## [0.8.8] - 2024-05-30

Expand Down
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ pub enum Error {
#[error("too many variable length records: {0}")]
TooManyVlrs(usize),

/// [std::num::TryFromIntError]
#[error(transparent)]
TryFromIntError(#[from] std::num::TryFromIntError),

/// Feature is not supported by version.
#[error("feature {feature} is not supported by version {version}")]
UnsupportedFeature {
Expand Down
4 changes: 2 additions & 2 deletions src/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! A [Reader](crate::Reader) uses a [Header] to expose metadata:
//!
//! ```
//! use las::{Read, Reader};
//! use las::Reader;
//! let reader = Reader::from_path("tests/data/autzen.las").unwrap();
//! let header = reader.header();
//! println!("The file has {} points.", header.number_of_points());
Expand All @@ -29,7 +29,7 @@
//!
//! ```
//! use std::io::Cursor;
//! use las::{Writer, Reader, Read};
//! use las::{Writer, Reader};
//!
//! let header = Reader::from_path("tests/data/autzen.las").unwrap().header().clone();
//! let writer = Writer::new(Cursor::new(Vec::new()), header).unwrap();
Expand Down
119 changes: 1 addition & 118 deletions src/laz.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use crate::{
error::Error,
reader::{read_point_from, PointReader},
writer::{write_header_and_vlrs_to, write_point_to, PointWriter},
Header, Point, Result, Vlr,
};
use laz::las::laszip::LazVlr;
use std::fmt::Debug;
/// Module with functions and structs specific to brigde the las crate and laz crate to allow
/// writing & reading LAZ data
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::io::{Cursor, Seek, SeekFrom, Write};

fn is_laszip_vlr(vlr: &Vlr) -> bool {
vlr.user_id == LazVlr::USER_ID && vlr.record_id == LazVlr::RECORD_ID
Expand All @@ -25,121 +23,6 @@ fn create_laszip_vlr(laszip_vlr: &LazVlr) -> std::io::Result<Vlr> {
})
}

/// struct that knows how to decompress LAZ
///
/// Decompression is done in 2 steps:
///
/// 1. call the decompressor that reads & decompress the next point
/// and put its data in an in-memory buffer
/// 2. read the buffer to get the decompress point
pub(crate) struct CompressedPointReader<Decompressor> {
/// decompressor that does the actual job
decompressor: Decompressor,
header: Header,
/// in-memory buffer where the decompressor writes decompression result
decompressor_output: Cursor<Vec<u8>>,
last_point_idx: u64,
}

#[cfg(not(feature = "laz-parallel"))]
impl<R: Read + Seek + Send> CompressedPointReader<laz::LasZipDecompressor<'_, R>> {
pub(crate) fn new(source: R, header: Header) -> Result<Self> {
let laszip_vlr = match header.vlrs().iter().find(|vlr| is_laszip_vlr(vlr)) {
None => return Err(Error::LasZipVlrNotFound),
Some(vlr) => LazVlr::from_buffer(&vlr.data)?,
};
let decompressor_output = Cursor::new(vec![0u8; header.point_format().len() as usize]);

Ok(Self {
decompressor: laz::LasZipDecompressor::new(source, laszip_vlr)?,
header,
decompressor_output,
last_point_idx: 0,
})
}
}

#[cfg(feature = "laz-parallel")]
impl<R: Read + Seek + Send> CompressedPointReader<laz::ParLasZipDecompressor<R>> {
pub(crate) fn new(source: R, header: Header) -> Result<Self> {
let laszip_vlr = match header.vlrs().iter().find(|vlr| is_laszip_vlr(vlr)) {
None => return Err(Error::LasZipVlrNotFound),
Some(vlr) => LazVlr::from_buffer(&vlr.data)?,
};
let decompressor_output = Cursor::new(vec![0u8; header.point_format().len() as usize]);

Ok(Self {
decompressor: laz::ParLasZipDecompressor::new(source, laszip_vlr)?,
header,
decompressor_output,
last_point_idx: 0,
})
}
}

impl<Decompressor> Debug for CompressedPointReader<Decompressor> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"CompressedPointReader(num_read: {}, header: {:?})",
self.last_point_idx, self.header
)
}
}

impl<Decompressor> PointReader for CompressedPointReader<Decompressor>
where
Decompressor: laz::LazDecompressor + Send,
{
fn read_next(&mut self) -> Option<Result<Point>> {
if self.last_point_idx < self.header.number_of_points() {
self.last_point_idx += 1;
let res = self
.decompressor
.decompress_one(self.decompressor_output.get_mut());
if let Err(e) = res {
Some(Err(e.into()))
} else {
self.decompressor_output.set_position(0);
Some(read_point_from(&mut self.decompressor_output, &self.header))
}
} else {
None
}
}

fn read_into_vec(&mut self, points: &mut Vec<Point>, n: u64) -> Result<u64> {
let points_left = self.header().number_of_points() - self.last_point_idx;
let num_points_to_read = points_left.min(n);

self.decompressor_output.get_mut().resize(
num_points_to_read as usize * self.header.point_format().len() as usize,
0u8,
);
self.decompressor
.decompress_many(self.decompressor_output.get_mut())?;
self.decompressor_output.set_position(0);
points.reserve(num_points_to_read as usize);

for _ in 0..num_points_to_read {
let point = read_point_from(&mut self.decompressor_output, &self.header)?;
self.last_point_idx += 1;
points.push(point);
}
Ok(num_points_to_read)
}

fn seek(&mut self, position: u64) -> Result<()> {
self.last_point_idx = position;
self.decompressor.seek(position)?;
Ok(())
}

fn header(&self) -> &Header {
&self.header
}
}

fn laz_vlr_from_point_format(point_format: &crate::point::Format) -> LazVlr {
let mut laz_items = laz::las::laszip::LazItemRecordBuilder::new();
if !point_format.is_extended {
Expand Down
5 changes: 2 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@
//! Read points one-by-one with [Reader::read]:
//!
//! ```
//! use las::{Read, Reader};
//! use las::Reader;
//! let mut reader = Reader::from_path("tests/data/autzen.las").unwrap();
//! let point = reader.read().unwrap().unwrap();
//! ```
//!
//! Or iterate over all points with [Reader::points]:
//!
//! ```
//! use las::{Read, Reader};
//! use las::Reader;
//! let mut reader = Reader::from_path("tests/data/autzen.las").unwrap();
//! for wrapped_point in reader.points() {
//! let point = wrapped_point.unwrap();
Expand Down Expand Up @@ -171,7 +171,6 @@
unused_results,
warnings
)]
#![recursion_limit = "128"]

#[cfg(feature = "laz")]
mod laz;
Expand Down
64 changes: 64 additions & 0 deletions src/reader/las.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use super::ReadPoints;
use crate::{raw, Header, Point, Result};
use std::io::{Read, Seek, SeekFrom};

pub(crate) struct PointReader<R: Read + Seek> {
read: R,
header: Header,
index: u64,
start: u64,
}

impl<R: Read + Seek> PointReader<R> {
pub(crate) fn new(header: Header, mut read: R) -> Result<PointReader<R>> {
Ok(PointReader {
start: read.stream_position()?,
read,
header,
index: 0,
})
}
}

impl<R: Read + Seek> ReadPoints for PointReader<R> {
fn read_point(&mut self) -> Result<Option<Point>> {
if self.index < self.header.number_of_points() {
self.index += 1;
raw::Point::read_from(&mut self.read, self.header.point_format())
.map(|p| Point::new(p, self.header.transforms()))
.map(Some)
} else {
Ok(None)
}
}

fn read_points(&mut self, n: u64, points: &mut Vec<Point>) -> Result<u64> {
let points_left = self.header.number_of_points() - self.index;
let n = points_left.min(n);
if let Ok(n) = usize::try_from(n) {
points.reserve(n);
}
let mut count = 0;
for _ in 0..n {
if let Some(point) = self.read_point()? {
points.push(point);
count += 1;
} else {
break;
}
}
Ok(count)
}

fn seek(&mut self, index: u64) -> Result<()> {
self.index = index;
let _ = self.read.seek(SeekFrom::Start(
self.start + index * u64::from(self.header.point_format().len()),
))?;
Ok(())
}

fn header(&self) -> &Header {
&self.header
}
}
107 changes: 107 additions & 0 deletions src/reader/laz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use super::ReadPoints;
use crate::{raw, Error, Header, Point, Result, Vlr};
use laz::{LazDecompressor, LazVlr};
use std::io::{Cursor, Read, Seek};

pub(crate) struct PointReader<D: LazDecompressor> {
buffer: Cursor<Vec<u8>>,
decompressor: D,
header: Header,
index: u64,
}

#[cfg(feature = "laz-parallel")]
impl<R: Read + Seek> PointReader<laz::ParLasZipDecompressor<R>> {
pub(crate) fn new(
header: Header,
read: R,
) -> Result<PointReader<laz::ParLasZipDecompressor<R>>> {
let (vlr, buffer) = vlr_and_buffer(&header)?;
let decompressor = laz::ParLasZipDecompressor::new(read, vlr)?;
Ok(PointReader {
decompressor,
header,
buffer,
index: 0,
})
}
}

#[cfg(not(feature = "laz-parallel"))]
impl<R: Read + Seek + Send> PointReader<laz::LasZipDecompressor<'_, R>> {
pub(crate) fn new(
header: Header,
read: R,
) -> Result<PointReader<laz::LasZipDecompressor<'static, R>>> {
let (vlr, buffer) = vlr_and_buffer(&header)?;
let decompressor = laz::LasZipDecompressor::new(read, vlr)?;
Ok(PointReader {
decompressor,
header,
buffer,
index: 0,
})
}
}

impl<D> ReadPoints for PointReader<D>
where
D: LazDecompressor + Send,
{
fn read_point(&mut self) -> Result<Option<Point>> {
if self.index < self.header.number_of_points() {
self.index += 1;
self.decompressor.decompress_one(self.buffer.get_mut())?;
self.buffer.set_position(0);
raw::Point::read_from(&mut self.buffer, self.header.point_format())
.map(|raw_point| Point::new(raw_point, self.header.transforms()))
.map(Some)
} else {
Ok(None)
}
}

fn read_points(&mut self, n: u64, points: &mut Vec<Point>) -> Result<u64> {
let points_left = self.header.number_of_points() - self.index;
let n = points_left.min(n);

let resize = usize::try_from(n * 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);
if let Ok(n) = usize::try_from(n) {
points.reserve(n);
}

for _ in 0..n {
let point = raw::Point::read_from(&mut self.buffer, self.header.point_format())
.map(|raw_point| Point::new(raw_point, self.header.transforms()))?;
self.index += 1;
points.push(point);
}
Ok(n)
}

fn seek(&mut self, index: u64) -> Result<()> {
self.index = index;
self.decompressor.seek(index)?;
Ok(())
}

fn header(&self) -> &Header {
&self.header
}
}

fn is_laszip_vlr(vlr: &Vlr) -> bool {
vlr.user_id == LazVlr::USER_ID && vlr.record_id == LazVlr::RECORD_ID
}

fn vlr_and_buffer(header: &Header) -> Result<(LazVlr, Cursor<Vec<u8>>)> {
let vlr = match header.vlrs().iter().find(|vlr| is_laszip_vlr(vlr)) {
None => return Err(Error::LasZipVlrNotFound),
Some(vlr) => LazVlr::from_buffer(&vlr.data)?,
};
let buffer = Cursor::new(vec![0u8; header.point_format().len().into()]);
Ok((vlr, buffer))
}
Loading

0 comments on commit 96e456c

Please sign in to comment.