@@ -69,6 +69,71 @@ def get_grid_spacing(ds: xr.DataArray, coords: tuple[Hashable, ...]) -> tuple[fl
6969 return tuple (np .abs (ds .coords [coord ][0 ].data - ds .coords [coord ][1 ].data ) for coord in coords )
7070
7171
72+ def _transform_from_coordinates (
73+ dataset : xr .Dataset ,
74+ ) -> tuple [float , float , float , float , float , float ] | None :
75+ """Construct an affine transform from dataset coordinates when possible."""
76+ if "x" not in dataset .coords or "y" not in dataset .coords :
77+ return None
78+
79+ x_coords = dataset .coords ["x" ].values
80+ y_coords = dataset .coords ["y" ].values
81+ if len (x_coords ) < 2 or len (y_coords ) < 2 :
82+ return None
83+
84+ pixel_size_x = float (np .abs (x_coords [1 ] - x_coords [0 ]))
85+ pixel_size_y = float (np .abs (y_coords [1 ] - y_coords [0 ]))
86+ x_min = float (x_coords .min ())
87+ y_max = float (y_coords .max ())
88+ return (pixel_size_x , 0.0 , x_min , 0.0 , - pixel_size_y , y_max )
89+
90+
91+ def _rio_transform_matches_coordinates (
92+ transform : tuple [float , float , float , float , float , float ] | None ,
93+ coordinate_transform : tuple [float , float , float , float , float , float ] | None ,
94+ ) -> bool :
95+ """Check whether rio-derived metadata matches the current x/y grid."""
96+ if transform is None or coordinate_transform is None :
97+ return False
98+
99+ return all (np .isclose (a , b ) for a , b in zip (transform , coordinate_transform , strict = False ))
100+
101+
102+ def _preferred_spatial_transform (
103+ dataset : xr .Dataset ,
104+ ) -> tuple [float , float , float , float , float , float ] | None :
105+ """Prefer rio metadata only when it matches the current coordinate grid."""
106+ coordinate_transform = _transform_from_coordinates (dataset )
107+ rio_transform : tuple [float , float , float , float , float , float ] | None = None
108+
109+ if hasattr (dataset , "rio" ) and hasattr (dataset .rio , "transform" ):
110+ try :
111+ rio_value = dataset .rio .transform
112+ if callable (rio_value ):
113+ rio_value = rio_value ()
114+ rio_values = tuple (float (value ) for value in tuple (rio_value )[:6 ])
115+ if len (rio_values ) == 6 :
116+ rio_transform = (
117+ rio_values [0 ],
118+ rio_values [1 ],
119+ rio_values [2 ],
120+ rio_values [3 ],
121+ rio_values [4 ],
122+ rio_values [5 ],
123+ )
124+ except (AttributeError , TypeError , ValueError ):
125+ rio_transform = None
126+
127+ if (
128+ rio_transform is not None
129+ and not all (value == 0 for value in rio_transform )
130+ and _rio_transform_matches_coordinates (rio_transform , coordinate_transform )
131+ ):
132+ return rio_transform
133+
134+ return coordinate_transform or rio_transform
135+
136+
72137def _coarsen_variable (var_name : str , var_data : xr .DataArray , factor : int ) -> xr .DataArray :
73138 """Coarsen a single variable using type-aware resampling.
74139
@@ -607,56 +672,7 @@ def add_multiscales_metadata_to_parent(
607672 first_var = next (iter (dataset .data_vars .values ()))
608673 height , width = first_var .shape [- 2 :]
609674
610- # Calculate spatial transform (affine transformation)
611- transform = None
612- if hasattr (dataset , "rio" ) and hasattr (dataset .rio , "transform" ):
613- try :
614- # Try to get transform as property first
615- rio_transform = dataset .rio .transform
616- if callable (rio_transform ):
617- rio_transform = rio_transform ()
618- transform = tuple (rio_transform )[:6 ] # Get 6 coefficients
619- log .info ("Got transform from rio accessor" , transform = transform , level = res_name )
620- except (AttributeError , TypeError ) as e :
621- log .warning (
622- "Could not get transform from rio accessor" , error = str (e ), level = res_name
623- )
624-
625- if transform is None or all (t == 0 for t in transform ):
626- # Fallback: construct from grid spacing and bounds
627- if "x" in dataset .coords and "y" in dataset .coords :
628- # Use coordinate arrays to calculate spacing
629- x_coords = dataset .coords ["x" ].values
630- y_coords = dataset .coords ["y" ].values
631-
632- if len (x_coords ) > 1 and len (y_coords ) > 1 :
633- # Calculate pixel size from actual coordinate spacing
634- pixel_size_x = float (np .abs (x_coords [1 ] - x_coords [0 ]))
635- pixel_size_y = float (np .abs (y_coords [1 ] - y_coords [0 ]))
636-
637- x_min = float (x_coords .min ())
638- y_max = float (y_coords .max ())
639- transform = (pixel_size_x , 0.0 , x_min , 0.0 , - pixel_size_y , y_max )
640- log .info (
641- "Calculated transform from coordinates" ,
642- transform = transform ,
643- pixel_size_x = pixel_size_x ,
644- pixel_size_y = pixel_size_y ,
645- level = res_name ,
646- )
647- else :
648- log .warning (
649- "Insufficient coordinate points for transform calculation" ,
650- x_len = len (x_coords ),
651- y_len = len (y_coords ),
652- level = res_name ,
653- )
654- else :
655- log .warning (
656- "Missing x/y coordinates for transform calculation" ,
657- coords = list (dataset .coords .keys ()),
658- level = res_name ,
659- )
675+ transform = _preferred_spatial_transform (dataset )
660676
661677 # Calculate zoom level (higher resolution = higher zoom)
662678 tile_width = 256
@@ -1162,30 +1178,11 @@ def write_geo_metadata(
11621178 y_min , y_max = float (y_coords .min ()), float (y_coords .max ())
11631179 dataset .attrs ["spatial:bbox" ] = [x_min , y_min , x_max , y_max ]
11641180
1165- # Calculate spatial transform (affine transformation)
1166- spatial_transform = None
1167- if hasattr (dataset , "rio" ) and hasattr (dataset .rio , "transform" ):
1168- try :
1169- rio_transform = dataset .rio .transform
1170- if callable (rio_transform ):
1171- rio_transform = rio_transform ()
1172- spatial_transform = list (rio_transform )[:6 ]
1173- except (AttributeError , TypeError ):
1174- # Fallback: construct from coordinate spacing
1175- pixel_size_x = float (get_grid_spacing (dataset , ("x" ,))[0 ])
1176- pixel_size_y = float (get_grid_spacing (dataset , ("y" ,))[0 ])
1177- spatial_transform = [
1178- pixel_size_x ,
1179- 0.0 ,
1180- x_min ,
1181- 0.0 ,
1182- - pixel_size_y ,
1183- y_max ,
1184- ]
1181+ spatial_transform = _preferred_spatial_transform (dataset )
11851182
11861183 # Only add spatial:transform if we have valid transform data (not all zeros)
11871184 if spatial_transform is not None and not all (t == 0 for t in spatial_transform ):
1188- dataset .attrs ["spatial:transform" ] = spatial_transform
1185+ dataset .attrs ["spatial:transform" ] = list ( spatial_transform )
11891186
11901187 # Add spatial shape if data variables exist
11911188 if dataset .data_vars :
0 commit comments