use crate::copc_vlr::{CopcInfo, Vlr};
use crate::error::CopcError;
use crate::las_header::LasHeader;
pub fn parse_vlrs(data: &[u8], header: &LasHeader) -> Result<Vec<Vlr>, CopcError> {
let mut vlrs = Vec::with_capacity(header.number_of_vlrs as usize);
let mut offset = header.header_size as usize;
for i in 0..header.number_of_vlrs {
let (vlr, next_offset) = Vlr::parse(data, offset).map_err(|e| {
CopcError::InvalidFormat(format!("Failed to parse VLR #{i} at offset {offset}: {e}"))
})?;
vlrs.push(vlr);
offset = next_offset;
}
Ok(vlrs)
}
pub fn find_copc_info(vlrs: &[Vlr]) -> Result<CopcInfo, CopcError> {
let vlr = vlrs
.iter()
.find(|v| v.key.user_id == "copc" && v.key.record_id == 1)
.ok_or_else(|| {
CopcError::InvalidFormat("No COPC info VLR (user_id=\"copc\", record_id=1)".into())
})?;
CopcInfo::parse(&vlr.data)
}
pub fn find_copc_hierarchy_vlr(vlrs: &[Vlr]) -> Result<&Vlr, CopcError> {
vlrs.iter()
.find(|v| v.key.user_id == "copc" && v.key.record_id == 1000)
.ok_or_else(|| {
CopcError::InvalidFormat(
"No COPC hierarchy VLR (user_id=\"copc\", record_id=1000)".into(),
)
})
}
#[cfg(test)]
mod tests {
use super::*;
fn make_header_for_vlrs(n_vlrs: u32) -> Vec<u8> {
let mut data = vec![0u8; 227];
data[0..4].copy_from_slice(b"LASF");
data[24] = 1;
data[25] = 4;
data[94..96].copy_from_slice(&227u16.to_le_bytes());
data[96..100].copy_from_slice(&227u32.to_le_bytes());
data[100..104].copy_from_slice(&n_vlrs.to_le_bytes());
data[104] = 6; data[105..107].copy_from_slice(&30u16.to_le_bytes());
let scale = 0.001f64.to_le_bytes();
data[131..139].copy_from_slice(&scale);
data[139..147].copy_from_slice(&scale);
data[147..155].copy_from_slice(&scale);
data
}
fn append_vlr(buf: &mut Vec<u8>, user_id: &str, record_id: u16, payload: &[u8]) {
buf.extend_from_slice(&[0u8; 2]);
let uid_bytes = user_id.as_bytes();
let mut uid_buf = [0u8; 16];
let len = uid_bytes.len().min(16);
uid_buf[..len].copy_from_slice(&uid_bytes[..len]);
buf.extend_from_slice(&uid_buf);
buf.extend_from_slice(&record_id.to_le_bytes());
buf.extend_from_slice(&(payload.len() as u16).to_le_bytes());
buf.extend_from_slice(&[0u8; 32]);
buf.extend_from_slice(payload);
}
fn make_copc_info_payload() -> Vec<u8> {
let mut data = vec![0u8; 160];
data[0..8].copy_from_slice(&50.0f64.to_le_bytes()); data[8..16].copy_from_slice(&50.0f64.to_le_bytes()); data[16..24].copy_from_slice(&25.0f64.to_le_bytes()); data[24..32].copy_from_slice(&50.0f64.to_le_bytes()); data[32..40].copy_from_slice(&1.0f64.to_le_bytes()); data[40..48].copy_from_slice(&500u64.to_le_bytes()); data[48..56].copy_from_slice(&200u64.to_le_bytes()); data
}
#[test]
fn test_parse_vlrs_zero() {
let data = make_header_for_vlrs(0);
let header = LasHeader::parse(&data).expect("valid header");
let vlrs = parse_vlrs(&data, &header).expect("zero VLRs should succeed");
assert!(vlrs.is_empty());
}
#[test]
fn test_parse_vlrs_two() {
let mut data = make_header_for_vlrs(2);
append_vlr(&mut data, "copc", 1, &make_copc_info_payload());
append_vlr(&mut data, "copc", 1000, b"hierarchy_data");
let header = LasHeader::parse(&data).expect("valid header");
let vlrs = parse_vlrs(&data, &header).expect("should parse 2 VLRs");
assert_eq!(vlrs.len(), 2);
assert_eq!(vlrs[0].key.user_id, "copc");
assert_eq!(vlrs[0].key.record_id, 1);
assert_eq!(vlrs[1].key.user_id, "copc");
assert_eq!(vlrs[1].key.record_id, 1000);
}
#[test]
fn test_parse_vlrs_truncated_data_errors() {
let data = make_header_for_vlrs(1);
let header = LasHeader::parse(&data).expect("valid header");
assert!(parse_vlrs(&data, &header).is_err());
}
#[test]
fn test_find_copc_info_present() {
let mut data = make_header_for_vlrs(2);
append_vlr(&mut data, "other", 99, b"something");
append_vlr(&mut data, "copc", 1, &make_copc_info_payload());
let header = LasHeader::parse(&data).expect("valid header");
let vlrs = parse_vlrs(&data, &header).expect("parse vlrs");
let info = find_copc_info(&vlrs).expect("should find COPC info");
assert!((info.center_x - 50.0).abs() < f64::EPSILON);
assert!((info.halfsize - 50.0).abs() < f64::EPSILON);
}
#[test]
fn test_find_copc_info_missing() {
let vlrs = vec![Vlr {
key: crate::copc_vlr::VlrKey {
user_id: "laszip".into(),
record_id: 22204,
},
description: String::new(),
data: vec![0u8; 10],
}];
assert!(find_copc_info(&vlrs).is_err());
}
#[test]
fn test_find_copc_hierarchy_vlr_present() {
let mut data = make_header_for_vlrs(2);
append_vlr(&mut data, "copc", 1, &make_copc_info_payload());
append_vlr(&mut data, "copc", 1000, b"hier_bytes");
let header = LasHeader::parse(&data).expect("valid header");
let vlrs = parse_vlrs(&data, &header).expect("parse vlrs");
let hier_vlr = find_copc_hierarchy_vlr(&vlrs).expect("should find hierarchy VLR");
assert_eq!(hier_vlr.key.record_id, 1000);
assert_eq!(hier_vlr.data, b"hier_bytes");
}
#[test]
fn test_find_copc_hierarchy_vlr_missing() {
let vlrs: Vec<Vlr> = Vec::new();
assert!(find_copc_hierarchy_vlr(&vlrs).is_err());
}
#[test]
fn test_parse_vlrs_three_mixed() {
let mut data = make_header_for_vlrs(3);
append_vlr(&mut data, "laszip", 22204, b"lz_data");
append_vlr(&mut data, "copc", 1, &make_copc_info_payload());
append_vlr(&mut data, "copc", 1000, b"hier");
let header = LasHeader::parse(&data).expect("valid header");
let vlrs = parse_vlrs(&data, &header).expect("parse vlrs");
assert_eq!(vlrs.len(), 3);
assert_eq!(vlrs[0].key.user_id, "laszip");
assert_eq!(vlrs[1].key.user_id, "copc");
assert_eq!(vlrs[2].key.user_id, "copc");
}
#[test]
fn test_parse_vlrs_empty_payload() {
let mut data = make_header_for_vlrs(1);
append_vlr(&mut data, "test", 42, b"");
let header = LasHeader::parse(&data).expect("valid header");
let vlrs = parse_vlrs(&data, &header).expect("parse vlrs");
assert_eq!(vlrs.len(), 1);
assert!(vlrs[0].data.is_empty());
}
}