@@ -978,7 +978,7 @@ def apply_masked_replace(
978978 patch_rows = overlap ["patch" ][0 ]
979979 patch_cols = overlap ["patch" ][1 ]
980980
981- mask_slice = mask [ patch_rows , patch_cols ]
981+ mask_slice = self . _resample_patch_slice_to_map ( mask , geometry , map_rows , map_cols , patch_rows , patch_cols )
982982 valid_mask = np .isfinite (mask_slice )
983983 if not np .any (valid_mask ):
984984 return
@@ -991,7 +991,7 @@ def apply_masked_replace(
991991 target = self ._resolve_layer_target (name )
992992 if target is None :
993993 raise ValueError (f"Layer '{ name } ' does not exist in the map." )
994- incoming_slice = array [ patch_rows , patch_cols ]
994+ incoming_slice = self . _resample_patch_slice_to_map ( array , geometry , map_rows , map_cols , patch_rows , patch_cols )
995995 if incoming_slice .shape != mask_slice .shape :
996996 raise ValueError ("Mismatch between mask and incoming slice dimensions." )
997997 if name in ("elevation" , "upper_bound" ):
@@ -1075,10 +1075,43 @@ def _validate_geometry_against_shape(self, shape: Tuple[int, int], geometry: Gri
10751075 raise ValueError (
10761076 f"Grid shape mismatch: expected { expected_shape } , received { shape } ."
10771077 )
1078- if not math .isclose (float (geometry .resolution ), float (self .resolution ), rel_tol = 1e-6 , abs_tol = 1e-6 ):
1079- raise ValueError (
1080- f"Resolution mismatch: map uses { self .resolution } , incoming grid uses { geometry .resolution } ."
1081- )
1078+
1079+ def _resample_patch_slice_to_map (
1080+ self ,
1081+ patch_array : np .ndarray ,
1082+ geometry : GridGeometry ,
1083+ map_rows : slice ,
1084+ map_cols : slice ,
1085+ patch_rows : slice ,
1086+ patch_cols : slice ,
1087+ ) -> np .ndarray :
1088+ """Sample a patch onto the map grid when incoming and map resolutions differ."""
1089+ map_row_count = map_rows .stop - map_rows .start
1090+ map_col_count = map_cols .stop - map_cols .start
1091+ if map_row_count <= 0 or map_col_count <= 0 :
1092+ raise ValueError ("Invalid overlap region for masked replace." )
1093+
1094+ if math .isclose (float (geometry .resolution ), float (self .resolution ), rel_tol = 1e-6 , abs_tol = 1e-6 ):
1095+ return patch_array [patch_rows , patch_cols ]
1096+
1097+ map_length = (self .cell_n - 2 ) * self .resolution
1098+ center_cpu = np .asarray (cp .asnumpy (self .center ))
1099+ map_min_x = center_cpu [0 ] - map_length / 2.0
1100+ map_min_y = center_cpu [1 ] - map_length / 2.0
1101+ patch_min_x , _ = geometry .bounds_x
1102+ patch_min_y , _ = geometry .bounds_y
1103+
1104+ map_r = np .arange (map_rows .start , map_rows .stop , dtype = np .float32 )
1105+ map_c = np .arange (map_cols .start , map_cols .stop , dtype = np .float32 )
1106+ world_x = map_min_x + (map_c + 0.5 ) * self .resolution
1107+ world_y = map_min_y + (map_r + 0.5 ) * self .resolution
1108+ xx , yy = np .meshgrid (world_x , world_y )
1109+
1110+ patch_col = np .floor ((xx - patch_min_x ) / geometry .resolution ).astype (np .int32 )
1111+ patch_row = np .floor ((yy - patch_min_y ) / geometry .resolution ).astype (np .int32 )
1112+ patch_col = np .clip (patch_col , 0 , patch_array .shape [1 ] - 1 )
1113+ patch_row = np .clip (patch_row , 0 , patch_array .shape [0 ] - 1 )
1114+ return patch_array [patch_row , patch_col ]
10821115
10831116 def _compute_overlap_indices (
10841117 self , incoming_shape : Tuple [int , int ], geometry : GridGeometry
0 commit comments