///|
/// In-memory LAS document. Public operations validate mutable array contents before use.
pub(all) struct Document {
header : Header
vlrs : Array[Vlr]
gap : Bytes
points : Array[Point]
} derive(Debug)
///|
pub fn decode(
data : Bytes,
limits~ : Limits = Limits::default(),
) -> Document raise LasError {
let header = read_header(data, limits~)
let (vlrs, gap) = read_vlrs(data, header, limits~)
let expected = header.point_count * header.record_length
require(
data.length() - header.point_offset == expected,
"trailing bytes after point records are unsupported",
)
let reader = Reader::new(data, header.point_offset, data.length())
let points : Array[Point] = []
for _ in 0.. Unit raise LasError {
limits.validate()
let h = self.header
validate_header_fields(h)
h.transform.validate()
ignore(record_size(h.format))
require(h.minor == 2 || h.minor == 4, "invalid document version")
require(h.minor == 4 || h.format <= 3, "format requires LAS 1.4")
if self.points.length() > limits.max_points ||
self.vlrs.length() > limits.max_vlrs {
raise Limit("document count budget exceeded")
}
require(h.project_id.length() == 16, "project UUID must be 16 bytes")
require(
h.point_count == self.points.length(),
"point count disagrees with array",
)
require(h.vlr_count == self.vlrs.length(), "VLR count disagrees with array")
let width = record_size(h.format)
require(
h.record_length >= width && h.record_length <= 65535,
"invalid record length",
)
for p in self.points {
p.validate(h.format)
require(
p.extra.length() == h.record_length - width,
"inconsistent extra-byte widths",
)
ignore(h.transform.position(p))
}
let mut metadata_size = if h.minor == 2 { 227 } else { 375 }
for v in self.vlrs {
v.validate()
let n = 54 + v.payload.length()
if n > limits.max_bytes - metadata_size {
raise Limit("metadata byte budget exceeded")
}
metadata_size += n
}
if self.gap.length() > limits.max_bytes - metadata_size {
raise Limit("gap byte budget exceeded")
}
metadata_size += self.gap.length()
require(
h.point_offset == metadata_size,
"point offset disagrees with metadata",
)
if self.points.length() > (limits.max_bytes - metadata_size) / h.record_length {
raise Limit("record byte budget exceeded")
}
}
///|
/// Recompute counts, bounds, offset and width while retaining metadata and format.
pub fn Document::with_points(
self : Document,
points : Array[Point],
limits~ : Limits = Limits::default(),
) -> Document raise LasError {
limits.validate()
if points.length() > limits.max_points {
raise Limit("point budget exceeded")
}
let h = self.header
require(h.minor == 2 || h.minor == 4, "invalid document version")
if self.vlrs.length() > limits.max_vlrs {
raise Limit("VLR budget exceeded")
}
require(h.minor == 4 || h.format <= 3, "format requires LAS 1.4")
let by_return = Array::make(if h.minor == 4 { 15 } else { 5 }, 0)
for p in points {
p.validate(h.format)
by_return[p.return_number - 1] += 1
}
let bounds = match point_bounds(points, h.transform) {
None => (0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
Some(b) => (b.min.x, b.min.y, b.min.z, b.max.x, b.max.y, b.max.z)
}
let width = record_size(h.format) +
(if points.is_empty() {
h.record_length - record_size(h.format)
} else {
points[0].extra.length()
})
let offset = metadata_end(self.vlrs, self.gap, h.minor, limits)
let header = {
..h,
point_offset: offset,
point_count: points.length(),
vlr_count: self.vlrs.length(),
by_return,
bounds,
record_length: width,
}
let doc = {
header,
vlrs: self.vlrs.copy(),
gap: self.gap,
points: points.copy(),
}
doc.validate(limits~)
doc
}
///|
pub fn create(
points : Array[Point],
format : Int32,
transform : Transform,
minor~ : Int32 = 4,
vlrs~ : Array[Vlr] = [],
limits~ : Limits = Limits::default(),
) -> Document raise LasError {
let header = {
minor,
file_source_id: 0,
global_encoding: 0,
project_id: Bytes::make(16, 0),
system: "MoonLAS",
software: "MoonLAS 0.1.0",
day: 0,
year: 0,
point_offset: if minor == 2 {
227
} else {
375
},
vlr_count: 0,
format,
record_length: record_size(format),
point_count: 0,
by_return: [],
transform,
bounds: (0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
}
let doc = { header, vlrs, gap: b"", points: [], }
doc.with_points(points, limits~)
}
///|
pub extend Document with @debug.Debug::{to_repr}