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: i16,
20 pub user_data: u8,
21 pub point_source_id: u16,
22 pub synthetic: bool,
23 pub key_point: bool,
24 pub withheld: bool,
25 pub overlap: bool,
26 pub scan_channel: u8,
27 pub gps_time: f64,
28 pub red: u16,
29 pub green: u16,
30 pub blue: u16,
31 pub nir: u16,
32 pub wave_packet_descriptor_index: u8,
33 pub byte_offset_to_waveform_data: u64,
34 pub waveform_packet_size: u32,
35 pub return_point_waveform_location: f32,
36}
37
38impl LasPointRecord {
39 pub fn from_las_point(point: &las::Point) -> Self {
41 let scan_direction_flag =
42 matches!(point.scan_direction, las::point::ScanDirection::LeftToRight);
43 let scan_angle_scaled = (point.scan_angle * 180.0 / 90.0) as i32;
44 let scan_angle = scan_angle_scaled.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
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 + 2 + 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_i16(&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_i16(&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_i16(offset: &mut usize, dst: &mut [u8], value: i16) {
296 dst[*offset..*offset + 2].copy_from_slice(&value.to_le_bytes());
297 *offset += 2;
298}
299
300#[inline]
301fn write_u32(offset: &mut usize, dst: &mut [u8], value: u32) {
302 dst[*offset..*offset + 4].copy_from_slice(&value.to_le_bytes());
303 *offset += 4;
304}
305
306#[inline]
307fn write_u64(offset: &mut usize, dst: &mut [u8], value: u64) {
308 dst[*offset..*offset + 8].copy_from_slice(&value.to_le_bytes());
309 *offset += 8;
310}
311
312#[inline]
313fn write_f32(offset: &mut usize, dst: &mut [u8], value: f32) {
314 dst[*offset..*offset + 4].copy_from_slice(&value.to_le_bytes());
315 *offset += 4;
316}
317
318#[inline]
319fn write_f64(offset: &mut usize, dst: &mut [u8], value: f64) {
320 dst[*offset..*offset + 8].copy_from_slice(&value.to_le_bytes());
321 *offset += 8;
322}
323
324#[inline]
325fn read_u8(offset: &mut usize, src: &[u8]) -> u8 {
326 let value = src[*offset];
327 *offset += 1;
328 value
329}
330
331#[inline]
332fn read_u16(offset: &mut usize, src: &[u8]) -> u16 {
333 let value = u16::from_le_bytes(src[*offset..*offset + 2].try_into().expect("u16 width"));
334 *offset += 2;
335 value
336}
337
338#[inline]
339fn read_i16(offset: &mut usize, src: &[u8]) -> i16 {
340 let value = i16::from_le_bytes(src[*offset..*offset + 2].try_into().expect("i16 width"));
341 *offset += 2;
342 value
343}
344
345#[inline]
346fn read_u32(offset: &mut usize, src: &[u8]) -> u32 {
347 let value = u32::from_le_bytes(src[*offset..*offset + 4].try_into().expect("u32 width"));
348 *offset += 4;
349 value
350}
351
352#[inline]
353fn read_u64(offset: &mut usize, src: &[u8]) -> u64 {
354 let value = u64::from_le_bytes(src[*offset..*offset + 8].try_into().expect("u64 width"));
355 *offset += 8;
356 value
357}
358
359#[inline]
360fn read_f32(offset: &mut usize, src: &[u8]) -> f32 {
361 let value = f32::from_le_bytes(src[*offset..*offset + 4].try_into().expect("f32 width"));
362 *offset += 4;
363 value
364}
365
366#[inline]
367fn read_f64(offset: &mut usize, src: &[u8]) -> f64 {
368 let value = f64::from_le_bytes(src[*offset..*offset + 8].try_into().expect("f64 width"));
369 *offset += 8;
370 value
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 fn fixture_record() -> LasPointRecord {
378 LasPointRecord {
379 x: 1234.5678,
380 y: -9876.54321,
381 z: 100.0,
382 intensity: 0xBEEF,
383 return_number: 3,
384 number_of_returns: 5,
385 classification: 7,
386 scan_direction_flag: true,
387 edge_of_flight_line: true,
388 scan_angle: -1234,
389 user_data: 0x42,
390 point_source_id: 0xCAFE,
391 synthetic: true,
392 key_point: false,
393 withheld: true,
394 overlap: false,
395 scan_channel: 2,
396 gps_time: 1.234e9,
397 red: 0xAAAA,
398 green: 0x5555,
399 blue: 0xF00F,
400 nir: 0xCDCD,
401 wave_packet_descriptor_index: 9,
402 byte_offset_to_waveform_data: 0xDEADBEEF,
403 waveform_packet_size: 0xABCD,
404 return_point_waveform_location: -42.5,
405 }
406 }
407
408 #[test]
409 fn round_trip_every_optional_combination() {
410 let template = fixture_record();
411 for has_gps in [false, true] {
412 for has_color in [false, true] {
413 for has_nir in [false, true] {
414 for has_waveform in [false, true] {
415 let layout = StreamingLayout {
416 point_format: 10,
417 has_gps,
418 has_color,
419 has_nir,
420 has_waveform,
421 };
422 let mut record = template.clone();
423 if !layout.has_gps {
424 record.gps_time = 0.0;
425 }
426 if !layout.has_color {
427 record.red = 0;
428 record.green = 0;
429 record.blue = 0;
430 }
431 if !layout.has_nir {
432 record.nir = 0;
433 }
434 if !layout.has_waveform {
435 record.wave_packet_descriptor_index = 0;
436 record.byte_offset_to_waveform_data = 0;
437 record.waveform_packet_size = 0;
438 record.return_point_waveform_location = 0.0;
439 }
440 let mut bytes = vec![0u8; layout.record_width()];
441 serialize_le(&record, &layout, &mut bytes);
442 assert_eq!(deserialize_le(&bytes, &layout).unwrap(), record);
443 }
444 }
445 }
446 }
447 }
448
449 #[test]
450 fn from_las_format_records_presence_flags() {
451 let layout0 = StreamingLayout::from_las_format(LasFormat::new(0).unwrap());
452 assert!(!layout0.has_gps);
453 assert!(!layout0.has_color);
454 assert!(!layout0.has_nir);
455 assert!(!layout0.has_waveform);
456
457 let layout3 = StreamingLayout::from_las_format(LasFormat::new(3).unwrap());
458 assert!(layout3.has_gps);
459 assert!(layout3.has_color);
460 assert!(!layout3.has_nir);
461 assert!(!layout3.has_waveform);
462
463 let layout10 = StreamingLayout::from_las_format(LasFormat::new(10).unwrap());
464 assert!(layout10.has_gps);
465 assert!(layout10.has_color);
466 assert!(layout10.has_nir);
467 assert!(layout10.has_waveform);
468 }
469}