1use std::io;
4
5use las::{point::Format as LasFormat, Header as LasHeader, Vlr};
6
7const LASF_SPEC_USER_ID: &str = "LASF_Spec";
8const EXTRA_BYTES_RECORD_ID: u16 = 4;
9
10#[derive(Clone, Debug, Default, PartialEq)]
12pub struct LasPointRecord {
13 pub x: f64,
14 pub y: f64,
15 pub z: f64,
16 pub intensity: u16,
17 pub return_number: u8,
18 pub number_of_returns: u8,
19 pub classification: u8,
20 pub scan_direction_flag: bool,
21 pub edge_of_flight_line: bool,
22 pub scan_angle: f32,
24 pub user_data: u8,
25 pub point_source_id: u16,
26 pub synthetic: bool,
27 pub key_point: bool,
28 pub withheld: bool,
29 pub overlap: bool,
30 pub scan_channel: u8,
31 pub gps_time: f64,
32 pub red: u16,
33 pub green: u16,
34 pub blue: u16,
35 pub nir: u16,
36 pub wave_packet_descriptor_index: u8,
37 pub byte_offset_to_waveform_data: u64,
38 pub waveform_packet_size: u32,
39 pub return_point_waveform_location: f32,
40 pub extra_bytes: Vec<u8>,
41}
42
43impl LasPointRecord {
44 pub fn from_las_point(point: &las::Point) -> Self {
46 let scan_direction_flag =
47 matches!(point.scan_direction, las::point::ScanDirection::LeftToRight);
48 let scan_angle = point.scan_angle;
49 let (red, green, blue) = match point.color {
50 Some(color) => (color.red, color.green, color.blue),
51 None => (32_768, 32_768, 32_768),
52 };
53 let (
54 wave_packet_descriptor_index,
55 byte_offset_to_waveform_data,
56 waveform_packet_size,
57 return_point_waveform_location,
58 ) = match point.waveform.as_ref() {
59 Some(wf) => (
60 wf.wave_packet_descriptor_index,
61 wf.byte_offset_to_waveform_data,
62 wf.waveform_packet_size_in_bytes,
63 wf.return_point_waveform_location,
64 ),
65 None => (0, 0, 0, 0.0),
66 };
67 Self {
68 x: point.x,
69 y: point.y,
70 z: point.z,
71 intensity: point.intensity,
72 return_number: point.return_number,
73 number_of_returns: point.number_of_returns,
74 classification: u8::from(point.classification),
75 scan_direction_flag,
76 edge_of_flight_line: point.is_edge_of_flight_line,
77 scan_angle,
78 user_data: point.user_data,
79 point_source_id: point.point_source_id,
80 synthetic: point.is_synthetic,
81 key_point: point.is_key_point,
82 withheld: point.is_withheld,
83 overlap: point.is_overlap,
84 scan_channel: point.scanner_channel,
85 gps_time: point.gps_time.unwrap_or(0.0),
86 red,
87 green,
88 blue,
89 nir: point.nir.unwrap_or(0),
90 wave_packet_descriptor_index,
91 byte_offset_to_waveform_data,
92 waveform_packet_size,
93 return_point_waveform_location,
94 extra_bytes: point.extra_bytes.clone(),
95 }
96 }
97}
98
99#[derive(Clone, Debug, PartialEq)]
101pub struct StreamingLayout {
102 pub point_format: u8,
103 pub has_gps: bool,
104 pub has_color: bool,
105 pub has_nir: bool,
106 pub has_waveform: bool,
107 pub extra_bytes: u16,
108 pub extra_bytes_descriptors: Vec<Vlr>,
109}
110
111impl StreamingLayout {
112 pub fn from_las_format(format: LasFormat) -> Self {
113 Self {
114 point_format: format.to_u8().unwrap_or(0),
115 has_gps: format.has_gps_time,
116 has_color: format.has_color,
117 has_nir: format.has_nir,
118 has_waveform: format.has_waveform,
119 extra_bytes: format.extra_bytes,
120 extra_bytes_descriptors: Vec::new(),
121 }
122 }
123
124 pub fn from_las_header(header: &LasHeader) -> Self {
125 let mut layout = Self::from_las_format(*header.point_format());
126 if layout.extra_bytes > 0 {
127 layout.extra_bytes_descriptors = header
128 .vlrs()
129 .iter()
130 .filter(|vlr| is_extra_bytes_descriptor_vlr(vlr))
131 .cloned()
132 .collect();
133 }
134 layout
135 }
136
137 pub const fn record_width(&self) -> usize {
139 let mut width = ALWAYS_BYTES;
140 if self.has_gps {
141 width += GPS_BYTES;
142 }
143 if self.has_color {
144 width += COLOR_BYTES;
145 }
146 if self.has_nir {
147 width += NIR_BYTES;
148 }
149 if self.has_waveform {
150 width += WAVEFORM_BYTES;
151 }
152 width += self.extra_bytes as usize;
153 width
154 }
155
156 pub const fn max_record_width() -> usize {
157 ALWAYS_BYTES + GPS_BYTES + COLOR_BYTES + NIR_BYTES + WAVEFORM_BYTES + u16::MAX as usize
158 }
159}
160
161const ALWAYS_BYTES: usize = 8 + 8 + 8 + 2 + 1 + 1 + 1 + 1 + 1 + 4 + 1 + 2 + 1 + 1 + 1 + 1 + 1;
162const GPS_BYTES: usize = 8;
163const COLOR_BYTES: usize = 6;
164const NIR_BYTES: usize = 2;
165const WAVEFORM_BYTES: usize = 1 + 8 + 4 + 4;
166
167fn is_extra_bytes_descriptor_vlr(vlr: &Vlr) -> bool {
168 vlr.user_id == LASF_SPEC_USER_ID && vlr.record_id == EXTRA_BYTES_RECORD_ID
169}
170
171pub fn serialize_le(
173 record: &LasPointRecord,
174 layout: &StreamingLayout,
175 dst: &mut [u8],
176) -> io::Result<()> {
177 if dst.len() != layout.record_width() {
178 return Err(io::Error::new(
179 io::ErrorKind::InvalidInput,
180 format!(
181 "destination is {} bytes, expected {}",
182 dst.len(),
183 layout.record_width()
184 ),
185 ));
186 }
187 let expected_extra_bytes = usize::from(layout.extra_bytes);
188 if record.extra_bytes.len() != expected_extra_bytes {
189 return Err(io::Error::new(
190 io::ErrorKind::InvalidInput,
191 format!(
192 "record has {} extra byte(s), expected {expected_extra_bytes}",
193 record.extra_bytes.len()
194 ),
195 ));
196 }
197 let mut offset = 0;
198
199 write_f64(&mut offset, dst, record.x);
200 write_f64(&mut offset, dst, record.y);
201 write_f64(&mut offset, dst, record.z);
202 write_u16(&mut offset, dst, record.intensity);
203 write_u8(&mut offset, dst, record.return_number);
204 write_u8(&mut offset, dst, record.number_of_returns);
205 write_u8(&mut offset, dst, record.classification);
206 write_u8(&mut offset, dst, u8::from(record.scan_direction_flag));
207 write_u8(&mut offset, dst, u8::from(record.edge_of_flight_line));
208 write_f32(&mut offset, dst, record.scan_angle);
209 write_u8(&mut offset, dst, record.user_data);
210 write_u16(&mut offset, dst, record.point_source_id);
211 write_u8(&mut offset, dst, u8::from(record.synthetic));
212 write_u8(&mut offset, dst, u8::from(record.key_point));
213 write_u8(&mut offset, dst, u8::from(record.withheld));
214 write_u8(&mut offset, dst, u8::from(record.overlap));
215 write_u8(&mut offset, dst, record.scan_channel);
216
217 if layout.has_gps {
218 write_f64(&mut offset, dst, record.gps_time);
219 }
220 if layout.has_color {
221 write_u16(&mut offset, dst, record.red);
222 write_u16(&mut offset, dst, record.green);
223 write_u16(&mut offset, dst, record.blue);
224 }
225 if layout.has_nir {
226 write_u16(&mut offset, dst, record.nir);
227 }
228 if layout.has_waveform {
229 write_u8(&mut offset, dst, record.wave_packet_descriptor_index);
230 write_u64(&mut offset, dst, record.byte_offset_to_waveform_data);
231 write_u32(&mut offset, dst, record.waveform_packet_size);
232 write_f32(&mut offset, dst, record.return_point_waveform_location);
233 }
234 dst[offset..offset + expected_extra_bytes].copy_from_slice(&record.extra_bytes);
235 offset += expected_extra_bytes;
236 debug_assert_eq!(offset, layout.record_width());
237 Ok(())
238}
239
240pub 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
247pub fn deserialize_le_into(
249 src: &[u8],
250 layout: &StreamingLayout,
251 out: &mut LasPointRecord,
252) -> io::Result<()> {
253 if src.len() != layout.record_width() {
254 return Err(io::Error::new(
255 io::ErrorKind::InvalidData,
256 format!(
257 "record is {} bytes, expected {}",
258 src.len(),
259 layout.record_width()
260 ),
261 ));
262 }
263 let mut offset = 0;
264 let x = read_f64(&mut offset, src);
265 let y = read_f64(&mut offset, src);
266 let z = read_f64(&mut offset, src);
267 let intensity = read_u16(&mut offset, src);
268 let return_number = read_u8(&mut offset, src);
269 let number_of_returns = read_u8(&mut offset, src);
270 let classification = read_u8(&mut offset, src);
271 let scan_direction_flag = read_u8(&mut offset, src) != 0;
272 let edge_of_flight_line = read_u8(&mut offset, src) != 0;
273 let scan_angle = read_f32(&mut offset, src);
274 let user_data = read_u8(&mut offset, src);
275 let point_source_id = read_u16(&mut offset, src);
276 let synthetic = read_u8(&mut offset, src) != 0;
277 let key_point = read_u8(&mut offset, src) != 0;
278 let withheld = read_u8(&mut offset, src) != 0;
279 let overlap = read_u8(&mut offset, src) != 0;
280 let scan_channel = read_u8(&mut offset, src);
281 let gps_time = if layout.has_gps {
282 read_f64(&mut offset, src)
283 } else {
284 0.0
285 };
286 let (red, green, blue) = if layout.has_color {
287 (
288 read_u16(&mut offset, src),
289 read_u16(&mut offset, src),
290 read_u16(&mut offset, src),
291 )
292 } else {
293 (0, 0, 0)
294 };
295 let nir = if layout.has_nir {
296 read_u16(&mut offset, src)
297 } else {
298 0
299 };
300 let (
301 wave_packet_descriptor_index,
302 byte_offset_to_waveform_data,
303 waveform_packet_size,
304 return_point_waveform_location,
305 ) = if layout.has_waveform {
306 (
307 read_u8(&mut offset, src),
308 read_u64(&mut offset, src),
309 read_u32(&mut offset, src),
310 read_f32(&mut offset, src),
311 )
312 } else {
313 (0, 0, 0, 0.0)
314 };
315 out.extra_bytes.clear();
316 if layout.extra_bytes > 0 {
317 let end = offset + usize::from(layout.extra_bytes);
318 out.extra_bytes.extend_from_slice(&src[offset..end]);
319 offset = end;
320 }
321 debug_assert_eq!(offset, layout.record_width());
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(())
349}
350
351#[inline]
352fn write_u8(offset: &mut usize, dst: &mut [u8], value: u8) {
353 dst[*offset] = value;
354 *offset += 1;
355}
356
357#[inline]
358fn write_u16(offset: &mut usize, dst: &mut [u8], value: u16) {
359 dst[*offset..*offset + 2].copy_from_slice(&value.to_le_bytes());
360 *offset += 2;
361}
362
363#[inline]
364fn write_u32(offset: &mut usize, dst: &mut [u8], value: u32) {
365 dst[*offset..*offset + 4].copy_from_slice(&value.to_le_bytes());
366 *offset += 4;
367}
368
369#[inline]
370fn write_u64(offset: &mut usize, dst: &mut [u8], value: u64) {
371 dst[*offset..*offset + 8].copy_from_slice(&value.to_le_bytes());
372 *offset += 8;
373}
374
375#[inline]
376fn write_f32(offset: &mut usize, dst: &mut [u8], value: f32) {
377 dst[*offset..*offset + 4].copy_from_slice(&value.to_le_bytes());
378 *offset += 4;
379}
380
381#[inline]
382fn write_f64(offset: &mut usize, dst: &mut [u8], value: f64) {
383 dst[*offset..*offset + 8].copy_from_slice(&value.to_le_bytes());
384 *offset += 8;
385}
386
387#[inline]
388fn read_u8(offset: &mut usize, src: &[u8]) -> u8 {
389 let value = src[*offset];
390 *offset += 1;
391 value
392}
393
394#[inline]
395fn read_u16(offset: &mut usize, src: &[u8]) -> u16 {
396 let value = u16::from_le_bytes(src[*offset..*offset + 2].try_into().expect("u16 width"));
397 *offset += 2;
398 value
399}
400
401#[inline]
402fn read_u32(offset: &mut usize, src: &[u8]) -> u32 {
403 let value = u32::from_le_bytes(src[*offset..*offset + 4].try_into().expect("u32 width"));
404 *offset += 4;
405 value
406}
407
408#[inline]
409fn read_u64(offset: &mut usize, src: &[u8]) -> u64 {
410 let value = u64::from_le_bytes(src[*offset..*offset + 8].try_into().expect("u64 width"));
411 *offset += 8;
412 value
413}
414
415#[inline]
416fn read_f32(offset: &mut usize, src: &[u8]) -> f32 {
417 let value = f32::from_le_bytes(src[*offset..*offset + 4].try_into().expect("f32 width"));
418 *offset += 4;
419 value
420}
421
422#[inline]
423fn read_f64(offset: &mut usize, src: &[u8]) -> f64 {
424 let value = f64::from_le_bytes(src[*offset..*offset + 8].try_into().expect("f64 width"));
425 *offset += 8;
426 value
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432
433 fn fixture_record() -> LasPointRecord {
434 LasPointRecord {
435 x: 1234.5678,
436 y: -9876.54321,
437 z: 100.0,
438 intensity: 0xBEEF,
439 return_number: 3,
440 number_of_returns: 5,
441 classification: 7,
442 scan_direction_flag: true,
443 edge_of_flight_line: true,
444 scan_angle: -12.34,
445 user_data: 0x42,
446 point_source_id: 0xCAFE,
447 synthetic: true,
448 key_point: false,
449 withheld: true,
450 overlap: false,
451 scan_channel: 2,
452 gps_time: 1.234e9,
453 red: 0xAAAA,
454 green: 0x5555,
455 blue: 0xF00F,
456 nir: 0xCDCD,
457 wave_packet_descriptor_index: 9,
458 byte_offset_to_waveform_data: 0xDEADBEEF,
459 waveform_packet_size: 0xABCD,
460 return_point_waveform_location: -42.5,
461 extra_bytes: vec![0xA0, 0xB1, 0xC2],
462 }
463 }
464
465 #[test]
466 fn round_trip_every_optional_combination() {
467 let template = fixture_record();
468 for has_gps in [false, true] {
469 for has_color in [false, true] {
470 for has_nir in [false, true] {
471 for has_waveform in [false, true] {
472 let layout = StreamingLayout {
473 point_format: 10,
474 has_gps,
475 has_color,
476 has_nir,
477 has_waveform,
478 extra_bytes: 3,
479 extra_bytes_descriptors: Vec::new(),
480 };
481 let mut record = template.clone();
482 if !layout.has_gps {
483 record.gps_time = 0.0;
484 }
485 if !layout.has_color {
486 record.red = 0;
487 record.green = 0;
488 record.blue = 0;
489 }
490 if !layout.has_nir {
491 record.nir = 0;
492 }
493 if !layout.has_waveform {
494 record.wave_packet_descriptor_index = 0;
495 record.byte_offset_to_waveform_data = 0;
496 record.waveform_packet_size = 0;
497 record.return_point_waveform_location = 0.0;
498 }
499 let mut bytes = vec![0u8; layout.record_width()];
500 serialize_le(&record, &layout, &mut bytes).unwrap();
501 assert_eq!(deserialize_le(&bytes, &layout).unwrap(), record);
502 }
503 }
504 }
505 }
506 }
507
508 #[test]
509 fn from_las_point_preserves_fractional_scan_angle_degrees() {
510 let point = las::Point {
511 scan_angle: 30.25,
512 ..Default::default()
513 };
514
515 let record = LasPointRecord::from_las_point(&point);
516
517 assert_eq!(30.25, record.scan_angle);
518 }
519
520 #[test]
521 fn from_las_format_records_presence_flags() {
522 let layout0 = StreamingLayout::from_las_format(LasFormat::new(0).unwrap());
523 assert!(!layout0.has_gps);
524 assert!(!layout0.has_color);
525 assert!(!layout0.has_nir);
526 assert!(!layout0.has_waveform);
527 assert_eq!(0, layout0.extra_bytes);
528
529 let layout3 = StreamingLayout::from_las_format(LasFormat::new(3).unwrap());
530 assert!(layout3.has_gps);
531 assert!(layout3.has_color);
532 assert!(!layout3.has_nir);
533 assert!(!layout3.has_waveform);
534 assert_eq!(0, layout3.extra_bytes);
535
536 let mut format10 = LasFormat::new(10).unwrap();
537 format10.extra_bytes = 7;
538 let layout10 = StreamingLayout::from_las_format(format10);
539 assert!(layout10.has_gps);
540 assert!(layout10.has_color);
541 assert!(layout10.has_nir);
542 assert!(layout10.has_waveform);
543 assert_eq!(7, layout10.extra_bytes);
544 }
545}