@@ -238,7 +238,9 @@ where
238238 if index % CANCEL_POLL_STRIDE == 0 {
239239 cancel. check ( ) ?;
240240 }
241- spill. push ( & item?) ?;
241+ let record = item?;
242+ validate_record_coordinates ( & record, index) ?;
243+ spill. push ( & record) ?;
242244 }
243245 cancel. check ( ) ?;
244246 let reader = spill. finalize ( ) ?;
@@ -264,6 +266,7 @@ pub fn convert_las_to_copc_streaming(
264266 }
265267 let point = result. map_err ( |e| Error :: Las ( e. to_string ( ) ) ) ?;
266268 let record = LasPointRecord :: from_las_point ( & point) ;
269+ validate_record_coordinates ( & record, index) ?;
267270 spill. push ( & record) ?;
268271 }
269272 cancel. check ( ) ?;
@@ -342,6 +345,126 @@ fn is_laszip_vlr(vlr: &las::Vlr) -> bool {
342345 vlr. user_id == LASZIP_VLR_USER_ID && vlr. record_id == LASZIP_VLR_RECORD_ID
343346}
344347
348+ fn validate_record_coordinates ( record : & LasPointRecord , index : usize ) -> Result < ( ) > {
349+ validate_xyz_finite ( index, record. x , record. y , record. z )
350+ }
351+
352+ fn validate_coordinate_inputs < S : CopcPointSource > (
353+ source : & S ,
354+ bounds : Bounds ,
355+ scale : ( f64 , f64 , f64 ) ,
356+ offset : ( f64 , f64 , f64 ) ,
357+ cancel : & dyn CancelCheck ,
358+ ) -> Result < ( ) > {
359+ validate_bounds ( bounds) ?;
360+ validate_transform ( scale, offset) ?;
361+ for index in 0 ..source. len ( ) {
362+ if index % CANCEL_POLL_STRIDE == 0 {
363+ cancel. check ( ) ?;
364+ }
365+ let ( x, y, z) = source. xyz ( index) ;
366+ validate_xyz_finite ( index, x, y, z) ?;
367+ quantize_xyz ( index, x, y, z, scale, offset) ?;
368+
369+ let fields = source. fields ( index) ?;
370+ validate_xyz_finite ( index, fields. x , fields. y , fields. z ) ?;
371+ quantize_xyz ( index, fields. x , fields. y , fields. z , scale, offset) ?;
372+ }
373+ Ok ( ( ) )
374+ }
375+
376+ fn validate_bounds ( bounds : Bounds ) -> Result < ( ) > {
377+ validate_finite_value ( "bounds min x" , bounds. min . 0 ) ?;
378+ validate_finite_value ( "bounds min y" , bounds. min . 1 ) ?;
379+ validate_finite_value ( "bounds min z" , bounds. min . 2 ) ?;
380+ validate_finite_value ( "bounds max x" , bounds. max . 0 ) ?;
381+ validate_finite_value ( "bounds max y" , bounds. max . 1 ) ?;
382+ validate_finite_value ( "bounds max z" , bounds. max . 2 ) ?;
383+ for ( axis, min, max) in [
384+ ( "x" , bounds. min . 0 , bounds. max . 0 ) ,
385+ ( "y" , bounds. min . 1 , bounds. max . 1 ) ,
386+ ( "z" , bounds. min . 2 , bounds. max . 2 ) ,
387+ ] {
388+ if min > max {
389+ return Err ( Error :: InvalidInput ( format ! (
390+ "bounds {axis} min {min} exceeds max {max}"
391+ ) ) ) ;
392+ }
393+ validate_finite_value ( & format ! ( "bounds {axis} span" ) , max - min) ?;
394+ }
395+ Ok ( ( ) )
396+ }
397+
398+ fn validate_transform ( scale : ( f64 , f64 , f64 ) , offset : ( f64 , f64 , f64 ) ) -> Result < ( ) > {
399+ for ( axis, value) in [ ( "x" , scale. 0 ) , ( "y" , scale. 1 ) , ( "z" , scale. 2 ) ] {
400+ if !value. is_finite ( ) || value <= 0.0 {
401+ return Err ( Error :: InvalidInput ( format ! (
402+ "LAS {axis} scale must be finite and positive, got {value}"
403+ ) ) ) ;
404+ }
405+ }
406+ validate_finite_value ( "LAS x offset" , offset. 0 ) ?;
407+ validate_finite_value ( "LAS y offset" , offset. 1 ) ?;
408+ validate_finite_value ( "LAS z offset" , offset. 2 ) ?;
409+ Ok ( ( ) )
410+ }
411+
412+ fn validate_xyz_finite ( index : usize , x : f64 , y : f64 , z : f64 ) -> Result < ( ) > {
413+ validate_point_axis_finite ( index, "x" , x) ?;
414+ validate_point_axis_finite ( index, "y" , y) ?;
415+ validate_point_axis_finite ( index, "z" , z)
416+ }
417+
418+ fn validate_point_axis_finite ( index : usize , axis : & str , value : f64 ) -> Result < ( ) > {
419+ if value. is_finite ( ) {
420+ Ok ( ( ) )
421+ } else {
422+ Err ( Error :: InvalidInput ( format ! (
423+ "point {index} {axis} coordinate must be finite, got {value}"
424+ ) ) )
425+ }
426+ }
427+
428+ fn validate_finite_value ( name : & str , value : f64 ) -> Result < ( ) > {
429+ if value. is_finite ( ) {
430+ Ok ( ( ) )
431+ } else {
432+ Err ( Error :: InvalidInput ( format ! (
433+ "{name} must be finite, got {value}"
434+ ) ) )
435+ }
436+ }
437+
438+ fn quantize_xyz (
439+ index : usize ,
440+ x : f64 ,
441+ y : f64 ,
442+ z : f64 ,
443+ scale : ( f64 , f64 , f64 ) ,
444+ offset : ( f64 , f64 , f64 ) ,
445+ ) -> Result < ( i32 , i32 , i32 ) > {
446+ Ok ( (
447+ quantize_axis ( index, "x" , x, scale. 0 , offset. 0 ) ?,
448+ quantize_axis ( index, "y" , y, scale. 1 , offset. 1 ) ?,
449+ quantize_axis ( index, "z" , z, scale. 2 , offset. 2 ) ?,
450+ ) )
451+ }
452+
453+ fn quantize_axis ( index : usize , axis : & str , value : f64 , scale : f64 , offset : f64 ) -> Result < i32 > {
454+ let scaled = ( ( value - offset) / scale) . round ( ) ;
455+ if !scaled. is_finite ( ) {
456+ return Err ( Error :: InvalidInput ( format ! (
457+ "point {index} {axis} coordinate cannot be encoded with scale {scale} and offset {offset}"
458+ ) ) ) ;
459+ }
460+ if scaled < f64:: from ( i32:: MIN ) || scaled > f64:: from ( i32:: MAX ) {
461+ return Err ( Error :: InvalidInput ( format ! (
462+ "point {index} {axis} coordinate {value} encodes to {scaled}, outside LAS i32 range"
463+ ) ) ) ;
464+ }
465+ Ok ( scaled as i32 )
466+ }
467+
345468fn write_copc_from_spill (
346469 path : & Path ,
347470 reader : SpillReader ,
@@ -377,12 +500,19 @@ fn write_copc_inner<S: CopcPointSource>(
377500 LasFormat :: new ( point_format_id) . map_err ( |e| Error :: Las ( format ! ( "point format: {e}" ) ) ) ?;
378501 let point_record_length = point_format. len ( ) ;
379502
380- let ( center, halfsize) = cube_from_bounds ( & bounds) ;
381503 let ( scale_x, scale_y, scale_z) = metadata. scale ;
382504 let ( offset_x, offset_y, offset_z) =
383505 metadata
384506 . offset
385507 . unwrap_or ( ( bounds. min . 0 , bounds. min . 1 , bounds. min . 2 ) ) ;
508+ validate_coordinate_inputs (
509+ source,
510+ bounds,
511+ ( scale_x, scale_y, scale_z) ,
512+ ( offset_x, offset_y, offset_z) ,
513+ cancel,
514+ ) ?;
515+ let ( center, halfsize) = cube_from_bounds ( & bounds) ;
386516
387517 let nodes = build_lod_nodes ( source, center, halfsize, params, cancel) ?;
388518 cancel. check ( ) ?;
@@ -479,6 +609,7 @@ fn write_copc_inner<S: CopcPointSource>(
479609 & fields,
480610 ( scale_x, scale_y, scale_z) ,
481611 ( offset_x, offset_y, offset_z) ,
612+ source_index as usize ,
482613 & point_format,
483614 ) ?;
484615 compressor
@@ -659,10 +790,14 @@ fn child_octant(bounds: Bounds, x: f64, y: f64, z: f64) -> usize {
659790}
660791
661792fn cube_from_bounds ( bounds : & Bounds ) -> ( ( f64 , f64 , f64 ) , f64 ) {
662- let center = bounds. center ( ) ;
663793 let dx = bounds. max . 0 - bounds. min . 0 ;
664794 let dy = bounds. max . 1 - bounds. min . 1 ;
665795 let dz = bounds. max . 2 - bounds. min . 2 ;
796+ let center = (
797+ bounds. min . 0 + dx * 0.5 ,
798+ bounds. min . 1 + dy * 0.5 ,
799+ bounds. min . 2 + dz * 0.5 ,
800+ ) ;
666801 let halfsize = ( dx. max ( dy) . max ( dz) * 0.5 ) . max ( 1e-6 ) ;
667802 ( center, halfsize)
668803}
@@ -864,12 +999,11 @@ fn encode_point_record(
864999 fields : & CopcPointFields ,
8651000 scale : ( f64 , f64 , f64 ) ,
8661001 offset : ( f64 , f64 , f64 ) ,
1002+ point_index : usize ,
8671003 format : & LasFormat ,
8681004) -> Result < ( ) > {
8691005 let mut cursor = Cursor :: new ( buf) ;
870- let ix = ( ( fields. x - offset. 0 ) / scale. 0 ) . round ( ) as i32 ;
871- let iy = ( ( fields. y - offset. 1 ) / scale. 1 ) . round ( ) as i32 ;
872- let iz = ( ( fields. z - offset. 2 ) / scale. 2 ) . round ( ) as i32 ;
1006+ let ( ix, iy, iz) = quantize_xyz ( point_index, fields. x , fields. y , fields. z , scale, offset) ?;
8731007 let rn = fields. return_number & 0x0F ;
8741008 let nr = fields. number_of_returns & 0x0F ;
8751009 let flags = ( fields. synthetic & 1 )
0 commit comments