2121
2222from collections .abc import Callable , Hashable , Iterable , Mapping , Sequence
2323from dataclasses import dataclass
24+ import warnings
2425
2526import dask .array as da
2627import numpy as np
@@ -313,12 +314,54 @@ def _split_bbox_antimeridian(bbox: Sequence[FloatInt]) -> Sequence[Sequence[Floa
313314 return [bbox ]
314315
315316
317+ def transform_resolution (
318+ ref_point : tuple [FloatInt , FloatInt ],
319+ resolution : FloatInt | tuple [FloatInt , FloatInt ],
320+ src_crs : str | pyproj .CRS ,
321+ dst_crs : str | pyproj .CRS ,
322+ ) -> tuple [FloatInt , FloatInt ]:
323+ """Estimate local spatial resolution in the destination CRS using
324+ finite differences.
325+
326+ Args:
327+ ref_point: Reference point (easting, northing) in the source CRS.
328+ resolution: Spatial resolution in the source CRS. Can be a single number
329+ (applied equally to both axes) or a tuple `(x_res, y_res)`.
330+ src_crs: Source coordinate reference system.
331+ dst_crs: Destination coordinate reference system.
332+
333+ Returns:
334+ tuple: Estimated local resolution in the destination CRS as `(x_res, y_res)`.
335+ """
336+ transformer = pyproj .Transformer .from_crs (src_crs , dst_crs , always_xy = True )
337+ if not isinstance (resolution , tuple ):
338+ resolution = (resolution , resolution )
339+
340+ # reference point
341+ x0 , y0 = transformer .transform (* ref_point )
342+
343+ # step in x direction
344+ x1 , y1 = transformer .transform (ref_point [0 ] + resolution [0 ], ref_point [1 ])
345+
346+ # step in y direction
347+ x2 , y2 = transformer .transform (ref_point [0 ], ref_point [1 ] + resolution [1 ])
348+
349+ # Euclidean distances
350+ res_x = np .sqrt ((x1 - x0 ) ** 2 + (y1 - y0 ) ** 2 )
351+ res_y = np .sqrt ((x2 - x0 ) ** 2 + (y2 - y0 ) ** 2 )
352+
353+ return res_x , res_y
354+
355+
316356def resolution_meters_to_degrees (
317357 resolution : FloatInt | tuple [FloatInt , FloatInt ], latitude : FloatInt
318358) -> tuple [FloatInt , FloatInt ]:
319359 """Convert spatial resolution from meters to degrees in longitude and latitude
320360 at a given geographic latitude.
321361
362+ This function is deprecated and will be removed in a future release.
363+ Use `transform_resolution` instead for CRS-aware and more accurate conversions.
364+
322365 Args:
323366 resolution: Spatial resolution in meters. Can be a single number
324367 (applied equally to both axes) or a tuple ``(x_res, y_res)``.
@@ -333,6 +376,13 @@ def resolution_meters_to_degrees(
333376 - 1 degree of longitude ≈ 111,320 * cos(latitude) meters.
334377
335378 """
379+ warnings .warn (
380+ "resolution_meters_to_degrees is deprecated and will be removed in a future release. "
381+ "Use `transform_resolution` instead for accurate CRS-based resolution handling." ,
382+ DeprecationWarning ,
383+ stacklevel = 2 ,
384+ )
385+
336386 if not isinstance (resolution , tuple ):
337387 resolution = (resolution , resolution )
338388 return (
@@ -346,6 +396,9 @@ def resolution_degrees_to_meters(
346396) -> tuple [FloatInt , FloatInt ]:
347397 """Convert spatial resolution from degrees to meters at a given geographic latitude.
348398
399+ This function is deprecated and will be removed in a future release.
400+ Use `transform_resolution` instead for CRS-aware and more accurate conversions.
401+
349402 Args:
350403 resolution: Spatial resolution in degrees. Can be a single number
351404 (applied equally to both axes) or a tuple ``(lon_res, lat_res)``.
@@ -360,6 +413,13 @@ def resolution_degrees_to_meters(
360413 - 1 degree of longitude ≈ 111,320 * cos(latitude) meters.
361414
362415 """
416+ warnings .warn (
417+ "resolution_degrees_to_meters is deprecated and will be removed in a future release. "
418+ "Use `transform_resolution` instead for accurate CRS-based resolution handling." ,
419+ DeprecationWarning ,
420+ stacklevel = 2 ,
421+ )
422+
363423 if not isinstance (resolution , tuple ):
364424 resolution = (resolution , resolution )
365425 return (
0 commit comments