use std::io::{Read, Seek, SeekFrom, Write};
use crate::io::le;
use crate::{Error, Result};
pub const SIGNATURE: &[u8; 4] = b"LASF";
#[derive(Debug, Clone, Copy, Default)]
pub struct GlobalEncoding(pub u16);
impl GlobalEncoding {
pub const GPS_TIME_TYPE: u16 = 0x0001;
pub const WAVEFORM_DATA_INTERNAL: u16 = 0x0002;
pub const WAVEFORM_DATA_EXTERNAL: u16 = 0x0004;
pub const SYNTHETIC_RETURN_NUMBERS: u16 = 0x0008;
pub const WKT: u16 = 0x0010;
pub fn is_set(self, flag: u16) -> bool { self.0 & flag != 0 }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum PointDataFormat {
Pdrf0 = 0,
Pdrf1 = 1,
Pdrf2 = 2,
Pdrf3 = 3,
Pdrf4 = 4,
Pdrf5 = 5,
Pdrf6 = 6,
Pdrf7 = 7,
Pdrf8 = 8,
Pdrf9 = 9,
Pdrf10 = 10,
Pdrf11 = 11,
Pdrf12 = 12,
Pdrf13 = 13,
Pdrf14 = 14,
Pdrf15 = 15,
}
impl PointDataFormat {
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(Self::Pdrf0),
1 => Some(Self::Pdrf1),
2 => Some(Self::Pdrf2),
3 => Some(Self::Pdrf3),
4 => Some(Self::Pdrf4),
5 => Some(Self::Pdrf5),
6 => Some(Self::Pdrf6),
7 => Some(Self::Pdrf7),
8 => Some(Self::Pdrf8),
9 => Some(Self::Pdrf9),
10 => Some(Self::Pdrf10),
11 => Some(Self::Pdrf11),
12 => Some(Self::Pdrf12),
13 => Some(Self::Pdrf13),
14 => Some(Self::Pdrf14),
15 => Some(Self::Pdrf15),
_ => None,
}
}
pub fn core_size(self) -> u16 {
match self {
Self::Pdrf0 => 20,
Self::Pdrf1 => 28,
Self::Pdrf2 => 26,
Self::Pdrf3 => 34,
Self::Pdrf4 => 57,
Self::Pdrf5 => 63,
Self::Pdrf6 => 30,
Self::Pdrf7 => 36,
Self::Pdrf8 => 38,
Self::Pdrf9 => 59,
Self::Pdrf10 => 67,
Self::Pdrf11 => 30, Self::Pdrf12 => 36, Self::Pdrf13 => 46, Self::Pdrf14 => 65, Self::Pdrf15 => 75, }
}
pub fn has_gps_time(self) -> bool {
!matches!(self, Self::Pdrf0 | Self::Pdrf2)
}
pub fn has_rgb(self) -> bool {
matches!(self, Self::Pdrf2 | Self::Pdrf3 | Self::Pdrf5 | Self::Pdrf7 | Self::Pdrf8 | Self::Pdrf10
| Self::Pdrf12 | Self::Pdrf13 | Self::Pdrf14 | Self::Pdrf15)
}
pub fn has_nir(self) -> bool { matches!(self, Self::Pdrf8 | Self::Pdrf13 | Self::Pdrf15) }
pub fn has_waveform(self) -> bool {
matches!(self, Self::Pdrf4 | Self::Pdrf5 | Self::Pdrf9 | Self::Pdrf10 | Self::Pdrf14 | Self::Pdrf15)
}
pub fn is_v14(self) -> bool { (6..=10).contains(&(self as u8)) }
pub fn is_v15(self) -> bool { (11..=15).contains(&(self as u8)) }
pub fn has_extended_rgb(self) -> bool {
matches!(self, Self::Pdrf12 | Self::Pdrf13 | Self::Pdrf14 | Self::Pdrf15)
}
}
#[derive(Debug, Clone)]
pub struct LasHeader {
pub version_major: u8,
pub version_minor: u8,
pub system_identifier: String,
pub generating_software: String,
pub file_creation_day: u16,
pub file_creation_year: u16,
pub header_size: u16,
pub offset_to_point_data: u32,
pub number_of_vlrs: u32,
pub point_data_format: PointDataFormat,
pub point_data_record_length: u16,
pub global_encoding: GlobalEncoding,
pub project_id: [u8; 16],
pub x_scale: f64,
pub y_scale: f64,
pub z_scale: f64,
pub x_offset: f64,
pub y_offset: f64,
pub z_offset: f64,
pub max_x: f64,
pub min_x: f64,
pub max_y: f64,
pub min_y: f64,
pub max_z: f64,
pub min_z: f64,
pub legacy_point_count: u32,
pub legacy_point_count_by_return: [u32; 5],
pub waveform_data_packet_offset: Option<u64>,
pub start_of_first_evlr: Option<u64>,
pub number_of_evlrs: Option<u32>,
pub point_count_64: Option<u64>,
pub point_count_by_return_64: Option<[u64; 15]>,
pub extra_bytes_count: u16,
}
impl LasHeader {
pub fn read<R: Read + Seek>(r: &mut R) -> Result<Self> {
r.seek(SeekFrom::Start(0))?;
let mut sig = [0u8; 4];
r.read_exact(&mut sig)?;
if &sig != SIGNATURE {
return Err(Error::InvalidSignature { format: "LAS", found: sig.to_vec() });
}
let file_source_id = le::read_u16(r)?;
let _ = file_source_id;
let global_encoding = GlobalEncoding(le::read_u16(r)?);
let mut project_id = [0u8; 16];
r.read_exact(&mut project_id)?;
let version_major = le::read_u8(r)?;
let version_minor = le::read_u8(r)?;
let mut sys_id = [0u8; 32];
r.read_exact(&mut sys_id)?;
let mut gen_sw = [0u8; 32];
r.read_exact(&mut gen_sw)?;
let file_creation_day = le::read_u16(r)?;
let file_creation_year = le::read_u16(r)?;
let header_size = le::read_u16(r)?;
let offset_to_point_data = le::read_u32(r)?;
let number_of_vlrs = le::read_u32(r)?;
let raw_pdrf = le::read_u8(r)?;
let point_data_format = PointDataFormat::from_u8(raw_pdrf & 0x7F)
.ok_or_else(|| Error::InvalidValue {
field: "point_data_format_id",
detail: format!("unknown PDRF {raw_pdrf}"),
})?;
let point_data_record_length = le::read_u16(r)?;
let legacy_point_count = le::read_u32(r)?;
let mut legacy_returns = [0u32; 5];
for v in &mut legacy_returns { *v = le::read_u32(r)?; }
let x_scale = le::read_f64(r)?;
let y_scale = le::read_f64(r)?;
let z_scale = le::read_f64(r)?;
let x_offset = le::read_f64(r)?;
let y_offset = le::read_f64(r)?;
let z_offset = le::read_f64(r)?;
let max_x = le::read_f64(r)?;
let min_x = le::read_f64(r)?;
let max_y = le::read_f64(r)?;
let min_y = le::read_f64(r)?;
let max_z = le::read_f64(r)?;
let min_z = le::read_f64(r)?;
let (waveform_offset, evlr_start, evlr_count, pc64, pcr64) =
if version_minor >= 3 && version_minor <= 5 {
let wf = le::read_u64(r)?;
let (evlr_s, evlr_c, pc, pcr) = if version_minor >= 4 {
let es = le::read_u64(r)?;
let ec = le::read_u32(r)?;
let pc = le::read_u64(r)?;
let mut ret = [0u64; 15];
for v in &mut ret { *v = le::read_u64(r)?; }
(Some(es), Some(ec), Some(pc), Some(ret))
} else {
(None, None, None, None)
};
(Some(wf), evlr_s, evlr_c, pc, pcr)
} else {
(None, None, None, None, None)
};
let extra_bytes_count = point_data_record_length
.saturating_sub(point_data_format.core_size());
Ok(LasHeader {
version_major, version_minor,
system_identifier: null_padded_str(&sys_id),
generating_software: null_padded_str(&gen_sw),
file_creation_day, file_creation_year,
header_size,
offset_to_point_data, number_of_vlrs,
point_data_format,
point_data_record_length,
global_encoding, project_id,
x_scale, y_scale, z_scale,
x_offset, y_offset, z_offset,
max_x, min_x, max_y, min_y, max_z, min_z,
legacy_point_count,
legacy_point_count_by_return: legacy_returns,
waveform_data_packet_offset: waveform_offset,
start_of_first_evlr: evlr_start,
number_of_evlrs: evlr_count,
point_count_64: pc64,
point_count_by_return_64: pcr64,
extra_bytes_count,
})
}
pub fn point_count(&self) -> u64 {
self.point_count_64.unwrap_or(u64::from(self.legacy_point_count))
}
pub fn write<W: Write>(&self, w: &mut W) -> Result<()> {
w.write_all(SIGNATURE)?;
le::write_u16(w, 0)?; le::write_u16(w, self.global_encoding.0)?;
w.write_all(&self.project_id)?;
le::write_u8(w, self.version_major)?;
le::write_u8(w, self.version_minor)?;
write_fixed_str(w, &self.system_identifier, 32)?;
write_fixed_str(w, &self.generating_software, 32)?;
le::write_u16(w, self.file_creation_day)?;
le::write_u16(w, self.file_creation_year)?;
le::write_u16(w, 375)?; le::write_u32(w, self.offset_to_point_data)?;
le::write_u32(w, self.number_of_vlrs)?;
le::write_u8(w, self.point_data_format as u8)?;
le::write_u16(w, self.point_data_record_length)?;
le::write_u32(w, self.legacy_point_count.min(u32::MAX))?;
for v in &self.legacy_point_count_by_return { le::write_u32(w, *v)?; }
le::write_f64(w, self.x_scale)?;
le::write_f64(w, self.y_scale)?;
le::write_f64(w, self.z_scale)?;
le::write_f64(w, self.x_offset)?;
le::write_f64(w, self.y_offset)?;
le::write_f64(w, self.z_offset)?;
le::write_f64(w, self.max_x)?;
le::write_f64(w, self.min_x)?;
le::write_f64(w, self.max_y)?;
le::write_f64(w, self.min_y)?;
le::write_f64(w, self.max_z)?;
le::write_f64(w, self.min_z)?;
le::write_u64(w, self.waveform_data_packet_offset.unwrap_or(0))?;
le::write_u64(w, self.start_of_first_evlr.unwrap_or(0))?;
le::write_u32(w, self.number_of_evlrs.unwrap_or(0))?;
le::write_u64(w, self.point_count_64.unwrap_or(u64::from(self.legacy_point_count)))?;
let pcr = self.point_count_by_return_64.unwrap_or([0u64; 15]);
for v in &pcr { le::write_u64(w, *v)?; }
Ok(())
}
}
fn null_padded_str(bytes: &[u8]) -> String {
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
String::from_utf8_lossy(&bytes[..end]).into_owned()
}
fn write_fixed_str<W: Write>(w: &mut W, s: &str, n: usize) -> Result<()> {
let mut buf = vec![0u8; n];
let bytes = s.as_bytes();
let len = bytes.len().min(n);
buf[..len].copy_from_slice(&bytes[..len]);
Ok(w.write_all(&buf)?)
}