@@ -475,12 +475,21 @@ impl Default for OutputLasMetadata {
475475}
476476
477477impl OutputLasMetadata {
478- fn from_las_header ( header : & las:: Header , source_evlrs : & [ las:: Vlr ] ) -> Self {
478+ fn from_las_header (
479+ header : & las:: Header ,
480+ source_evlrs : & [ las:: Vlr ] ,
481+ crs_wkt_override : Option < & str > ,
482+ ) -> Self {
479483 let mut global_encoding = u16:: from ( header. gps_time_type ( ) ) ;
480484 if header. has_synthetic_return_numbers ( ) {
481485 global_encoding |= 8 ;
482486 }
483- let crs_records = extract_source_wkt_crs_records ( header, source_evlrs) ;
487+ let mut crs_records = extract_source_wkt_crs_records ( header, source_evlrs) ;
488+ if crs_records. is_empty ( ) && has_geotiff_crs_record ( header, source_evlrs) {
489+ if let Some ( crs_wkt) = normalized_crs_wkt_override ( crs_wkt_override) {
490+ crs_records. push ( wkt_override_crs_record ( crs_wkt) ) ;
491+ }
492+ }
484493 if !crs_records. is_empty ( ) {
485494 global_encoding |= WKT_GLOBAL_ENCODING_BIT ;
486495 }
@@ -678,14 +687,46 @@ pub fn convert_las_to_copc_streaming(
678687 params : & CopcWriterParams ,
679688 spill_dir : & Path ,
680689 cancel : & dyn CancelCheck ,
690+ ) -> Result < ( ) > {
691+ convert_las_to_copc_streaming_inner ( las_path, copc_path, params, spill_dir, cancel, None )
692+ }
693+
694+ /// Converts LAS/LAZ to COPC and emits `crs_wkt_override` as a WKT CRS VLR
695+ /// when the source has GeoTIFF CRS records but no WKT CRS record.
696+ pub fn convert_las_to_copc_streaming_with_crs_wkt_override (
697+ las_path : & Path ,
698+ copc_path : & Path ,
699+ params : & CopcWriterParams ,
700+ spill_dir : & Path ,
701+ cancel : & dyn CancelCheck ,
702+ crs_wkt_override : Option < & str > ,
703+ ) -> Result < ( ) > {
704+ convert_las_to_copc_streaming_inner (
705+ las_path,
706+ copc_path,
707+ params,
708+ spill_dir,
709+ cancel,
710+ crs_wkt_override,
711+ )
712+ }
713+
714+ fn convert_las_to_copc_streaming_inner (
715+ las_path : & Path ,
716+ copc_path : & Path ,
717+ params : & CopcWriterParams ,
718+ spill_dir : & Path ,
719+ cancel : & dyn CancelCheck ,
720+ crs_wkt_override : Option < & str > ,
681721) -> Result < ( ) > {
682722 cancel. check ( ) ?;
683723 let las_file = File :: open ( las_path) . map_err ( |e| Error :: io ( "open source LAS/LAZ" , e) ) ?;
684724 let mut reader = las:: Reader :: new ( BufReader :: with_capacity ( LAS_INPUT_BUFFER_BYTES , las_file) )
685725 . map_err ( |e| Error :: Las ( e. to_string ( ) ) ) ?;
686726 let source_evlrs = read_all_source_evlrs ( las_path) ?;
687- validate_las_conversion_supported ( reader. header ( ) , & source_evlrs) ?;
688- let output_metadata = OutputLasMetadata :: from_las_header ( reader. header ( ) , & source_evlrs) ;
727+ validate_las_conversion_supported ( reader. header ( ) , & source_evlrs, crs_wkt_override) ?;
728+ let output_metadata =
729+ OutputLasMetadata :: from_las_header ( reader. header ( ) , & source_evlrs, crs_wkt_override) ;
689730 let layout = StreamingLayout :: from_las_header ( reader. header ( ) ) ;
690731 let mut spill = SpillWriter :: create ( spill_dir, layout) ?;
691732 for ( index, result) in reader. points ( ) . enumerate ( ) {
@@ -745,6 +786,7 @@ fn validate_streaming_layout_supported(layout: &StreamingLayout) -> Result<()> {
745786fn validate_las_conversion_supported (
746787 header : & las:: Header ,
747788 source_evlrs : & [ las:: Vlr ] ,
789+ crs_wkt_override : Option < & str > ,
748790) -> Result < ( ) > {
749791 let mut unsupported = Vec :: new ( ) ;
750792 let format = header. point_format ( ) ;
@@ -755,14 +797,15 @@ fn validate_las_conversion_supported(
755797 unsupported. push ( "waveform point data" . to_string ( ) ) ;
756798 }
757799 let source_has_wkt_crs_record = has_wkt_crs_record ( header, source_evlrs) ;
800+ let has_crs_wkt_override = normalized_crs_wkt_override ( crs_wkt_override) . is_some ( ) ;
758801 let mut geotiff_crs_record_count = 0usize ;
759802 for vlr in header. vlrs ( ) {
760- if is_geotiff_crs_vlr ( vlr) && !source_has_wkt_crs_record {
803+ if is_geotiff_crs_vlr ( vlr) && !source_has_wkt_crs_record && !has_crs_wkt_override {
761804 geotiff_crs_record_count += 1 ;
762805 }
763806 }
764807 for evlr in source_evlrs {
765- if is_geotiff_crs_vlr ( evlr) && !source_has_wkt_crs_record {
808+ if is_geotiff_crs_vlr ( evlr) && !source_has_wkt_crs_record && !has_crs_wkt_override {
766809 geotiff_crs_record_count += 1 ;
767810 }
768811 }
@@ -808,6 +851,10 @@ fn is_geotiff_crs_vlr(vlr: &las::Vlr) -> bool {
808851 )
809852}
810853
854+ fn normalized_crs_wkt_override ( crs_wkt_override : Option < & str > ) -> Option < & str > {
855+ crs_wkt_override. filter ( |crs_wkt| !crs_wkt. trim ( ) . is_empty ( ) )
856+ }
857+
811858fn is_extra_bytes_descriptor_vlr ( vlr : & las:: Vlr ) -> bool {
812859 vlr. user_id == LASF_SPEC_USER_ID && vlr. record_id == EXTRA_BYTES_RECORD_ID
813860}
@@ -816,6 +863,10 @@ fn has_wkt_crs_record(header: &las::Header, source_evlrs: &[las::Vlr]) -> bool {
816863 header. vlrs ( ) . iter ( ) . any ( is_wkt_crs_vlr) || source_evlrs. iter ( ) . any ( is_wkt_crs_vlr)
817864}
818865
866+ fn has_geotiff_crs_record ( header : & las:: Header , source_evlrs : & [ las:: Vlr ] ) -> bool {
867+ header. vlrs ( ) . iter ( ) . any ( is_geotiff_crs_vlr) || source_evlrs. iter ( ) . any ( is_geotiff_crs_vlr)
868+ }
869+
819870fn extract_source_wkt_crs_records (
820871 header : & las:: Header ,
821872 source_evlrs : & [ las:: Vlr ] ,
@@ -840,6 +891,18 @@ fn extract_source_wkt_crs_records(
840891 records
841892}
842893
894+ fn wkt_override_crs_record ( crs_wkt : & str ) -> OutputCrsRecord {
895+ OutputCrsRecord {
896+ vlr : las:: Vlr {
897+ user_id : LASF_PROJECTION_USER_ID . to_string ( ) ,
898+ record_id : WKT_CRS_RECORD_ID ,
899+ description : "OGC WKT CRS" . to_string ( ) ,
900+ data : crs_wkt. as_bytes ( ) . to_vec ( ) ,
901+ } ,
902+ is_extended : false ,
903+ }
904+ }
905+
843906fn extract_pass_through_vlrs ( header : & las:: Header ) -> Vec < las:: Vlr > {
844907 header
845908 . vlrs ( )
0 commit comments