44
55import cartopy .crs as ccrs
66import numpy as np
7- import pandas as pd
87import xarray as xr
98
109from ClimateGraph .reader import Reader
@@ -118,7 +117,6 @@ def __init__(
118117 self ._path = None
119118 self ._bbox = None # minlon, minlat, maxlon, maxlat
120119 self ._dims = None
121- self .resampled = None
122120
123121 # Provided by user
124122 self .name = name
@@ -145,7 +143,6 @@ def copy(self):
145143 new ._geom = self ._geom
146144 new ._bbox = self ._bbox # minlon, minlat, maxlon, maxlat
147145 new ._dims = self ._dims
148- new .resampled = self .resampled
149146
150147 return new
151148
@@ -180,7 +177,6 @@ def obj(self, _obj: xr.Dataset):
180177 self ._bbox = None
181178 self ._geom = None
182179 self ._dims = None
183- self .resampled = None
184180
185181 @property
186182 def geom (self ):
@@ -402,28 +398,27 @@ def resample_vars(
402398 other : "Data" ,
403399 vars : str | list [str ],
404400 radius_of_influence : int = 10000 ,
405- time_tolerance : str | None = "30min" ,
406401 engine : str | ResampleEngine = "pyresample" ,
407402 engine_kwargs : dict | None = None ,
408- ) -> xr .DataArray | xr . Dataset :
409- """Project ``other``'s vars onto ``self``'s geometry (and time axis) .
403+ ) -> xr .Dataset :
404+ """Project ``other``'s vars onto ``self``'s spatial geometry .
410405
411- A single responsibility: spatial resampling with the requested engine, plus
412- a nearest-neighbour time *alignment* of ``other`` onto ``self``'s time axis.
413- It does NOT convert units or filter/resample time — those belong to the
414- caller (``get_var`` / the plot's ``change_unit`` and ``time_resampling``).
406+ Purely spatial and stateless: each variable of ``other`` is reprojected
407+ onto ``self``'s geometry with the requested engine, keeping ``other``'s own
408+ time axis and any extra dims (z, member, …) untouched, and inheriting
409+ ``self``'s spatial coordinates (site / latitude / longitude / region / …).
410+ It does NOT align time, convert units, or cache — those are the caller's
411+ concern. Each call is independent, so two sources with different time
412+ extents resampled onto the same target never clobber each other.
415413
416414 Parameters
417415 ----------
418416 other : Data
419- Other data object to resample into the " self" geometry.
417+ Data whose vars are reprojected onto `` self``'s geometry.
420418 vars : str | list[str]
421- Variable name or list of names to resample (kept in their source units) .
419+ Variable name or list of names to resample.
422420 radius_of_influence : int, optional
423421 Radius length in meters to use for resampling. by default 10000
424- time_tolerance : str | None, optional
425- Pandas-style timedelta used as the tolerance when snapping ``other``'s
426- time axis onto ``self``'s via nearest-neighbour reindex. Default ``"30min"``.
427422 engine : str | ResampleEngine, optional
428423 Resample backend name or instance. Default ``"pyresample"``.
429424 engine_kwargs : dict | None, optional
@@ -432,40 +427,37 @@ def resample_vars(
432427
433428 Returns
434429 -------
435- xr.DataArray | xr.Dataset
436- Resampled data on ``self``'s geometry, aligned onto ``self``'s time axis.
430+ xr.Dataset
431+ One variable per input var, keyed ``"{var}__{other.name}"``, on
432+ ``self``'s geometry and carrying ``other``'s time / extra dims.
437433 """
438434 if isinstance (vars , str ):
439435 vars = [vars ]
440436
441437 resample_engine = get_engine (engine , ** (engine_kwargs or {}))
442- src_geom = other .geom
443- dst_geom = self .geom
444-
445438 info = resample_engine .prepare (
446- src_geom ,
447- dst_geom ,
439+ other . geom ,
440+ self . geom ,
448441 radius_of_influence = radius_of_influence ,
449442 )
450- _resample = resample_engine .make_resampler (info , dst_geom .shape )
451-
452- if self .resampled is None :
453- self .resampled = self .obj .drop_vars (list (self .obj .data_vars ))
454-
455- new_vars = []
443+ _resample = resample_engine .make_resampler (info , self .geom .shape )
444+
445+ # Target geometry straight from self.obj (no data vars, no time axis): the
446+ # geom-dim ORDER follows self.obj so it matches self.geom.shape, and the
447+ # spatial coords (those depending only on the geom dims) are reattached to
448+ # the result — this is how the source inherits site/region/lat/lon.
449+ dst_geom_dims = [d for d in self .obj .dims if d in set (self .geom_dims )]
450+ dst_sizes = {d : self .obj .sizes [d ] for d in dst_geom_dims }
451+ dst_coords = {
452+ name : coord
453+ for name , coord in self .obj .coords .items ()
454+ if set (coord .dims ) <= set (self .geom_dims )
455+ }
456+
457+ resampled_vars = {}
456458 for var in vars :
457- var_dst_dims = self .get_var (var ).sizes
458- var_src = other .get_var (var )
459-
460- if time_tolerance is not None and "time" in var_src .dims :
461- var_src = var_src .reindex (
462- time = self .resampled ["time" ],
463- method = "nearest" ,
464- tolerance = pd .Timedelta (time_tolerance ),
465- )
466-
459+ var_src = other .get_var (var ) # keeps other's own time + extra dims
467460 src_geom_dims = [d for d in var_src .dims if d in set (other .geom_dims )]
468- dst_geom_dims = [d for d in var_dst_dims if d in set (self .geom_dims )]
469461 resampled = xr .apply_ufunc (
470462 _resample ,
471463 var_src ,
@@ -474,30 +466,22 @@ def resample_vars(
474466 vectorize = True ,
475467 dask = "parallelized" ,
476468 output_dtypes = [var_src .dtype ],
477- dask_gufunc_kwargs = {
478- "output_sizes" : {
479- name : value
480- for name , value in var_dst_dims .items ()
481- if name in set (self .geom_dims )
482- }
483- },
469+ dask_gufunc_kwargs = {"output_sizes" : dst_sizes },
484470 )
471+ resampled_vars [f"{ var } __{ other .name } " ] = resampled .assign_coords (dst_coords )
485472
486- new_name = f"{ var } __{ other .name } "
487- self .resampled [new_name ] = resampled
488- new_vars .append (new_name )
489-
473+ result = xr .Dataset (resampled_vars )
490474 _record (
491- self . resampled ,
492- f"spatially resampled { new_vars } from { other .name } "
475+ result ,
476+ f"spatially resampled { list ( resampled_vars ) } from { other .name } "
493477 f"({ type (other ).__name__ } ) to { self .name } ({ type (self ).__name__ } )" ,
494478 )
495479
496480 save_to = self .reader_kwargs .get ("save_resampled_to" )
497481 if save_to :
498482 target = Path (save_to )
499483 target .parent .mkdir (parents = True , exist_ok = True )
500- self . resampled .to_netcdf (target )
501- _record (self . resampled , f"saved resampled data to { target } " )
484+ result .to_netcdf (target )
485+ _record (result , f"saved resampled data to { target } " )
502486
503- return self . resampled [ new_vars ]
487+ return result
0 commit comments