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, 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 if src.len() != layout.record_width() {
243 return Err(io::Error::new(
244 io::ErrorKind::InvalidData,
245 format!(
246 "record is {} bytes, expected {}",
247 src.len(),
248 layout.record_width()
249 ),
250 ));
251 }
252 let mut offset = 0;
253 let x = read_f64(&mut offset, src);
254 let y = read_f64(&mut offset, src);
255 let z = read_f64(&mut offset, src);
256 let intensity = read_u16(&mut offset, src);
257 let return_number = read_u8(&mut offset, src);
258 let number_of_returns = read_u8(&mut offset, src);
259 let classification = read_u8(&mut offset, src);
260 let scan_direction_flag = read_u8(&mut offset, src) != 0;
261 let edge_of_flight_line = read_u8(&mut offset, src) != 0;
262 let scan_angle = read_f32(&mut offset, src);
263 let user_data = read_u8(&mut offset, src);
264 let point_source_id = read_u16(&mut offset, src);
265 let synthetic = read_u8(&mut offset, src) != 0;
266 let key_point = read_u8(&mut offset, src) != 0;
267 let withheld = read_u8(&mut offset, src) != 0;
268 let overlap = read_u8(&mut offset, src) != 0;
269 let scan_channel = read_u8(&mut offset, src);
270 let gps_time = if layout.has_gps {
271 read_f64(&mut offset, src)
272 } else {
273 0.0
274 };
275 let (red, green, blue) = if layout.has_color {
276 (
277 read_u16(&mut offset, src),
278 read_u16(&mut offset, src),
279 read_u16(&mut offset, src),
280 )
281 } else {
282 (0, 0, 0)
283 };
284 let nir = if layout.has_nir {
285 read_u16(&mut offset, src)
286 } else {
287 0
288 };
289 let (
290 wave_packet_descriptor_index,
291 byte_offset_to_waveform_data,
292 waveform_packet_size,
293 return_point_waveform_location,
294 ) = if layout.has_waveform {
295 (
296 read_u8(&mut offset, src),
297 read_u64(&mut offset, src),
298 read_u32(&mut offset, src),
299 read_f32(&mut offset, src),
300 )
301 } else {
302 (0, 0, 0, 0.0)
303 };
304 let extra_bytes = if layout.extra_bytes > 0 {
305 let end = offset + usize::from(layout.extra_bytes);
306 let extra_bytes = src[offset..end].to_vec();
307 offset = end;
308 extra_bytes
309 } else {
310 Vec::new()
311 };
312 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 })
342}
343
344#[inline]
345fn write_u8(offset: &mut usize, dst: &mut [u8], value: u8) {
346 dst[*offset] = value;
347 *offset += 1;
348}
349
350#[inline]
351fn write_u16(offset: &mut usize, dst: &mut [u8], value: u16) {
352 dst[*offset..*offset + 2].copy_from_slice(&value.to_le_bytes());
353 *offset += 2;
354}
355
356#[inline]
357fn write_u32(offset: &mut usize, dst: &mut [u8], value: u32) {
358 dst[*offset..*offset + 4].copy_from_slice(&value.to_le_bytes());
359 *offset += 4;
360}
361
362#[inline]
363fn write_u64(offset: &mut usize, dst: &mut [u8], value: u64) {
364 dst[*offset..*offset + 8].copy_from_slice(&value.to_le_bytes());
365 *offset += 8;
366}
367
368#[inline]
369fn write_f32(offset: &mut usize, dst: &mut [u8], value: f32) {
370 dst[*offset..*offset + 4].copy_from_slice(&value.to_le_bytes());
371 *offset += 4;
372}
373
374#[inline]
375fn write_f64(offset: &mut usize, dst: &mut [u8], value: f64) {
376 dst[*offset..*offset + 8].copy_from_slice(&value.to_le_bytes());
377 *offset += 8;
378}
379
380#[inline]
381fn read_u8(offset: &mut usize, src: &[u8]) -> u8 {
382 let value = src[*offset];
383 *offset += 1;
384 value
385}
386
387#[inline]
388fn read_u16(offset: &mut usize, src: &[u8]) -> u16 {
389 let value = u16::from_le_bytes(src[*offset..*offset + 2].try_into().expect("u16 width"));
390 *offset += 2;
391 value
392}
393
394#[inline]
395fn read_u32(offset: &mut usize, src: &[u8]) -> u32 {
396 let value = u32::from_le_bytes(src[*offset..*offset + 4].try_into().expect("u32 width"));
397 *offset += 4;
398 value
399}
400
401#[inline]
402fn read_u64(offset: &mut usize, src: &[u8]) -> u64 {
403 let value = u64::from_le_bytes(src[*offset..*offset + 8].try_into().expect("u64 width"));
404 *offset += 8;
405 value
406}
407
408#[inline]
409fn read_f32(offset: &mut usize, src: &[u8]) -> f32 {
410 let value = f32::from_le_bytes(src[*offset..*offset + 4].try_into().expect("f32 width"));
411 *offset += 4;
412 value
413}
414
415#[inline]
416fn read_f64(offset: &mut usize, src: &[u8]) -> f64 {
417 let value = f64::from_le_bytes(src[*offset..*offset + 8].try_into().expect("f64 width"));
418 *offset += 8;
419 value
420}
421
422#[cfg(test)]
423mod tests {
424 use super::*;
425
426 fn fixture_record() -> LasPointRecord {
427 LasPointRecord {
428 x: 1234.5678,
429 y: -9876.54321,
430 z: 100.0,
431 intensity: 0xBEEF,
432 return_number: 3,
433 number_of_returns: 5,
434 classification: 7,
435 scan_direction_flag: true,
436 edge_of_flight_line: true,
437 scan_angle: -12.34,
438 user_data: 0x42,
439 point_source_id: 0xCAFE,
440 synthetic: true,
441 key_point: false,
442 withheld: true,
443 overlap: false,
444 scan_channel: 2,
445 gps_time: 1.234e9,
446 red: 0xAAAA,
447 green: 0x5555,
448 blue: 0xF00F,
449 nir: 0xCDCD,
450 wave_packet_descriptor_index: 9,
451 byte_offset_to_waveform_data: 0xDEADBEEF,
452 waveform_packet_size: 0xABCD,
453 return_point_waveform_location: -42.5,
454 extra_bytes: vec![0xA0, 0xB1, 0xC2],
455 }
456 }
457
458 #[test]
459 fn round_trip_every_optional_combination() {
460 let template = fixture_record();
461 for has_gps in [false, true] {
462 for has_color in [false, true] {
463 for has_nir in [false, true] {
464 for has_waveform in [false, true] {
465 let layout = StreamingLayout {
466 point_format: 10,
467 has_gps,
468 has_color,
469 has_nir,
470 has_waveform,
471 extra_bytes: 3,
472 extra_bytes_descriptors: Vec::new(),
473 };
474 let mut record = template.clone();
475 if !layout.has_gps {
476 record.gps_time = 0.0;
477 }
478 if !layout.has_color {
479 record.red = 0;
480 record.green = 0;
481 record.blue = 0;
482 }
483 if !layout.has_nir {
484 record.nir = 0;
485 }
486 if !layout.has_waveform {
487 record.wave_packet_descriptor_index = 0;
488 record.byte_offset_to_waveform_data = 0;
489 record.waveform_packet_size = 0;
490 record.return_point_waveform_location = 0.0;
491 }
492 let mut bytes = vec![0u8; layout.record_width()];
493 serialize_le(&record, &layout, &mut bytes).unwrap();
494 assert_eq!(deserialize_le(&bytes, &layout).unwrap(), record);
495 }
496 }
497 }
498 }
499 }
500
501 #[test]
502 fn from_las_point_preserves_fractional_scan_angle_degrees() {
503 let point = las::Point {
504 scan_angle: 30.25,
505 ..Default::default()
506 };
507
508 let record = LasPointRecord::from_las_point(&point);
509
510 assert_eq!(30.25, record.scan_angle);
511 }
512
513 #[test]
514 fn from_las_format_records_presence_flags() {
515 let layout0 = StreamingLayout::from_las_format(LasFormat::new(0).unwrap());
516 assert!(!layout0.has_gps);
517 assert!(!layout0.has_color);
518 assert!(!layout0.has_nir);
519 assert!(!layout0.has_waveform);
520 assert_eq!(0, layout0.extra_bytes);
521
522 let layout3 = StreamingLayout::from_las_format(LasFormat::new(3).unwrap());
523 assert!(layout3.has_gps);
524 assert!(layout3.has_color);
525 assert!(!layout3.has_nir);
526 assert!(!layout3.has_waveform);
527 assert_eq!(0, layout3.extra_bytes);
528
529 let mut format10 = LasFormat::new(10).unwrap();
530 format10.extra_bytes = 7;
531 let layout10 = StreamingLayout::from_las_format(format10);
532 assert!(layout10.has_gps);
533 assert!(layout10.has_color);
534 assert!(layout10.has_nir);
535 assert!(layout10.has_waveform);
536 assert_eq!(7, layout10.extra_bytes);
537 }
538}