Skip to content

Commit

Permalink
refactor!: move write functions to modules
Browse files Browse the repository at this point in the history
  • Loading branch information
gadomski committed Aug 15, 2024
1 parent 01074d5 commit ee921b9
Show file tree
Hide file tree
Showing 15 changed files with 288 additions and 238 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Reader::read_points`
- `Reader::read_points_into`
- `Reader::read_all_points_into`
- `Writer::write_point`, `Header::write_to`, `laz` module, a few laz-specific methods on `Header` ([#90](https://github.com/gadomski/las-rs/pull/90))

### Changed

Expand All @@ -23,11 +24,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Deprecated

- `Read` trait ([#88](https://github.com/gadomski/las-rs/pull/88))
- `Write` trait ([#90](https://github.com/gadomski/las-rs/pull/90))
- 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`
- `Writer::write` ([#90](https://github.com/gadomski/las-rs/pull/90))

## [0.8.8] - 2024-05-30

Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ laz-parallel = ["dep:laz", "laz/parallel"]
[[bench]]
name = "roundtrip"
harness = false

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
2 changes: 1 addition & 1 deletion benches/roundtrip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use las::{Point, Reader, Writer};
fn roundtrip(npoints: usize) {
let mut writer = Writer::default();
for _ in 0..npoints {
writer.write(Point::default()).unwrap();
writer.write_point(Point::default()).unwrap();
}
let mut reader = Reader::new(writer.into_inner().unwrap()).unwrap();
for point in reader.points() {
Expand Down
37 changes: 30 additions & 7 deletions src/header/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ use crate::{
Vector, Version, Vlr,
};
use chrono::{Datelike, NaiveDate, Utc};
use std::{collections::HashMap, iter::Chain, slice::Iter};
use std::{collections::HashMap, io::Write, iter::Chain, slice::Iter};
use thiserror::Error;
use uuid::Uuid;

Expand Down Expand Up @@ -95,7 +95,7 @@ pub struct Header {
transforms: Vector<Transform>,
version: Version,
vlr_padding: Vec<u8>,
vlrs: Vec<Vlr>,
pub(crate) vlrs: Vec<Vlr>,
}

/// An iterator over a header's variable length records.
Expand Down Expand Up @@ -418,11 +418,6 @@ impl Header {
&self.vlrs
}

#[cfg(feature = "laz")]
pub(crate) fn vlrs_mut(&mut self) -> &mut Vec<Vlr> {
&mut self.vlrs
}

/// Returns a reference to header's extended variable length records.
///
/// # Examples
Expand Down Expand Up @@ -501,6 +496,34 @@ impl Header {
})
}

/// Writes this header to a [Write].
///
/// # Examples
///
/// ```
/// use std::io::Cursor;
/// use las::Header;
///
/// let header = Header::default();
/// let cursor = Cursor::new(Vec::new());
/// header.write_to(cursor).unwrap();
/// ```
pub fn write_to<W: Write>(&self, mut write: W) -> Result<()> {
self.clone()
.into_raw()
.and_then(|raw_header| raw_header.write_to(&mut write))?;
for vlr in self.vlrs() {
(*vlr)
.clone()
.into_raw(false)
.and_then(|raw_vlr| raw_vlr.write_to(&mut write))?;
}
if !self.vlr_padding().is_empty() {
write.write_all(self.vlr_padding())?;
}
Ok(())
}

fn global_encoding(&self) -> u16 {
let mut bits = self.gps_time_type.into();
if self.has_synthetic_return_numbers {
Expand Down
209 changes: 100 additions & 109 deletions src/laz.rs
Original file line number Diff line number Diff line change
@@ -1,125 +1,116 @@
use crate::{
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, Seek, SeekFrom, Write};
//! Utility functions for working with laszip compressed data.

fn is_laszip_vlr(vlr: &Vlr) -> bool {
vlr.user_id == LazVlr::USER_ID && vlr.record_id == LazVlr::RECORD_ID
}
use crate::{Error, Header, Result, Vlr};
use laz::{LazItemRecordBuilder, LazItemType, LazVlr};
use std::io::Cursor;

fn create_laszip_vlr(laszip_vlr: &LazVlr) -> std::io::Result<Vlr> {
let mut cursor = Cursor::new(Vec::<u8>::new());
laszip_vlr.write_to(&mut cursor)?;
Ok(Vlr {
user_id: LazVlr::USER_ID.to_owned(),
record_id: LazVlr::RECORD_ID,
description: LazVlr::DESCRIPTION.to_owned(),
data: cursor.into_inner(),
})
/// Returns true if this [Vlr] is the laszip Vlr.
///
/// # Examples
///
/// ```
/// #[cfg(feature = "laz")]
/// {
/// use las::{laz, Vlr};
///
/// let mut vlr = Vlr::default();
/// assert!(!laz::is_laszip_vlr(&vlr));
/// vlr.user_id = "laszip encoded".to_string();
/// vlr.record_id = 22204;
/// assert!(laz::is_laszip_vlr(&vlr));
/// }
/// ```
pub fn is_laszip_vlr(vlr: &Vlr) -> bool {
vlr.user_id == LazVlr::USER_ID && vlr.record_id == LazVlr::RECORD_ID
}

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 {
let _ = laz_items.add_item(laz::LazItemType::Point10);

if point_format.has_gps_time {
let _ = laz_items.add_item(laz::LazItemType::GpsTime);
}

if point_format.has_color {
let _ = laz_items.add_item(laz::LazItemType::RGB12);
}
impl Header {
/// Adds a new laszip vlr to this header.
///
/// Ensures that there's only one laszip vlr, as well.
///
/// # Examples
///
/// ```
/// use las::Header;
///
/// let mut header = Header::default();
/// #[cfg(feature = "laz")]
/// header.add_laz_vlr().unwrap();
/// ```
pub fn add_laz_vlr(&mut self) -> Result<()> {
let point_format = self.point_format();
let mut laz_items = LazItemRecordBuilder::new();
if !point_format.is_extended {
let _ = laz_items.add_item(LazItemType::Point10);

if point_format.has_gps_time {
let _ = laz_items.add_item(LazItemType::GpsTime);
}

if point_format.extra_bytes > 0 {
let _ = laz_items.add_item(laz::LazItemType::Byte(point_format.extra_bytes));
}
} else {
let _ = laz_items.add_item(laz::LazItemType::Point14);
if point_format.has_color {
let _ = laz_items.add_item(LazItemType::RGB12);
}

if point_format.has_color {
// Point format 7 & 8 both have RGB
if point_format.has_nir {
let _ = laz_items.add_item(laz::LazItemType::RGBNIR14);
} else {
let _ = laz_items.add_item(laz::LazItemType::RGB14);
if point_format.extra_bytes > 0 {
let _ = laz_items.add_item(LazItemType::Byte(point_format.extra_bytes));
}
} else {
let _ = laz_items.add_item(LazItemType::Point14);

if point_format.has_color {
// Point format 7 & 8 both have RGB
if point_format.has_nir {
let _ = laz_items.add_item(LazItemType::RGBNIR14);
} else {
let _ = laz_items.add_item(LazItemType::RGB14);
}
}
if point_format.extra_bytes > 0 {
let _ = laz_items.add_item(LazItemType::Byte14(point_format.extra_bytes));
}
}
if point_format.extra_bytes > 0 {
let _ = laz_items.add_item(laz::LazItemType::Byte14(point_format.extra_bytes));
}
}
LazVlr::from_laz_items(laz_items.build())
}

/// struct that knows how to write LAZ
///
/// Writing a point compressed is done in 2 steps
/// 1) write the point to a in-memory buffer
/// 2) call the laz compressor on this buffer
pub(crate) struct CompressedPointWriter<'a, W: Write + Seek + Send> {
header: Header,
/// buffer used to write the uncompressed point
compressor_input: Cursor<Vec<u8>>,
/// The compressor that actually does the job of compressing the data
compressor: laz::las::laszip::LasZipCompressor<'a, W>,
}

impl<W: Write + Seek + Send> CompressedPointWriter<'_, W> {
pub(crate) fn new(mut dest: W, mut header: Header) -> Result<Self> {
let laz_vlr = laz_vlr_from_point_format(header.point_format());
// Clear any existing laszip vlr as they might not be correct
header.vlrs_mut().retain(|vlr| !is_laszip_vlr(vlr));
header.vlrs_mut().push(create_laszip_vlr(&laz_vlr)?);

write_header_and_vlrs_to(&mut dest, &header)?;

let compressor_input = Cursor::new(vec![0u8; header.point_format().len() as usize]);
let compressor = laz::las::laszip::LasZipCompressor::new(dest, laz_vlr)?;

Ok(Self {
header,
compressor_input,
compressor,
})
}
}

impl<W: Write + Seek + Send> PointWriter<W> for CompressedPointWriter<'_, W> {
fn write_next(&mut self, point: Point) -> Result<()> {
self.header.add_point(&point);
let _ = self.compressor_input.seek(SeekFrom::Start(0))?;
write_point_to(&mut self.compressor_input, point, &self.header)?;
self.compressor
.compress_one(self.compressor_input.get_ref())?;
let laz_vlr = LazVlr::from_laz_items(laz_items.build());
let mut cursor = Cursor::new(Vec::<u8>::new());
laz_vlr.write_to(&mut cursor)?;
let vlr = Vlr {
user_id: LazVlr::USER_ID.to_owned(),
record_id: LazVlr::RECORD_ID,
description: LazVlr::DESCRIPTION.to_owned(),
data: cursor.into_inner(),
};
self.vlrs.push(vlr);
Ok(())
}

fn into_inner(self: Box<Self>) -> W {
self.compressor.into_inner()
}

fn get_mut(&mut self) -> &mut W {
self.compressor.get_mut()
}

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

fn done(&mut self) -> Result<()> {
self.compressor.done()?;
Ok(())
/// Returns header's [LazVlr], or `None` if none is found.
///
/// # Examples
///
/// ```
/// use las::Header;
///
/// let mut header = Header::default();
///
/// #[cfg(feature = "laz")]
/// {
/// assert!(header.laz_vlr().is_none());
/// header.add_laz_vlr();
/// assert!(header.laz_vlr().is_some());
/// }
/// ```
pub fn laz_vlr(&self) -> Option<LazVlr> {
self.vlrs
.iter()
.find(|vlr| is_laszip_vlr(vlr))
.and_then(|vlr| vlr.try_into().ok())
}
}

impl<W: Write + Seek + Send> Debug for CompressedPointWriter<'_, W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "CompressedPointWriter(header: {:?})", self.header)
impl TryFrom<&Vlr> for LazVlr {
type Error = Error;

fn try_from(vlr: &Vlr) -> Result<LazVlr> {
LazVlr::from_buffer(&vlr.data).map_err(Error::from)
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
)]

#[cfg(feature = "laz")]
mod laz;
pub mod laz;

pub mod feature;
pub mod header;
Expand Down
2 changes: 1 addition & 1 deletion src/reader/las.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub(crate) struct PointReader<R: Read + Seek> {
}

impl<R: Read + Seek> PointReader<R> {
pub(crate) fn new(header: Header, mut read: R) -> Result<PointReader<R>> {
pub(crate) fn new(mut read: R, header: Header) -> Result<PointReader<R>> {
Ok(PointReader {
start: read.stream_position()?,
read,
Expand Down
4 changes: 2 additions & 2 deletions src/reader/laz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ pub(crate) struct PointReader<D: LazDecompressor> {
#[cfg(feature = "laz-parallel")]
impl<R: Read + Seek> PointReader<laz::ParLasZipDecompressor<R>> {
pub(crate) fn new(
header: Header,
read: R,
header: Header,
) -> Result<PointReader<laz::ParLasZipDecompressor<R>>> {
let (vlr, buffer) = vlr_and_buffer(&header)?;
let decompressor = laz::ParLasZipDecompressor::new(read, vlr)?;
Expand All @@ -30,8 +30,8 @@ impl<R: Read + Seek> PointReader<laz::ParLasZipDecompressor<R>> {
#[cfg(not(feature = "laz-parallel"))]
impl<R: Read + Seek + Send> PointReader<laz::LasZipDecompressor<'_, R>> {
pub(crate) fn new(
header: Header,
read: R,
header: Header,
) -> Result<PointReader<laz::LasZipDecompressor<'static, R>>> {
let (vlr, buffer) = vlr_and_buffer(&header)?;
let decompressor = laz::LasZipDecompressor::new(read, vlr)?;
Expand Down
Loading

0 comments on commit ee921b9

Please sign in to comment.