use crate::error::CopcError;
#[derive(Debug, Clone)]
pub struct CopcInfo {
pub center_x: f64,
pub center_y: f64,
pub center_z: f64,
pub halfsize: f64,
pub spacing: f64,
pub root_hier_offset: u64,
pub root_hier_size: u64,
pub gpstime_minimum: f64,
pub gpstime_maximum: f64,
}
impl CopcInfo {
pub fn parse(data: &[u8]) -> Result<Self, CopcError> {
if data.len() < 160 {
return Err(CopcError::InvalidFormat(format!(
"COPC info VLR too short: {} bytes (need 160)",
data.len()
)));
}
let f64_le = |o: usize| -> f64 {
f64::from_le_bytes([
data[o],
data[o + 1],
data[o + 2],
data[o + 3],
data[o + 4],
data[o + 5],
data[o + 6],
data[o + 7],
])
};
let u64_le = |o: usize| -> u64 {
u64::from_le_bytes([
data[o],
data[o + 1],
data[o + 2],
data[o + 3],
data[o + 4],
data[o + 5],
data[o + 6],
data[o + 7],
])
};
Ok(Self {
center_x: f64_le(0),
center_y: f64_le(8),
center_z: f64_le(16),
halfsize: f64_le(24),
spacing: f64_le(32),
root_hier_offset: u64_le(40),
root_hier_size: u64_le(48),
gpstime_minimum: f64_le(56),
gpstime_maximum: f64_le(64),
})
}
pub fn bounds(&self) -> ([f64; 3], [f64; 3]) {
let c = [self.center_x, self.center_y, self.center_z];
let h = self.halfsize;
(
[c[0] - h, c[1] - h, c[2] - h],
[c[0] + h, c[1] + h, c[2] + h],
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VlrKey {
pub user_id: String,
pub record_id: u16,
}
#[derive(Debug, Clone)]
pub struct Vlr {
pub key: VlrKey,
pub description: String,
pub data: Vec<u8>,
}
impl Vlr {
pub fn parse(data: &[u8], offset: usize) -> Result<(Self, usize), CopcError> {
if offset + 54 > data.len() {
return Err(CopcError::InvalidFormat(format!(
"VLR header truncated at offset {offset} (need ≥ {} bytes)",
offset + 54
)));
}
let user_id_bytes = &data[offset + 2..offset + 18];
let user_id = String::from_utf8_lossy(user_id_bytes)
.trim_end_matches('\0')
.to_string();
let record_id = u16::from_le_bytes([data[offset + 18], data[offset + 19]]);
let record_len = u16::from_le_bytes([data[offset + 20], data[offset + 21]]) as usize;
let desc_bytes = &data[offset + 22..offset + 54];
let description = String::from_utf8_lossy(desc_bytes)
.trim_end_matches('\0')
.to_string();
let data_start = offset + 54;
let data_end = data_start + record_len;
if data_end > data.len() {
return Err(CopcError::InvalidFormat(format!(
"VLR data truncated: need {data_end} bytes but only {} available",
data.len()
)));
}
Ok((
Vlr {
key: VlrKey { user_id, record_id },
description,
data: data[data_start..data_end].to_vec(),
},
data_end,
))
}
}