pub use self::builder::Builder;
use crate::{
point::Format, raw, utils::FromLasStr, Bounds, Error, GpsTimeType, Point, Result, Transform,
Vector, Version, Vlr,
};
use chrono::{Datelike, NaiveDate, Utc};
use std::{
cmp::Ordering,
collections::HashMap,
io::{Read, Seek, SeekFrom, Write},
iter::Chain,
slice::Iter,
};
use thiserror::Error;
use uuid::Uuid;
mod builder;
#[derive(Clone, Debug, PartialEq)]
pub struct Header {
bounds: Bounds,
date: Option<NaiveDate>,
pub(crate) evlrs: Vec<Vlr>,
file_source_id: u16,
generating_software: String,
gps_time_type: GpsTimeType,
guid: Uuid,
has_synthetic_return_numbers: bool,
pub(crate) has_wkt_crs: bool,
number_of_points: u64,
number_of_points_by_return: HashMap<u8, u64>,
padding: Vec<u8>,
point_format: Format,
point_padding: Vec<u8>,
start_of_first_evlr: Option<u64>,
system_identifier: String,
transforms: Vector<Transform>,
version: Version,
vlr_padding: Vec<u8>,
pub(crate) vlrs: Vec<Vlr>,
}
#[derive(Debug)]
pub struct Vlrs<'a>(Chain<Iter<'a, Vlr>, Iter<'a, Vlr>>);
impl Header {
pub fn new<R: Read + Seek>(mut read: R) -> Result<Self> {
let raw_header = raw::Header::read_from(read.by_ref())?;
let mut position = u64::from(raw_header.header_size);
let number_of_variable_length_records = raw_header.number_of_variable_length_records;
let offset_to_point_data = u64::from(raw_header.offset_to_point_data);
let offset_to_end_of_points = raw_header.offset_to_end_of_points();
let evlr = raw_header.evlr;
let mut builder = Builder::new(raw_header)?;
for _ in 0..number_of_variable_length_records {
let vlr = raw::Vlr::read_from(read.by_ref(), false).map(Vlr::new)?;
position += vlr.len(false) as u64;
builder.vlrs.push(vlr);
}
match position.cmp(&offset_to_point_data) {
Ordering::Less => {
let _ = read
.by_ref()
.take(offset_to_point_data - position)
.read_to_end(&mut builder.vlr_padding)?;
}
Ordering::Equal => {} Ordering::Greater => {
return Err(Error::OffsetToPointDataTooSmall(
offset_to_point_data as u32,
))
}
}
let _ = read.seek(SeekFrom::Start(offset_to_end_of_points))?;
if let Some(evlr) = evlr {
if !builder.point_format.is_compressed {
match evlr.start_of_first_evlr.cmp(&offset_to_end_of_points) {
Ordering::Less => {
return Err(Error::OffsetToEvlrsTooSmall(evlr.start_of_first_evlr));
}
Ordering::Equal => {} Ordering::Greater => {
let n = evlr.start_of_first_evlr - offset_to_end_of_points;
let _ = read
.by_ref()
.take(n)
.read_to_end(&mut builder.point_padding)?;
}
}
}
let _ = read.seek(SeekFrom::Start(evlr.start_of_first_evlr))?;
builder
.evlrs
.push(raw::Vlr::read_from(read.by_ref(), true).map(Vlr::new)?);
}
let _ = read.seek(SeekFrom::Start(offset_to_point_data))?;
if let Some(version) = builder.minimum_supported_version()
&& version > builder.version
{
log::warn!(
"upgrading las version to {} (from {})",
version,
builder.version
);
builder.version = version;
}
builder.into_header()
}
pub fn from_raw(raw_header: raw::Header) -> Result<Header> {
Builder::new(raw_header).and_then(|b| b.into_header())
}
pub fn clear(&mut self) {
self.number_of_points = 0;
self.number_of_points_by_return = Default::default();
self.bounds = Default::default();
}
pub fn add_point(&mut self, point: &Point) {
self.number_of_points += 1;
if point.return_number > 0 {
let entry = self
.number_of_points_by_return
.entry(point.return_number)
.or_insert(0);
*entry += 1;
}
self.bounds.grow(point);
}
pub fn file_source_id(&self) -> u16 {
self.file_source_id
}
pub fn gps_time_type(&self) -> GpsTimeType {
self.gps_time_type
}
pub fn has_synthetic_return_numbers(&self) -> bool {
self.has_synthetic_return_numbers
}
pub fn has_wkt_crs(&self) -> bool {
self.has_wkt_crs
}
pub fn guid(&self) -> Uuid {
self.guid
}
pub fn version(&self) -> Version {
self.version
}
pub fn system_identifier(&self) -> &str {
&self.system_identifier
}
pub fn generating_software(&self) -> &str {
&self.generating_software
}
pub fn date(&self) -> Option<NaiveDate> {
self.date
}
pub fn padding(&self) -> &Vec<u8> {
&self.padding
}
pub fn point_format(&self) -> &Format {
&self.point_format
}
pub(crate) fn point_format_mut(&mut self) -> &mut Format {
&mut self.point_format
}
pub fn transforms(&self) -> &Vector<Transform> {
&self.transforms
}
pub fn bounds(&self) -> Bounds {
self.bounds
}
pub fn number_of_points(&self) -> u64 {
self.number_of_points
}
pub fn number_of_points_by_return(&self, n: u8) -> Option<u64> {
self.number_of_points_by_return.get(&n).copied()
}
pub fn vlr_padding(&self) -> &Vec<u8> {
&self.vlr_padding
}
pub fn point_padding(&self) -> &Vec<u8> {
&self.point_padding
}
pub fn vlrs(&self) -> &Vec<Vlr> {
&self.vlrs
}
pub fn evlrs(&self) -> &Vec<Vlr> {
&self.evlrs
}
pub fn all_vlrs(&self) -> Vlrs<'_> {
Vlrs(self.vlrs.iter().chain(&self.evlrs))
}
pub fn has_crs_vlrs(&self) -> bool {
self.all_vlrs().any(|v| v.is_crs())
}
pub fn into_raw(self) -> Result<raw::Header> {
let bounds = self.bounds.adapt(&self.transforms)?;
Ok(raw::Header {
file_signature: raw::LASF,
file_source_id: self.file_source_id,
global_encoding: self.global_encoding(),
guid: *self.guid.as_bytes(),
version: self.version,
system_identifier: self.system_identifier_raw()?,
generating_software: self.generating_software_raw()?,
file_creation_day_of_year: self.date.map_or(0, |d| d.ordinal() as u16),
file_creation_year: self.date.map_or(0, |d| d.year() as u16),
header_size: self.header_size()?,
offset_to_point_data: self.offset_to_point_data()?,
number_of_variable_length_records: self.number_of_variable_length_records()?,
point_data_record_format: self.point_format.to_writable_u8()?,
point_data_record_length: self.point_format.len(),
number_of_point_records: self.number_of_points_raw()?,
number_of_points_by_return: self.number_of_points_by_return_raw()?,
x_scale_factor: self.transforms.x.scale,
y_scale_factor: self.transforms.y.scale,
z_scale_factor: self.transforms.z.scale,
x_offset: self.transforms.x.offset,
y_offset: self.transforms.y.offset,
z_offset: self.transforms.z.offset,
max_x: bounds.max.x,
min_x: bounds.min.x,
max_y: bounds.max.y,
min_y: bounds.min.y,
max_z: bounds.max.z,
min_z: bounds.min.z,
start_of_waveform_data_packet_record: None,
evlr: self.evlr()?,
large_file: self.large_file()?,
padding: self.padding,
})
}
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(())
}
pub(crate) fn set_start_of_first_evlr(&mut self, start_of_first_evlr: u64) {
self.start_of_first_evlr = Some(start_of_first_evlr);
}
fn global_encoding(&self) -> u16 {
let mut bits = self.gps_time_type.into();
if self.has_synthetic_return_numbers {
bits |= 8;
}
if self.has_wkt_crs || self.point_format.is_extended {
bits |= 16;
}
bits
}
fn system_identifier_raw(&self) -> Result<[u8; 32]> {
let mut system_identifier = [0; 32];
system_identifier
.as_mut()
.from_las_str(&self.system_identifier)?;
Ok(system_identifier)
}
fn generating_software_raw(&self) -> Result<[u8; 32]> {
let mut generating_software = [0; 32];
generating_software
.as_mut()
.from_las_str(&self.generating_software)?;
Ok(generating_software)
}
fn header_size(&self) -> Result<u16> {
let header_size = self.version.header_size() as usize + self.padding.len();
if header_size > u16::MAX as usize {
Err(Error::HeaderTooLarge(header_size))
} else {
Ok(header_size as u16)
}
}
fn offset_to_point_data(&self) -> Result<u32> {
let vlr_len = self.vlrs.iter().fold(0, |acc, vlr| acc + vlr.len(false));
let offset = self.header_size()? as usize + vlr_len + self.vlr_padding.len();
if offset > u32::MAX as usize {
Err(Error::OffsetToPointDataTooLarge(offset))
} else {
Ok(offset as u32)
}
}
fn number_of_variable_length_records(&self) -> Result<u32> {
let n = self.vlrs().len();
if n > u32::MAX as usize {
Err(Error::TooManyVlrs(n))
} else {
Ok(n as u32)
}
}
fn number_of_points_raw(&self) -> Result<u32> {
use crate::feature::LargeFiles;
if self.number_of_points > u64::from(u32::MAX) {
if self.version.supports::<LargeFiles>() {
Ok(0)
} else {
Err(Error::TooManyPoints {
n: self.number_of_points,
version: self.version,
})
}
} else {
Ok(self.number_of_points as u32)
}
}
fn number_of_points_by_return_raw(&self) -> Result<[u32; 5]> {
use crate::feature::LargeFiles;
let mut number_of_points_by_return = [0; 5];
for (&i, &n) in &self.number_of_points_by_return {
if i > 5 {
if !self.version.supports::<LargeFiles>() {
return Err(Error::ReturnNumber {
return_number: i,
version: Some(self.version),
});
}
} else if i > 0 {
if n > u64::from(u32::MAX) {
if !self.version.supports::<LargeFiles>() {
return Err(Error::TooManyPoints {
n,
version: self.version,
});
}
} else {
number_of_points_by_return[i as usize - 1] = n as u32;
}
}
}
Ok(number_of_points_by_return)
}
fn evlr(&self) -> Result<Option<raw::header::Evlr>> {
let n = self.evlrs.len();
if n == 0 {
Ok(None)
} else if n > u32::MAX as usize {
Err(Error::TooManyEvlrs(n))
} else {
let start_of_first_evlr = if let Some(start_of_fist_evlr) = self.start_of_first_evlr {
start_of_fist_evlr
} else {
self.point_data_len()
+ self.point_padding.len() as u64
+ u64::from(self.offset_to_point_data()?)
};
Ok(Some(raw::header::Evlr {
start_of_first_evlr,
number_of_evlrs: n as u32,
}))
}
}
fn large_file(&self) -> Result<Option<raw::header::LargeFile>> {
let mut number_of_points_by_return = [0; 15];
for (&i, &n) in &self.number_of_points_by_return {
if i > 15 {
return Err(Error::ReturnNumber {
return_number: i,
version: Some(self.version),
});
} else if i > 0 {
number_of_points_by_return[i as usize - 1] = n;
}
}
Ok(Some(raw::header::LargeFile {
number_of_point_records: self.number_of_points,
number_of_points_by_return,
}))
}
fn point_data_len(&self) -> u64 {
self.number_of_points * u64::from(self.point_format.len())
}
}
impl Default for Header {
fn default() -> Header {
Header {
bounds: Default::default(),
date: Some(Utc::now().date_naive()),
evlrs: Vec::new(),
file_source_id: 0,
generating_software: format!("las-rs {}", env!("CARGO_PKG_VERSION")),
gps_time_type: GpsTimeType::Week,
guid: Default::default(),
has_synthetic_return_numbers: false,
has_wkt_crs: false,
number_of_points: 0,
number_of_points_by_return: HashMap::new(),
padding: Vec::new(),
point_format: Default::default(),
point_padding: Vec::new(),
start_of_first_evlr: None,
system_identifier: "las-rs".to_string(),
transforms: Default::default(),
version: Default::default(),
vlr_padding: Vec::new(),
vlrs: Vec::new(),
}
}
}
impl<V: Into<Version>> From<V> for Header {
fn from(version: V) -> Header {
Builder::from(version)
.into_header()
.expect("Default builder could not be converted into a header")
}
}
impl<'a> Iterator for Vlrs<'a> {
type Item = &'a Vlr;
fn next(&mut self) -> Option<&'a Vlr> {
self.0.next()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn number_of_points_by_return_zero_return_number() {
let mut header = Header::default();
header.add_point(&Default::default());
assert_eq!(
[0; 5],
header.into_raw().unwrap().number_of_points_by_return
);
}
#[test]
fn number_of_points_by_return_las_1_2() {
let mut header = Header::from((1, 2));
for i in 1..6 {
let point = Point {
return_number: i,
..Default::default()
};
for _ in 0..42 {
header.add_point(&point);
}
}
assert_eq!(
[42; 5],
header.into_raw().unwrap().number_of_points_by_return
);
}
#[test]
fn number_of_points_by_return_las_1_2_return_6() {
let mut header = Header::from((1, 2));
header.add_point(&Point {
return_number: 6,
..Default::default()
});
assert!(header.into_raw().is_err());
}
#[test]
fn synchronize_legacy_fields() {
let mut header = Header::from((1, 4));
let point = Point {
return_number: 2,
..Default::default()
};
for _ in 0..42 {
header.add_point(&point);
}
let raw_header = header.into_raw().unwrap();
assert_eq!(42, raw_header.number_of_point_records);
assert_eq!([0, 42, 0, 0, 0], raw_header.number_of_points_by_return);
assert_eq!(42, raw_header.large_file.unwrap().number_of_point_records);
assert_eq!(
[0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
raw_header.large_file.unwrap().number_of_points_by_return
);
}
#[test]
fn zero_legacy_fields_when_too_large() {
let mut header = Header::from((1, 4));
header.number_of_points = u64::from(u32::MAX) + 1;
let _ = header.number_of_points_by_return.insert(6, 42);
let raw_header = header.into_raw().unwrap();
assert_eq!(0, raw_header.number_of_point_records);
assert_eq!(
u32::MAX as u64 + 1,
raw_header.large_file.unwrap().number_of_point_records
);
assert_eq!([0; 5], raw_header.number_of_points_by_return);
assert_eq!(
[0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0],
raw_header.large_file.unwrap().number_of_points_by_return
);
}
#[test]
fn prefer_legacy_fields() {
let mut raw_header = raw::Header {
version: (1, 4).into(),
number_of_point_records: 42,
number_of_points_by_return: [42, 0, 0, 0, 0],
..Default::default()
};
let large_file = raw::header::LargeFile {
number_of_point_records: 43,
number_of_points_by_return: [43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
};
raw_header.large_file = Some(large_file);
let header = Header::from_raw(raw_header).unwrap();
assert_eq!(42, header.number_of_points());
assert_eq!(42, header.number_of_points_by_return(1).unwrap());
}
#[test]
fn number_of_points_large() {
let mut header = Header::from((1, 2));
header.number_of_points = u32::MAX as u64 + 1;
assert!(header.into_raw().is_err());
let mut header = Header::from((1, 4));
header.number_of_points = u32::MAX as u64 + 1;
let raw_header = header.into_raw().unwrap();
assert_eq!(0, raw_header.number_of_point_records);
assert_eq!(
u32::MAX as u64 + 1,
raw_header.large_file.unwrap().number_of_point_records
);
let header = Header::from_raw(raw_header).unwrap();
assert_eq!(u32::MAX as u64 + 1, header.number_of_points);
}
#[test]
fn number_of_points_by_return_large() {
let mut header = Header::from((1, 2));
let _ = header
.number_of_points_by_return
.insert(1, u32::MAX as u64 + 1);
assert!(header.into_raw().is_err());
let mut header = Header::from((1, 4));
let _ = header
.number_of_points_by_return
.insert(1, u32::MAX as u64 + 1);
let raw_header = header.into_raw().unwrap();
assert_eq!(0, raw_header.number_of_points_by_return[0]);
assert_eq!(
u32::MAX as u64 + 1,
raw_header.large_file.unwrap().number_of_points_by_return[0]
);
}
#[test]
fn wkt_bit() {
let mut header = Header::from((1, 4));
let raw_header = header.clone().into_raw().unwrap();
assert_eq!(0, raw_header.global_encoding);
header.has_wkt_crs = true;
let raw_header = header.clone().into_raw().unwrap();
assert_eq!(16, raw_header.global_encoding);
header.has_wkt_crs = false;
header.point_format = Format::new(6).unwrap();
let raw_header = header.into_raw().unwrap();
assert_eq!(16, raw_header.global_encoding);
}
#[test]
fn header_too_large() {
let builder = Builder::new(raw::Header {
padding: vec![0; u16::MAX as usize - 226],
version: (1, 2).into(),
..Default::default()
})
.unwrap();
assert!(builder.into_header().unwrap().into_raw().is_err());
}
#[test]
fn offset_to_point_data_too_large() {
let mut builder = Builder::from((1, 2));
builder.vlr_padding = vec![0; u32::MAX as usize - 226];
assert!(builder.into_header().unwrap().into_raw().is_err());
}
}