1111from ClimateGraph .reader import Reader
1212from ClimateGraph .reader .reader .reader import ReadSpec
1313from ClimateGraph .utils .dataset_utils import (
14+ _record ,
1415 change_unit ,
16+ dim_reduction ,
1517 normalize_vars ,
1618 time_resampling ,
1719)
1820from ClimateGraph .utils .general_utils import ReductionMethodEnum
21+ from ClimateGraph .utils .registry import RegistryMixin
1922
2023
21- class Data (ABC ):
24+ class Data (RegistryMixin , ABC ):
2225 """The Data abstract class.
2326
2427 A class that abstracts the core nature of environmental data, independent of the topology of the data.
2528 This class contains and implements attributes and methods common to all the Data that ClimateGraph is supposed to handle.
2629 """
2730
2831 registry : dict [str , type ["Data" ]] = {}
29- type_aliases : list [str ] = list ()
30-
31- # Allows for self registering, and alias registering, for then looking up the right Data Class.
32- def __init_subclass__ (cls , ** kwargs ):
33- """__init_subclass__ This Dunder method is being used to dinamically register all inheriting classes from Data, this helps with Data creation."""
34- super ().__init_subclass__ (** kwargs )
35- Data .registry [cls .__name__ .lower ()] = cls
36-
37- for alias in getattr (cls , "type_aliases" , []):
38- Data .registry [alias .lower ()] = cls
32+ aliases : list [str ] = list ()
33+ geom_dims : tuple [str , ...] = ()
3934
4035 @classmethod
4136 def get_data_subclass (cls , name : str ):
42- """get_data_subclass Method for getting a class object from a string, centralizes the lookup operation for further development of smart lookup.
43-
44- Parameters
45- ----------
46- name : str
47- String to use for lookup in the Data registry
48-
49- Returns
50- -------
51- type
52- Object of Data subclass requested
53-
54- Raises
55- ------
56- ValueError
57- No subclass available for the requested string
58- """
59- _name = name .lower ()
60- try :
61- data_class = cls .registry [_name ]
62- except KeyError as err :
63- raise ValueError (
64- f"No type named { name } recognized. Options are { Data .registry .keys ()} (case insensitive)."
65- ) from err
66- return data_class
37+ return cls .get_class (name )
6738
6839 @classmethod
69- def check_topology_type (cls , type : str ):
70- """check_topology_type Method for checking if a string correlates to a Data subclass, meant to have the same lookup mechanism as get_data_subclass
71-
72- Parameters
73- ----------
74- type : str
75- String to lookup in Data class registry.
76-
77- Returns
78- -------
79- boolean
80- Boolean representing existence of a correlation between the provided string and a Data subclass.
81- """
82- return type .lower () in cls .registry
40+ def check_topology_type (cls , type : str ) -> bool :
41+ return cls .check_class (type )
8342
8443 @classmethod
8544 def create (
@@ -334,6 +293,7 @@ def get_var(
334293 keep_dims : str | list [str ] | None = None ,
335294 reduction_dims : str | list [str ] | None = None ,
336295 as_array : bool = False ,
296+ dim_reduce : dict [str , str | dict ] | None = None ,
337297 ) -> xr .DataArray | np .ndarray :
338298 """get_var Get variable from the obj attribute.
339299
@@ -372,6 +332,9 @@ def get_var(
372332 )
373333 xa = self .obj .data_vars [var_name ]
374334
335+ if dim_reduce is not None :
336+ xa = dim_reduction (xa , dim_reduce , name = var_name )
337+
375338 if reduction_func is not None :
376339 reduction_func = (
377340 ReductionMethodEnum (reduction_func ).func
@@ -381,6 +344,10 @@ def get_var(
381344 if reduction_dims is None and keep_dims is not None :
382345 reduction_dims = list (set (self .dims ) - set (keep_dims ))
383346 xa = xa .reduce (reduction_func , reduction_dims )
347+ _record (
348+ xa ,
349+ f"reduced { var_name !r} over { reduction_dims } with { reduction_func .__name__ } " ,
350+ )
384351
385352 if in_unit is not None :
386353 xa = change_unit (xa , self .var_unit (var_name ), in_unit )
@@ -440,7 +407,6 @@ def resample_vars(
440407 time_interval : str | None = None ,
441408 radius_of_influence : int = 10000 ,
442409 time_tolerance : str | None = "30min" ,
443- # reduction_dims: str | List[str] | None = None, reduction_func: Callable | None = None
444410 ) -> xr .DataArray | xr .Dataset :
445411 """resample_vars Resample the requested vars using Pyresample and the geom attributes. Until now only NearestNeighbour method is being used.
446412
@@ -523,29 +489,44 @@ def _resample(x):
523489 fill_value = np .nan ,
524490 )
525491
526- # Here if im resampling into a point surface topology then height will always get dropped. Unless its a point in space not surface.
492+ # input_core_dims: only the geom_dims of the source (the horizontal dims
493+ # the pyresample geometry was built from). Any extra dims like z are left
494+ # out so apply_ufunc loops over them automatically, broadcasting the
495+ # horizontal resampling across each vertical level independently.
496+ src_geom_dims = [d for d in var_src .dims if d in set (other .geom_dims )]
497+ dst_geom_dims = [d for d in var_dst_dims if d in set (self .geom_dims )]
527498 resampled = xr .apply_ufunc (
528499 _resample ,
529500 var_src ,
530- input_core_dims = [
531- [d for d in var_src .dims if d != "time" ]
532- ], # remove time from core dims so it loops over just time
533- output_core_dims = [
534- [d for d in var_dst_dims if d != "time" ]
535- ], # Produces a new array with the new geom minus time
501+ input_core_dims = [src_geom_dims ],
502+ output_core_dims = [dst_geom_dims ],
536503 vectorize = True ,
537504 dask = "parallelized" ,
538505 output_dtypes = [var_src .dtype ],
539506 dask_gufunc_kwargs = {
540507 "output_sizes" : {
541508 name : value
542509 for name , value in var_dst_dims .items ()
543- if name != "time"
510+ if name in set ( self . geom_dims )
544511 }
545512 },
546513 )
547514
548515 new_name = f"{ var } __{ other .name } "
549- self .resampled [new_name ] = ( var_dst_dims , resampled . data )
516+ self .resampled [new_name ] = resampled
550517 new_vars .append (new_name )
518+
519+ _record (
520+ self .resampled ,
521+ f"spatially resampled { new_vars } from { other .name } "
522+ f"({ type (other ).__name__ } ) to { self .name } ({ type (self ).__name__ } )" ,
523+ )
524+
525+ save_to = self .reader_kwargs .get ("save_resampled_to" )
526+ if save_to :
527+ target = Path (save_to )
528+ target .parent .mkdir (parents = True , exist_ok = True )
529+ self .resampled .to_netcdf (target )
530+ _record (self .resampled , f"saved resampled data to { target } " )
531+
551532 return self .resampled [new_vars ]
0 commit comments