1use std::io;
4
5use las::point::Format as LasFormat;
6
7#[derive(Clone, Debug, PartialEq)]
9pub struct LasPointRecord {
10 pub x: f64,
11 pub y: f64,
12 pub z: f64,
13 pub intensity: u16,
14 pub return_number: u8,
15 pub number_of_returns: u8,
16 pub classification: u8,
17 pub scan_direction_flag: bool,
18 pub edge_of_flight_line: bool,
19 pub scan_angle: f32,
21 pub user_data: u8,
22 pub point_source_id: u16,
23 pub synthetic: bool,
24 pub key_point: bool,
25 pub withheld: bool,
26 pub overlap: bool,
27 pub scan_channel: u8,
28 pub gps_time: f64,
29 pub red: u16,
30 pub green: u16,
31 pub blue: u16,
32 pub nir: u16,
33 pub wave_packet_descriptor_index: u8,
34 pub byte_offset_to_waveform_data: u64,
35 pub waveform_packet_size: u32,
36 pub return_point_waveform_location: f32,
37}
38
39impl LasPointRecord {
40 pub fn from_las_point(point: &las::Point) -> Self {
42 let scan_direction_flag =
43 matches!(point.scan_direction, las::point::ScanDirection::LeftToRight);
44 let scan_angle = point.scan_angle;
45 let (red, green, blue) = match point.color {
46 Some(color) => (color.red, color.green, color.blue),
47 None => (32_768, 32_768, 32_768),
48 };
49 let (
50 wave_packet_descriptor_index,
51 byte_offset_to_waveform_data,
52 waveform_packet_size,
53 return_point_waveform_location,
54 ) = match point.waveform.as_ref() {
55 Some(wf) => (
56 wf.wave_packet_descriptor_index,
57 wf.byte_offset_to_waveform_data,
58 wf.waveform_packet_size_in_bytes,
59 wf.return_point_waveform_location,
60 ),
61 None => (0, 0, 0, 0.0),
62 };
63 Self {
64 x: point.x,
65 y: point.y,
66 z: point.z,
67 intensity: point.intensity,
68 return_number: point.return_number,
69 number_of_returns: point.number_of_returns,
70 classification: u8::from(point.classification),
71 scan_direction_flag,
72 edge_of_flight_line: point.is_edge_of_flight_line,
73 scan_angle,
74 user_data: point.user_data,
75 point_source_id: point.point_source_id,
76 synthetic: point.is_synthetic,
77 key_point: point.is_key_point,
78 withheld: point.is_withheld,
79 overlap: point.is_overlap,
80 scan_channel: point.scanner_channel,
81 gps_time: point.gps_time.unwrap_or(0.0),
82 red,
83 green,
84 blue,
85 nir: point.nir.unwrap_or(0),
86 wave_packet_descriptor_index,
87 byte_offset_to_waveform_data,
88 waveform_packet_size,
89 return_point_waveform_location,
90 }
91 }
92}
93
94#[derive(Clone, Copy, Debug, PartialEq, Eq)]
96pub struct StreamingLayout {
97 pub point_format: u8,
98 pub has_gps: bool,
99 pub has_color: bool,
100 pub has_nir: bool,
101 pub has_waveform: bool,
102}
103
104impl StreamingLayout {
105 pub fn from_las_format(format: LasFormat) -> Self {
106 Self {
107 point_format: format.to_u8().unwrap_or(0),
108 has_gps: format.has_gps_time,
109 has_color: format.has_color,
110 has_nir: format.has_nir,
111 has_waveform: format.has_waveform,
112 }
113 }
114
115 pub const fn record_width(&self) -> usize {
117 let mut width = ALWAYS_BYTES;
118 if self.has_gps {
119 width += GPS_BYTES;
120 }
121 if self.has_color {
122 width += COLOR_BYTES;
123 }
124 if self.has_nir {
125 width += NIR_BYTES;
126 }
127 if self.has_waveform {
128 width += WAVEFORM_BYTES;
129 }
130 width
131 }
132
133 pub const fn max_record_width() -> usize {
134 ALWAYS_BYTES + GPS_BYTES + COLOR_BYTES + NIR_BYTES + WAVEFORM_BYTES
135 }
136}
137
138const ALWAYS_BYTES: usize = 8 + 8 + 8 + 2 + 1 + 1 + 1 + 1 + 1 + 4 + 1 + 2 + 1 + 1 + 1 + 1 + 1;
139const GPS_BYTES: usize = 8;
140const COLOR_BYTES: usize = 6;
141const NIR_BYTES: usize = 2;
142const WAVEFORM_BYTES: usize = 1 + 8 + 4 + 4;
143
144pub fn serialize_le(record: &LasPointRecord, layout: &StreamingLayout, dst: &mut [u8]) {
146 debug_assert_eq!(dst.len(), layout.record_width());
147 let mut offset = 0;
148
149 write_f64(&mut offset, dst, record.x);
150 write_f64(&mut offset, dst, record.y);
151 write_f64(&mut offset, dst, record.z);
152 write_u16(&mut offset, dst, record.intensity);
153 write_u8(&mut offset, dst, record.return_number);
154 write_u8(&mut offset, dst, record.number_of_returns);
155 write_u8(&mut offset, dst, record.classification);
156 write_u8(&mut offset, dst, u8::from(record.scan_direction_flag));
157 write_u8(&mut offset, dst, u8::from(record.edge_of_flight_line));
158 write_f32(&mut offset, dst, record.scan_angle);
159 write_u8(&mut offset, dst, record.user_data);
160 write_u16(&mut offset, dst, record.point_source_id);
161 write_u8(&mut offset, dst, u8::from(record.synthetic));
162 write_u8(&mut offset, dst, u8::from(record.key_point));
163 write_u8(&mut offset, dst, u8::from(record.withheld));
164 write_u8(&mut offset, dst, u8::from(record.overlap));
165 write_u8(&mut offset, dst, record.scan_channel);
166
167 if layout.has_gps {
168 write_f64(&mut offset, dst, record.gps_time);
169 }
170 if layout.has_color {
171 write_u16(&mut offset, dst, record.red);
172 write_u16(&mut offset, dst, record.green);
173 write_u16(&mut offset, dst, record.blue);
174 }
175 if layout.has_nir {
176 write_u16(&mut offset, dst, record.nir);
177 }
178 if layout.has_waveform {
179 write_u8(&mut offset, dst, record.wave_packet_descriptor_index);
180 write_u64(&mut offset, dst, record.byte_offset_to_waveform_data);
181 write_u32(&mut offset, dst, record.waveform_packet_size);
182 write_f32(&mut offset, dst, record.return_point_waveform_location);
183 }
184 debug_assert_eq!(offset, layout.record_width());
185}
186
187pub fn deserialize_le(src: &[u8], layout: &StreamingLayout) -> io::Result<LasPointRecord> {
189 if src.len() != layout.record_width() {
190 return Err(io::Error::new(
191 io::ErrorKind::InvalidData,
192 format!(
193 "record is {} bytes, expected {}",
194 src.len(),
195 layout.record_width()
196 ),
197 ));
198 }
199 let mut offset = 0;
200 let x = read_f64(&mut offset, src);
201 let y = read_f64(&mut offset, src);
202 let z = read_f64(&mut offset, src);
203 let intensity = read_u16(&mut offset, src);
204 let return_number = read_u8(&mut offset, src);
205 let number_of_returns = read_u8(&mut offset, src);
206 let classification = read_u8(&mut offset, src);
207 let scan_direction_flag = read_u8(&mut offset, src) != 0;
208 let edge_of_flight_line = read_u8(&mut offset, src) != 0;
209 let scan_angle = read_f32(&mut offset, src);
210 let user_data = read_u8(&mut offset, src);
211 let point_source_id = read_u16(&mut offset, src);
212 let synthetic = read_u8(&mut offset, src) != 0;
213 let key_point = read_u8(&mut offset, src) != 0;
214 let withheld = read_u8(&mut offset, src) != 0;
215 let overlap = read_u8(&mut offset, src) != 0;
216 let scan_channel = read_u8(&mut offset, src);
217 let gps_time = if layout.has_gps {
218 read_f64(&mut offset, src)
219 } else {
220 0.0
221 };
222 let (red, green, blue) = if layout.has_color {
223 (
224 read_u16(&mut offset, src),
225 read_u16(&mut offset, src),
226 read_u16(&mut offset, src),
227 )
228 } else {
229 (0, 0, 0)
230 };
231 let nir = if layout.has_nir {
232 read_u16(&mut offset, src)
233 } else {
234 0
235 };
236 let (
237 wave_packet_descriptor_index,
238 byte_offset_to_waveform_data,
239 waveform_packet_size,
240 return_point_waveform_location,
241 ) = if layout.has_waveform {
242 (
243 read_u8(&mut offset, src),
244 read_u64(&mut offset, src),
245 read_u32(&mut offset, src),
246 read_f32(&mut offset, src),
247 )
248 } else {
249 (0, 0, 0, 0.0)
250 };
251 debug_assert_eq!(offset, layout.record_width());
252 Ok(LasPointRecord {
253 x,
254 y,
255 z,
256 intensity,
257 return_number,
258 number_of_returns,
259 classification,
260 scan_direction_flag,
261 edge_of_flight_line,
262 scan_angle,
263 user_data,
264 point_source_id,
265 synthetic,
266 key_point,
267 withheld,
268 overlap,
269 scan_channel,
270 gps_time,
271 red,
272 green,
273 blue,
274 nir,
275 wave_packet_descriptor_index,
276 byte_offset_to_waveform_data,
277 waveform_packet_size,
278 return_point_waveform_location,
279 })
280}
281
282#[inline]
283fn write_u8(offset: &mut usize, dst: &mut [u8], value: u8) {
284 dst[*offset] = value;
285 *offset += 1;
286}
287
288#[inline]
289fn write_u16(offset: &mut usize, dst: &mut [u8], value: u16) {
290 dst[*offset..*offset + 2].copy_from_slice(&value.to_le_bytes());
291 *offset += 2;
292}
293
294#[inline]
295fn write_u32(offset: &mut usize, dst: &mut [u8], value: u32) {
296 dst[*offset..*offset + 4].copy_from_slice(&value.to_le_bytes());
297 *offset += 4;
298}
299
300#[inline]
301fn write_u64(offset: &mut usize, dst: &mut [u8], value: u64) {
302 dst[*offset..*offset + 8].copy_from_slice(&value.to_le_bytes());
303 *offset += 8;
304}
305
306#[inline]
307fn write_f32(offset: &mut usize, dst: &mut [u8], value: f32) {
308 dst[*offset..*offset + 4].copy_from_slice(&value.to_le_bytes());
309 *offset += 4;
310}
311
312#[inline]
313fn write_f64(offset: &mut usize, dst: &mut [u8], value: f64) {
314 dst[*offset..*offset + 8].copy_from_slice(&value.to_le_bytes());
315 *offset += 8;
316}
317
318#[inline]
319fn read_u8(offset: &mut usize, src: &[u8]) -> u8 {
320 let value = src[*offset];
321 *offset += 1;
322 value
323}
324
325#[inline]
326fn read_u16(offset: &mut usize, src: &[u8]) -> u16 {
327 let value = u16::from_le_bytes(src[*offset..*offset + 2].try_into().expect("u16 width"));
328 *offset += 2;
329 value
330}
331
332#[inline]
333fn read_u32(offset: &mut usize, src: &[u8]) -> u32 {
334 let value = u32::from_le_bytes(src[*offset..*offset + 4].try_into().expect("u32 width"));
335 *offset += 4;
336 value
337}
338
339#[inline]
340fn read_u64(offset: &mut usize, src: &[u8]) -> u64 {
341 let value = u64::from_le_bytes(src[*offset..*offset + 8].try_into().expect("u64 width"));
342 *offset += 8;
343 value
344}
345
346#[inline]
347fn read_f32(offset: &mut usize, src: &[u8]) -> f32 {
348 let value = f32::from_le_bytes(src[*offset..*offset + 4].try_into().expect("f32 width"));
349 *offset += 4;
350 value
351}
352
353#[inline]
354fn read_f64(offset: &mut usize, src: &[u8]) -> f64 {
355 let value = f64::from_le_bytes(src[*offset..*offset + 8].try_into().expect("f64 width"));
356 *offset += 8;
357 value
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 fn fixture_record() -> LasPointRecord {
365 LasPointRecord {
366 x: 1234.5678,
367 y: -9876.54321,
368 z: 100.0,
369 intensity: 0xBEEF,
370 return_number: 3,
371 number_of_returns: 5,
372 classification: 7,
373 scan_direction_flag: true,
374 edge_of_flight_line: true,
375 scan_angle: -12.34,
376 user_data: 0x42,
377 point_source_id: 0xCAFE,
378 synthetic: true,
379 key_point: false,
380 withheld: true,
381 overlap: false,
382 scan_channel: 2,
383 gps_time: 1.234e9,
384 red: 0xAAAA,
385 green: 0x5555,
386 blue: 0xF00F,
387 nir: 0xCDCD,
388 wave_packet_descriptor_index: 9,
389 byte_offset_to_waveform_data: 0xDEADBEEF,
390 waveform_packet_size: 0xABCD,
391 return_point_waveform_location: -42.5,
392 }
393 }
394
395 #[test]
396 fn round_trip_every_optional_combination() {
397 let template = fixture_record();
398 for has_gps in [false, true] {
399 for has_color in [false, true] {
400 for has_nir in [false, true] {
401 for has_waveform in [false, true] {
402 let layout = StreamingLayout {
403 point_format: 10,
404 has_gps,
405 has_color,
406 has_nir,
407 has_waveform,
408 };
409 let mut record = template.clone();
410 if !layout.has_gps {
411 record.gps_time = 0.0;
412 }
413 if !layout.has_color {
414 record.red = 0;
415 record.green = 0;
416 record.blue = 0;
417 }
418 if !layout.has_nir {
419 record.nir = 0;
420 }
421 if !layout.has_waveform {
422 record.wave_packet_descriptor_index = 0;
423 record.byte_offset_to_waveform_data = 0;
424 record.waveform_packet_size = 0;
425 record.return_point_waveform_location = 0.0;
426 }
427 let mut bytes = vec![0u8; layout.record_width()];
428 serialize_le(&record, &layout, &mut bytes);
429 assert_eq!(deserialize_le(&bytes, &layout).unwrap(), record);
430 }
431 }
432 }
433 }
434 }
435
436 #[test]
437 fn from_las_point_preserves_fractional_scan_angle_degrees() {
438 let point = las::Point {
439 scan_angle: 30.25,
440 ..Default::default()
441 };
442
443 let record = LasPointRecord::from_las_point(&point);
444
445 assert_eq!(30.25, record.scan_angle);
446 }
447
448 #[test]
449 fn from_las_format_records_presence_flags() {
450 let layout0 = StreamingLayout::from_las_format(LasFormat::new(0).unwrap());
451 assert!(!layout0.has_gps);
452 assert!(!layout0.has_color);
453 assert!(!layout0.has_nir);
454 assert!(!layout0.has_waveform);
455
456 let layout3 = StreamingLayout::from_las_format(LasFormat::new(3).unwrap());
457 assert!(layout3.has_gps);
458 assert!(layout3.has_color);
459 assert!(!layout3.has_nir);
460 assert!(!layout3.has_waveform);
461
462 let layout10 = StreamingLayout::from_las_format(LasFormat::new(10).unwrap());
463 assert!(layout10.has_gps);
464 assert!(layout10.has_color);
465 assert!(layout10.has_nir);
466 assert!(layout10.has_waveform);
467 }
468}