@@ -728,10 +728,8 @@ pub(crate) fn resolve_map_projection(
728728 }
729729
730730 // Step 2: Resolve source and target from numeric EPSG to PROJ strings
731- let source = resolve_epsg_property ( "source" , properties, execute_query)
732- . unwrap_or_else ( || "EPSG:4326" . to_string ( ) ) ;
733- let target = resolve_epsg_property ( "target" , properties, execute_query)
734- . unwrap_or_else ( || source. clone ( ) ) ;
731+ let source = resolve_epsg_property ( "source" , properties, "EPSG:4326" , execute_query) ;
732+ let target = resolve_epsg_property ( "target" , properties, & source, execute_query) ;
735733
736734 properties. insert ( "source" . to_string ( ) , ParameterValue :: String ( source. clone ( ) ) ) ;
737735 properties. insert ( "target" . to_string ( ) , ParameterValue :: String ( target. clone ( ) ) ) ;
@@ -776,27 +774,31 @@ pub(crate) fn resolve_map_projection(
776774
777775/// If `key` holds a numeric EPSG code or an `"EPSG:N"` string, resolve it to a PROJ
778776/// string. Falls back to `"EPSG:N"` format when no PROJ string can be found (the
779- /// database engine may still handle it). Returns `None` when the property is absent.
777+ /// database engine may still handle it). When the property is absent, resolves
778+ /// `fallback` through the same lookup path.
780779fn resolve_epsg_property (
781780 key : & str ,
782781 properties : & Parameters ,
782+ fallback : & str ,
783783 execute_query : & dyn Fn ( & str ) -> crate :: Result < DataFrame > ,
784- ) -> Option < String > {
784+ ) -> String {
785785 let code: u32 = match properties. get ( key) {
786786 Some ( ParameterValue :: Number ( n) ) => * n as u32 ,
787787 Some ( ParameterValue :: String ( s) ) => {
788788 match s. strip_prefix ( "EPSG:" ) . and_then ( |n| n. parse ( ) . ok ( ) ) {
789789 Some ( c) => c,
790790 // Raw PROJ strings (`+proj=foo`) don't have EPSG prefixes
791- None => return Some ( s. clone ( ) ) ,
791+ None => return s. clone ( ) ,
792792 }
793793 }
794- _ => return None ,
794+ _ => match fallback. strip_prefix ( "EPSG:" ) . and_then ( |n| n. parse ( ) . ok ( ) ) {
795+ Some ( c) => c,
796+ None => return fallback. to_string ( ) ,
797+ } ,
795798 } ;
796- let resolved = query_spatial_ref_sys ( code, execute_query)
799+ query_spatial_ref_sys ( code, execute_query)
797800 . or_else ( || builtin_epsg_lookup ( code) )
798- . unwrap_or_else ( || format ! ( "EPSG:{code}" ) ) ;
799- Some ( resolved)
801+ . unwrap_or_else ( || format ! ( "EPSG:{code}" ) )
800802}
801803
802804fn query_spatial_ref_sys (
@@ -1145,4 +1147,41 @@ mod tests {
11451147 fn epsg_unknown_code ( ) {
11461148 assert_eq ! ( builtin_epsg_lookup( 99999 ) , None ) ;
11471149 }
1150+
1151+ fn noop_execute ( _sql : & str ) -> crate :: Result < DataFrame > {
1152+ Err ( crate :: GgsqlError :: InternalError ( "no db" . into ( ) ) )
1153+ }
1154+
1155+ #[ test]
1156+ fn resolve_epsg_property_from_existing ( ) {
1157+ let mut props = Parameters :: new ( ) ;
1158+ props. insert (
1159+ "source" . to_string ( ) ,
1160+ ParameterValue :: String ( "EPSG:4326" . to_string ( ) ) ,
1161+ ) ;
1162+ let result = resolve_epsg_property ( "source" , & props, "EPSG:4326" , & noop_execute) ;
1163+ assert ! ( result. contains( "+proj=longlat" ) , "got: {result}" ) ;
1164+ }
1165+
1166+ #[ test]
1167+ fn resolve_epsg_property_uses_fallback_when_absent ( ) {
1168+ let props = Parameters :: new ( ) ;
1169+ let result = resolve_epsg_property ( "source" , & props, "EPSG:4326" , & noop_execute) ;
1170+ assert ! (
1171+ result. contains( "+proj=longlat" ) ,
1172+ "fallback should resolve EPSG:4326 to PROJ string, got: {result}"
1173+ ) ;
1174+ }
1175+
1176+ #[ test]
1177+ fn resolve_epsg_property_fallback_proj_string_passthrough ( ) {
1178+ let props = Parameters :: new ( ) ;
1179+ let result = resolve_epsg_property (
1180+ "target" ,
1181+ & props,
1182+ "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0" ,
1183+ & noop_execute,
1184+ ) ;
1185+ assert_eq ! ( result, "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0" ) ;
1186+ }
11481187}
0 commit comments