Skip to content

Commit e4a38e4

Browse files
committed
Validate COPC point flags
1 parent b35680e commit e4a38e4

2 files changed

Lines changed: 97 additions & 11 deletions

File tree

copc-writer/src/writer.rs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -391,11 +391,46 @@ fn validate_coordinate_inputs<S: CopcPointSource>(
391391
let fields = source.fields(index)?;
392392
validate_xyz_finite(index, fields.x, fields.y, fields.z)?;
393393
quantize_xyz(index, fields.x, fields.y, fields.z, scale, offset)?;
394+
validate_point_flags(index, &fields)?;
394395
stats.record(index, &fields)?;
395396
}
396397
Ok(stats)
397398
}
398399

400+
fn validate_point_flags(index: usize, fields: &CopcPointFields) -> Result<()> {
401+
validate_point_field_range(index, "return_number", fields.return_number, 0, 15)?;
402+
validate_point_field_range(index, "number_of_returns", fields.number_of_returns, 0, 15)?;
403+
validate_point_field_range(index, "synthetic", fields.synthetic, 0, 1)?;
404+
validate_point_field_range(index, "key_point", fields.key_point, 0, 1)?;
405+
validate_point_field_range(index, "withheld", fields.withheld, 0, 1)?;
406+
validate_point_field_range(index, "overlap", fields.overlap, 0, 1)?;
407+
validate_point_field_range(index, "scan_channel", fields.scan_channel, 0, 3)?;
408+
validate_point_field_range(
409+
index,
410+
"scan_direction_flag",
411+
fields.scan_direction_flag,
412+
0,
413+
1,
414+
)?;
415+
validate_point_field_range(
416+
index,
417+
"edge_of_flight_line",
418+
fields.edge_of_flight_line,
419+
0,
420+
1,
421+
)
422+
}
423+
424+
fn validate_point_field_range(index: usize, name: &str, value: u8, min: u8, max: u8) -> Result<()> {
425+
if (min..=max).contains(&value) {
426+
Ok(())
427+
} else {
428+
Err(Error::InvalidInput(format!(
429+
"point {index} {name} must be in {min}..={max}, got {value}"
430+
)))
431+
}
432+
}
433+
399434
fn validate_bounds(bounds: Bounds) -> Result<()> {
400435
validate_finite_value("bounds min x", bounds.min.0)?;
401436
validate_finite_value("bounds min y", bounds.min.1)?;
@@ -1396,23 +1431,19 @@ fn encode_point_record(
13961431
) -> Result<()> {
13971432
let mut cursor = Cursor::new(buf);
13981433
let (ix, iy, iz) = quantize_xyz(point_index, fields.x, fields.y, fields.z, scale, offset)?;
1399-
let rn = fields.return_number & 0x0F;
1400-
let nr = fields.number_of_returns & 0x0F;
1401-
let flags = (fields.synthetic & 1)
1402-
| ((fields.key_point & 1) << 1)
1403-
| ((fields.withheld & 1) << 2)
1404-
| ((fields.overlap & 1) << 3);
1405-
let chan = fields.scan_channel & 0x03;
1406-
let sd = fields.scan_direction_flag & 1;
1407-
let eof = fields.edge_of_flight_line & 1;
1434+
let flags =
1435+
fields.synthetic | (fields.key_point << 1) | (fields.withheld << 2) | (fields.overlap << 3);
14081436
let point = raw::Point {
14091437
x: ix,
14101438
y: iy,
14111439
z: iz,
14121440
intensity: fields.intensity,
14131441
flags: raw::point::Flags::ThreeByte(
1414-
rn | (nr << 4),
1415-
flags | (chan << 4) | (sd << 6) | (eof << 7),
1442+
fields.return_number | (fields.number_of_returns << 4),
1443+
flags
1444+
| (fields.scan_channel << 4)
1445+
| (fields.scan_direction_flag << 6)
1446+
| (fields.edge_of_flight_line << 7),
14161447
fields.classification,
14171448
),
14181449
scan_angle: raw::point::ScanAngle::from(fields.scan_angle_rank as f32),

copc-writer/tests/write_parse.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ struct VecSource {
1414
points: Vec<CopcPointFields>,
1515
}
1616

17+
type PointMutator = fn(&mut CopcPointFields);
18+
1719
impl CopcPointSource for VecSource {
1820
fn len(&self) -> usize {
1921
self.points.len()
@@ -314,6 +316,59 @@ fn writer_rejects_non_finite_gps_time() {
314316
assert!(!path.exists());
315317
}
316318

319+
#[test]
320+
fn writer_rejects_out_of_range_point_flag_fields() {
321+
let cases: [(&str, PointMutator); 9] = [
322+
("return_number", |point: &mut CopcPointFields| {
323+
point.return_number = 16
324+
}),
325+
("number_of_returns", |point: &mut CopcPointFields| {
326+
point.number_of_returns = 16
327+
}),
328+
("synthetic", |point: &mut CopcPointFields| {
329+
point.synthetic = 2
330+
}),
331+
("key_point", |point: &mut CopcPointFields| {
332+
point.key_point = 2
333+
}),
334+
("withheld", |point: &mut CopcPointFields| point.withheld = 2),
335+
("overlap", |point: &mut CopcPointFields| point.overlap = 2),
336+
("scan_channel", |point: &mut CopcPointFields| {
337+
point.scan_channel = 4
338+
}),
339+
("scan_direction_flag", |point: &mut CopcPointFields| {
340+
point.scan_direction_flag = 2
341+
}),
342+
("edge_of_flight_line", |point: &mut CopcPointFields| {
343+
point.edge_of_flight_line = 2
344+
}),
345+
];
346+
for (name, mutate) in cases {
347+
let mut point = point_fields(0.0, 0.0, 0.0);
348+
mutate(&mut point);
349+
let source = VecSource {
350+
points: vec![point],
351+
};
352+
let dir = tempfile::tempdir().unwrap();
353+
let path = dir.path().join(format!("{name}.copc.laz"));
354+
355+
let err = write_source(
356+
&path,
357+
&source,
358+
false,
359+
Bounds::point(0.0, 0.0, 0.0),
360+
&CopcWriterParams::default(),
361+
)
362+
.unwrap_err();
363+
364+
assert!(
365+
err.to_string().contains(name),
366+
"expected error to mention {name}, got {err}"
367+
);
368+
assert!(!path.exists());
369+
}
370+
}
371+
317372
#[test]
318373
fn writer_rejects_coordinate_outside_las_i32_range() {
319374
let source = VecSource {

0 commit comments

Comments
 (0)