Skip to content

Commit 418ef1f

Browse files
committed
Store scan angles as lossless F32 degrees and add shared parse limits in copc-core
1 parent 60010ad commit 418ef1f

4 files changed

Lines changed: 70 additions & 65 deletions

File tree

copc-core/src/columns.rs

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ pub enum LasDimension {
1616
Classification,
1717
ScanDirectionFlag,
1818
EdgeOfFlightLine,
19-
ScanAngleRank,
19+
/// Scan angle in degrees. LAS 1.4 stores it as a scaled i16 in 0.006°
20+
/// increments; the column carries the decoded degrees losslessly.
21+
ScanAngle,
2022
UserData,
2123
PointSourceId,
2224
Synthetic,
@@ -41,8 +43,7 @@ impl LasDimension {
4143
pub const fn default_scalar(self) -> Option<ScalarType> {
4244
match self {
4345
Self::X | Self::Y | Self::Z | Self::GpsTime => Some(ScalarType::F64),
44-
Self::WavePacketReturnPointWaveformLocation => Some(ScalarType::F32),
45-
Self::ScanAngleRank => Some(ScalarType::I16),
46+
Self::ScanAngle | Self::WavePacketReturnPointWaveformLocation => Some(ScalarType::F32),
4647
Self::WaveformPacketByteOffset => Some(ScalarType::U64),
4748
Self::Intensity
4849
| Self::PointSourceId
@@ -94,7 +95,7 @@ impl ColumnSelection {
9495
LasDimension::Classification,
9596
LasDimension::ScanDirectionFlag,
9697
LasDimension::EdgeOfFlightLine,
97-
LasDimension::ScanAngleRank,
98+
LasDimension::ScanAngle,
9899
LasDimension::UserData,
99100
LasDimension::PointSourceId,
100101
LasDimension::Synthetic,
@@ -433,7 +434,7 @@ pub fn layout_for_las_format(format: LasPointFormat) -> Vec<ColumnSpec> {
433434
LasDimension::Classification,
434435
LasDimension::ScanDirectionFlag,
435436
LasDimension::EdgeOfFlightLine,
436-
LasDimension::ScanAngleRank,
437+
LasDimension::ScanAngle,
437438
LasDimension::UserData,
438439
LasDimension::PointSourceId,
439440
LasDimension::Synthetic,
@@ -473,12 +474,6 @@ pub fn layout_for_las_format(format: LasPointFormat) -> Vec<ColumnSpec> {
473474
columns
474475
}
475476

476-
/// Converts scan angle degrees into the rank-style column used by existing readers.
477-
pub fn scan_angle_rank_from_degrees(degrees: f32) -> i16 {
478-
let scaled = (degrees * 180.0 / 90.0).round() as i32;
479-
scaled.clamp(i16::MIN as i32, i16::MAX as i32) as i16
480-
}
481-
482477
fn push_default_specs<I>(columns: &mut Vec<ColumnSpec>, dims: I)
483478
where
484479
I: IntoIterator<Item = LasDimension>,
@@ -575,7 +570,7 @@ mod tests {
575570
LasDimension::Classification,
576571
LasDimension::ScanDirectionFlag,
577572
LasDimension::EdgeOfFlightLine,
578-
LasDimension::ScanAngleRank,
573+
LasDimension::ScanAngle,
579574
LasDimension::UserData,
580575
LasDimension::PointSourceId,
581576
LasDimension::Synthetic,
@@ -699,11 +694,12 @@ mod tests {
699694
assert!(ColumnSpec::new(LasDimension::ExtraBytes, ScalarType::U8)
700695
.validate_default_scalar()
701696
.is_err());
702-
assert!(
703-
ColumnSpec::new(LasDimension::ScanAngleRank, ScalarType::F32)
704-
.validate_default_scalar()
705-
.is_err()
706-
);
697+
assert!(ColumnSpec::new(LasDimension::ScanAngle, ScalarType::I16)
698+
.validate_default_scalar()
699+
.is_err());
700+
assert!(ColumnSpec::new(LasDimension::ScanAngle, ScalarType::F32)
701+
.validate_default_scalar()
702+
.is_ok());
707703
}
708704

709705
#[test]
@@ -733,15 +729,6 @@ mod tests {
733729
assert!(all.contains(LasDimension::ExtraBytes));
734730
}
735731

736-
#[test]
737-
fn scan_angle_rank_uses_engine_conversion() {
738-
assert_eq!(0, scan_angle_rank_from_degrees(0.0));
739-
assert_eq!(91, scan_angle_rank_from_degrees(45.25));
740-
assert_eq!(-91, scan_angle_rank_from_degrees(-45.25));
741-
assert_eq!(i16::MAX, scan_angle_rank_from_degrees(f32::MAX));
742-
assert_eq!(i16::MIN, scan_angle_rank_from_degrees(f32::MIN));
743-
}
744-
745732
#[test]
746733
fn layout_for_format_0_has_core_dimensions() {
747734
assert_layout_dims(0, base_layout_dims());

copc-core/src/lib.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,19 @@ pub mod columns;
88
pub mod error;
99
pub mod hierarchy;
1010
pub mod info;
11+
pub mod limits;
1112
pub mod streaming;
1213

1314
pub use bounds::Bounds;
1415
pub use cancel::{CancelCheck, NeverCancel};
1516
pub use columns::{
16-
layout_for_las_format, scan_angle_rank_from_degrees, ColumnData, ColumnSelection, ColumnSpec,
17-
ColumnView, LasColumnBatch, LasDimension, ScalarType,
17+
layout_for_las_format, ColumnData, ColumnSelection, ColumnSpec, ColumnView, LasColumnBatch,
18+
LasDimension, ScalarType,
1819
};
1920
pub use error::{Error, Result};
2021
pub use hierarchy::{Entry, EntryAvailability, HierarchyPage, VoxelKey, HIERARCHY_ENTRY_BYTES};
2122
pub use info::CopcInfo;
22-
pub use streaming::{deserialize_le, serialize_le, LasPointRecord, StreamingLayout};
23+
pub use limits::{MAX_EVLR_COUNT, MAX_VLR_COUNT};
24+
pub use streaming::{
25+
deserialize_le, deserialize_le_into, serialize_le, LasPointRecord, StreamingLayout,
26+
};

copc-core/src/limits.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//! Shared parsing limits applied to untrusted LAS/COPC input.
2+
3+
/// Maximum number of VLRs accepted when parsing LAS/COPC input.
4+
pub const MAX_VLR_COUNT: u32 = 4_096;
5+
6+
/// Maximum number of EVLRs accepted when parsing LAS/COPC input.
7+
pub const MAX_EVLR_COUNT: u32 = 4_096;

copc-core/src/streaming.rs

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const LASF_SPEC_USER_ID: &str = "LASF_Spec";
88
const EXTRA_BYTES_RECORD_ID: u16 = 4;
99

1010
/// In-memory representation of one full-fidelity LAS point.
11-
#[derive(Clone, Debug, PartialEq)]
11+
#[derive(Clone, Debug, Default, PartialEq)]
1212
pub struct LasPointRecord {
1313
pub x: f64,
1414
pub y: f64,
@@ -239,6 +239,17 @@ pub fn serialize_le(
239239

240240
/// Deserialize one record from the little-endian spill bytes.
241241
pub fn deserialize_le(src: &[u8], layout: &StreamingLayout) -> io::Result<LasPointRecord> {
242+
let mut record = LasPointRecord::default();
243+
deserialize_le_into(src, layout, &mut record)?;
244+
Ok(record)
245+
}
246+
247+
/// Deserialize one record into `out`, reusing its `extra_bytes` allocation.
248+
pub fn deserialize_le_into(
249+
src: &[u8],
250+
layout: &StreamingLayout,
251+
out: &mut LasPointRecord,
252+
) -> io::Result<()> {
242253
if src.len() != layout.record_width() {
243254
return Err(io::Error::new(
244255
io::ErrorKind::InvalidData,
@@ -301,44 +312,40 @@ pub fn deserialize_le(src: &[u8], layout: &StreamingLayout) -> io::Result<LasPoi
301312
} else {
302313
(0, 0, 0, 0.0)
303314
};
304-
let extra_bytes = if layout.extra_bytes > 0 {
315+
out.extra_bytes.clear();
316+
if layout.extra_bytes > 0 {
305317
let end = offset + usize::from(layout.extra_bytes);
306-
let extra_bytes = src[offset..end].to_vec();
318+
out.extra_bytes.extend_from_slice(&src[offset..end]);
307319
offset = end;
308-
extra_bytes
309-
} else {
310-
Vec::new()
311-
};
320+
}
312321
debug_assert_eq!(offset, layout.record_width());
313-
Ok(LasPointRecord {
314-
x,
315-
y,
316-
z,
317-
intensity,
318-
return_number,
319-
number_of_returns,
320-
classification,
321-
scan_direction_flag,
322-
edge_of_flight_line,
323-
scan_angle,
324-
user_data,
325-
point_source_id,
326-
synthetic,
327-
key_point,
328-
withheld,
329-
overlap,
330-
scan_channel,
331-
gps_time,
332-
red,
333-
green,
334-
blue,
335-
nir,
336-
wave_packet_descriptor_index,
337-
byte_offset_to_waveform_data,
338-
waveform_packet_size,
339-
return_point_waveform_location,
340-
extra_bytes,
341-
})
322+
out.x = x;
323+
out.y = y;
324+
out.z = z;
325+
out.intensity = intensity;
326+
out.return_number = return_number;
327+
out.number_of_returns = number_of_returns;
328+
out.classification = classification;
329+
out.scan_direction_flag = scan_direction_flag;
330+
out.edge_of_flight_line = edge_of_flight_line;
331+
out.scan_angle = scan_angle;
332+
out.user_data = user_data;
333+
out.point_source_id = point_source_id;
334+
out.synthetic = synthetic;
335+
out.key_point = key_point;
336+
out.withheld = withheld;
337+
out.overlap = overlap;
338+
out.scan_channel = scan_channel;
339+
out.gps_time = gps_time;
340+
out.red = red;
341+
out.green = green;
342+
out.blue = blue;
343+
out.nir = nir;
344+
out.wave_packet_descriptor_index = wave_packet_descriptor_index;
345+
out.byte_offset_to_waveform_data = byte_offset_to_waveform_data;
346+
out.waveform_packet_size = waveform_packet_size;
347+
out.return_point_waveform_location = return_point_waveform_location;
348+
Ok(())
342349
}
343350

344351
#[inline]

0 commit comments

Comments
 (0)