ClimateGraph 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. ClimateGraph/__init__.py +9 -0
  2. ClimateGraph/appkernel.py +148 -0
  3. ClimateGraph/cli.py +63 -0
  4. ClimateGraph/data/__init__.py +8 -0
  5. ClimateGraph/data/data.py +508 -0
  6. ClimateGraph/data/point_surface.py +91 -0
  7. ClimateGraph/data/regular_grid.py +25 -0
  8. ClimateGraph/data/satellite_swath.py +21 -0
  9. ClimateGraph/domain/__init__.py +11 -0
  10. ClimateGraph/domain/domain.py +181 -0
  11. ClimateGraph/domain/domains.py +279 -0
  12. ClimateGraph/plot/__init__.py +11 -0
  13. ClimateGraph/plot/plot.py +402 -0
  14. ClimateGraph/plot/plots.py +1054 -0
  15. ClimateGraph/plot/primitive.py +99 -0
  16. ClimateGraph/plot/primitives.py +160 -0
  17. ClimateGraph/reader/__init__.py +15 -0
  18. ClimateGraph/reader/reader/__init__.py +14 -0
  19. ClimateGraph/reader/reader/point_surface/__init__.py +7 -0
  20. ClimateGraph/reader/reader/point_surface/csv_base.py +135 -0
  21. ClimateGraph/reader/reader/point_surface/default.py +36 -0
  22. ClimateGraph/reader/reader/point_surface/dmc.py +10 -0
  23. ClimateGraph/reader/reader/point_surface/sinca.py +10 -0
  24. ClimateGraph/reader/reader/point_surface/single_file.py +60 -0
  25. ClimateGraph/reader/reader/point_surface/station_per_file.py +55 -0
  26. ClimateGraph/reader/reader/point_surface/variable_per_file.py +91 -0
  27. ClimateGraph/reader/reader/reader.py +353 -0
  28. ClimateGraph/reader/reader/regular_grid/__init__.py +7 -0
  29. ClimateGraph/reader/reader/regular_grid/chimere.py +24 -0
  30. ClimateGraph/reader/reader/regular_grid/default.py +51 -0
  31. ClimateGraph/reader/reader/regular_grid/wrf.py +53 -0
  32. ClimateGraph/utils/__init__.py +1 -0
  33. ClimateGraph/utils/control_model.py +292 -0
  34. ClimateGraph/utils/dataset_utils.py +287 -0
  35. ClimateGraph/utils/general_utils.py +259 -0
  36. ClimateGraph/utils/parser.py +269 -0
  37. ClimateGraph/utils/registry.py +81 -0
  38. ClimateGraph/utils/resample_engine.py +156 -0
  39. climategraph-0.4.0.dist-info/METADATA +177 -0
  40. climategraph-0.4.0.dist-info/RECORD +44 -0
  41. climategraph-0.4.0.dist-info/WHEEL +5 -0
  42. climategraph-0.4.0.dist-info/entry_points.txt +2 -0
  43. climategraph-0.4.0.dist-info/licenses/LICENSE +21 -0
  44. climategraph-0.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,508 @@
1
+ """The dataset abstraction.
2
+
3
+ Brings datasets onto a common set of dimensions regardless of their format, file
4
+ layout or shape, so the rest of the framework can work with them interchangeably.
5
+ """
6
+
7
+ import logging
8
+ from abc import ABC, abstractmethod
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import cartopy.crs as ccrs
13
+ import numpy as np
14
+ import xarray as xr
15
+
16
+ from ClimateGraph.reader import Reader
17
+ from ClimateGraph.reader.reader.reader import ReadSpec
18
+ from ClimateGraph.utils.dataset_utils import (
19
+ _record,
20
+ change_unit,
21
+ dim_reduction,
22
+ normalize_vars,
23
+ )
24
+ from ClimateGraph.utils.general_utils import ReductionMethodEnum
25
+ from ClimateGraph.utils.registry import RegistryMixin
26
+ from ClimateGraph.utils.resample_engine import ResampleEngine, get_engine
27
+
28
+ log = logging.getLogger(__name__)
29
+
30
+
31
+ class Data(RegistryMixin, ABC):
32
+ """The Data abstract class.
33
+
34
+ A class that abstracts the core nature of environmental data, independent of the topology of the data.
35
+ This class contains and implements attributes and methods common to all the Data that ClimateGraph is supposed to handle.
36
+ """
37
+
38
+ registry: dict[str, type["Data"]] = {}
39
+ aliases: list[str] = list()
40
+ geom_dims: tuple[str, ...] = ()
41
+
42
+ @classmethod
43
+ def get_data_subclass(cls, name: str):
44
+ return cls.get_class(name)
45
+
46
+ @classmethod
47
+ def check_topology_type(cls, type: str) -> bool:
48
+ return cls.check_class(type)
49
+
50
+ @classmethod
51
+ def create(
52
+ cls,
53
+ name: str,
54
+ topology: str,
55
+ reader: str,
56
+ path: Path | list[Path],
57
+ vars: dict[str, dict[str, str]] | list[str] | None,
58
+ crs: ccrs,
59
+ reader_kwargs: dict,
60
+ ):
61
+ """Build a Data object of the subclass matching ``topology``.
62
+
63
+ Parameters
64
+ ----------
65
+ name : str
66
+ Name of the object, used for reference inside ClimateGraph execution.
67
+ topology : str
68
+ Type of topology, used for lookup in the Data registry.
69
+ reader : str
70
+ Specific reader name used for reading the files that compose the data object. A reader must exist for the corresponding topology and reader name.
71
+ path : Path | List[Path]
72
+ Path or list of paths that compose the data object.
73
+ vars : Dict[str, Dict[str, str]]
74
+ Dictionary where keys are Variable names, and the value is another Dictionary with "name" (with the name of the variable in the files) and "unit" (with the "pint" unit name for this variable) keys.
75
+ crs : Cartopy.CRS
76
+ Cartopy Coordinate Reference System object
77
+ reader_kwargs : Dict
78
+ Other keyword arguments in Dict format passed directly to the Reader, allows for further reader specialization.
79
+
80
+ Returns
81
+ -------
82
+ Data
83
+ Data object created with the arguments in the adequate Data subclass
84
+ """
85
+ data_cls = cls.get_data_subclass(topology)
86
+ reader_cls = Reader.get_reader_subclass(topology, reader)
87
+ return data_cls(name, path, vars, reader_cls, crs, reader_kwargs=reader_kwargs)
88
+
89
+ def __init__(
90
+ self,
91
+ name: str,
92
+ path: Path | list[Path],
93
+ vars: dict[str, dict[str, str]] | list[str] | None,
94
+ reader: Reader,
95
+ crs: ccrs.CRS,
96
+ reader_kwargs: dict[str, Any] | None = None,
97
+ ):
98
+ """Create a dataset handle. Nothing is read until ``obj`` is accessed.
99
+
100
+ Parameters
101
+ ----------
102
+ name : str
103
+ Name for reference inside the ClimateGraph execution.
104
+ path : Path | list[Path]
105
+ Path or list of paths that compose the data objects
106
+ vars : Dict[str, Dict[str, str]]
107
+ Dictionary where keys are Variable names, and the value is another Dictionary with "name" (with the name of the variable in the files) and "unit" (with the "pint" unit name for this variable) keys.
108
+ reader : Reader
109
+ Specific reader object used for reading the files that compose the data object. A reader must exist for the corresponding topology and reader name.
110
+ crs : ccrs.CRS
111
+ Cartopy Coordinate Reference System object
112
+ reader_kwargs : Dict[str, Any] | None, optional
113
+ Other keyword arguments in Dict format passed directly to the Reader, allows for further reader specialization, by default None
114
+ """
115
+ self.reader = reader
116
+ self.reader_kwargs = {} if reader_kwargs is None else reader_kwargs
117
+
118
+ self._obj = None
119
+ self._geom = None
120
+ self._resample_cache: dict = {}
121
+ self._vars = normalize_vars(vars)
122
+ self._path = None
123
+ self._bbox = None
124
+ self._dims = None
125
+
126
+ self.name = name
127
+ self.path = path
128
+ self.crs = crs
129
+
130
+ def copy(self):
131
+ """Return a copy of this object. Used when a domain produces a filtered dataset.
132
+
133
+ Returns
134
+ -------
135
+ Data
136
+ Exact replica of the Data object
137
+ """
138
+ new = self.__class__(
139
+ self.name,
140
+ self.path,
141
+ self.vars,
142
+ self.reader,
143
+ self.crs,
144
+ reader_kwargs=self.reader_kwargs,
145
+ )
146
+ new.obj = self._obj
147
+ new._geom = self._geom
148
+ new._bbox = self._bbox
149
+ new._dims = self._dims
150
+
151
+ return new
152
+
153
+ @abstractmethod
154
+ def _set_geom(self):
155
+ """Build the pyresample geometry used for resampling.
156
+
157
+ The core of the topology abstraction: each topology defines its own
158
+ spatial geometry here.
159
+ """
160
+ pass
161
+
162
+ @property
163
+ def obj(self) -> xr.Dataset:
164
+ """The underlying dataset, loaded on first access.
165
+
166
+ Returns
167
+ -------
168
+ xr.Dataset
169
+ Xarray dataset of the Data object.
170
+ """
171
+ if self._obj is None:
172
+ self.load_obj()
173
+ return self._obj
174
+
175
+ @obj.setter
176
+ def obj(self, _obj: xr.Dataset):
177
+ """Replace the underlying dataset, clearing everything derived from the old one.
178
+
179
+ Parameters
180
+ ----------
181
+ _obj : xr.Dataset
182
+ Xarray Dataset object meant to be set in the Data object.
183
+ """
184
+ self._obj = _obj
185
+ self._bbox = None
186
+ self._geom = None
187
+ self._dims = None
188
+ self._resample_cache = {}
189
+
190
+ @property
191
+ def geom(self):
192
+ if self._geom is None:
193
+ self._set_geom()
194
+ return self._geom
195
+
196
+ @property
197
+ def vars(self):
198
+ """The variable names this dataset exposes.
199
+
200
+ Returns
201
+ -------
202
+ Dict[str, Dict[str, str]] | None
203
+ Normalized dict mapping each user-facing variable name to a dict with
204
+ "name" (file-native name) and "unit" (pint unit, possibly None)
205
+ keys, or None when the data block declared no vars, and vars will be treated
206
+ by native file names.
207
+ """
208
+ return self._vars
209
+
210
+ def var_unit(self, var_name: str) -> str | None:
211
+ """Resolve the declared source unit for ``var_name``.
212
+
213
+ Single choke point for reading a variable's declared unit, so callers
214
+ never index self.vars[...]["unit"]
215
+
216
+ Parameters
217
+ ----------
218
+ var_name : str
219
+ User-facing variable name.
220
+
221
+ Returns
222
+ -------
223
+ str | None
224
+ The declared pint unit, or ``None`` when vars wasn't declared.
225
+ """
226
+ if isinstance(self._vars, dict):
227
+ entry = self._vars.get(var_name)
228
+ if entry is not None:
229
+ return entry.get("unit")
230
+ return None
231
+
232
+ @property
233
+ def dims(self):
234
+ """Mapping of dimension name to dimension size.
235
+
236
+ Returns
237
+ -------
238
+ Dict[str, int]
239
+ Dictionary mapping dimension names to dimension size.
240
+ """
241
+ if self._dims is None:
242
+ self._dims = dict(self.obj.sizes)
243
+ return self._dims
244
+
245
+ @property
246
+ def bbox(self):
247
+ """Bounding box around the spatial extent of the dataset.
248
+
249
+ Returns
250
+ -------
251
+ Tuple[float, float, float, float]
252
+ Tuple representing (Minimum Longitude, Minimum Latitude, Maximum Longitude, Maximum Latitude)
253
+ """
254
+ if self._bbox is None:
255
+ lons, lats = self.get_coordinates(["longitude", "latitude"])
256
+ self._bbox = (
257
+ float(lons.min()),
258
+ float(lats.min()),
259
+ float(lons.max()),
260
+ float(lats.max()),
261
+ )
262
+ return self._bbox
263
+
264
+ def load_obj(self):
265
+ """Read the dataset into ``obj`` by building
266
+ a ReadSpec and invoking ``reader.read(spec)``. ``load_mode``,
267
+ ``cache_dir`` and ``engine`` are pulled out of ``reader_kwargs``
268
+ if present; the remainder lives on ``spec.extras`` for the
269
+ subclass to consume.
270
+
271
+ Returns
272
+ -------
273
+ xr.Dataset
274
+ Xarray dataset of the Data object.
275
+ """
276
+ extras = dict(self.reader_kwargs)
277
+ load_mode = extras.pop("load_mode", "safe")
278
+ cache_dir = extras.pop("cache_dir", None)
279
+ engine = extras.pop("engine", None)
280
+ spec = ReadSpec(
281
+ paths=self.path if isinstance(self.path, list) else [self.path],
282
+ vars=self.vars,
283
+ load_mode=load_mode,
284
+ cache_dir=cache_dir,
285
+ engine=engine,
286
+ extras=extras,
287
+ )
288
+ self._obj = self.reader.read(spec)
289
+ return self._obj
290
+
291
+ def get_var(
292
+ self,
293
+ var_name: str,
294
+ in_unit: str | None = None,
295
+ reduction_func: str | None = None,
296
+ keep_dims: str | list[str] | None = None,
297
+ reduction_dims: str | list[str] | None = None,
298
+ as_array: bool = False,
299
+ dim_reduce: dict[str, str | dict] | None = None,
300
+ ) -> xr.DataArray | np.ndarray:
301
+ """Get a variable, reduced and unit-converted, ready to draw.
302
+
303
+ Parameters
304
+ ----------
305
+ var_name : str
306
+ Variable name.
307
+ in_unit : str | None, optional
308
+ Measure of unit in which to convert the variable, by default None
309
+ reduction_func : str | None, optional
310
+ name of function with which to reduce the variable dimensions, dimensions in reduction_dims or, that aren't in keep_dims arg. Works only if keep_dims or reduction_dims is also specified. By default None
311
+ keep_dims : str | list[str] | None, optional
312
+ Dimensions to keep after reducing the others with the reduction_func. Only works if reduction_func is not None. By default None
313
+ reduction_dims : str | list[str] | None, optional
314
+ Dimensions to reduce with the reduction_func. Only works if reduction_func is not None. By default None
315
+ as_array : bool, optional
316
+ Boolean representing if the resulting data will be returned in numpy.ndarray format, by default False
317
+
318
+ Returns
319
+ -------
320
+ xr.DataArray | numpy.ndarray
321
+ Variable obtained from the obj attribute. Processed if required.
322
+
323
+ Raises
324
+ ------
325
+ KeyError
326
+ If the variable can't be found in the obj object.
327
+ """
328
+ if var_name not in self.obj:
329
+ raise KeyError(
330
+ f"Variable {var_name!r} not found in dataset {self.name!r} "
331
+ f"(available: {list(self.obj.data_vars)})."
332
+ )
333
+ xa = self.obj.data_vars[var_name]
334
+
335
+ if dim_reduce is not None:
336
+ xa = dim_reduction(xa, dim_reduce, name=var_name)
337
+
338
+ if reduction_func is not None:
339
+ reduction_func = (
340
+ ReductionMethodEnum(reduction_func).func
341
+ if isinstance(reduction_func, str)
342
+ else reduction_func
343
+ )
344
+ if reduction_dims is None and keep_dims is not None:
345
+ reduction_dims = list(set(self.dims) - set(keep_dims))
346
+ 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
+ )
351
+
352
+ if in_unit is not None:
353
+ xa = change_unit(xa, self.var_unit(var_name), in_unit)
354
+
355
+ if as_array:
356
+ xa = xa.to_numpy()
357
+
358
+ return xa
359
+
360
+ def get_coordinates(
361
+ self,
362
+ coord_names: list[str] | str,
363
+ as_array: bool = False,
364
+ ) -> list[xr.DataArray] | xr.DataArray | list[np.ndarray] | np.ndarray:
365
+ """Get one or more coordinates from the dataset.
366
+
367
+ Parameters
368
+ ----------
369
+ coord_names : list[str] | str
370
+ Coordinate name or names.
371
+ as_array : bool, optional
372
+ Boolean representing if the resulting data will be returned in numpy.ndarray format, by default False
373
+
374
+ Returns
375
+ -------
376
+ list[xr.DataArray] | xr.DataArray | list[np.ndarray] | np.ndarray
377
+ Coordinate(s) obtained from the obj attribute. Processed if required.
378
+
379
+ Raises
380
+ ------
381
+ KeyError
382
+ If a coordinate can't be found in the obj object.
383
+ """
384
+ obj = self.obj
385
+ if isinstance(coord_names, str):
386
+ coord_names = [coord_names]
387
+ missing_coords = [i for i in coord_names if i not in obj.coords]
388
+ if len(missing_coords) > 0:
389
+ raise KeyError(f"Coordinates {missing_coords} not in {self.name} dataset.")
390
+
391
+ coords = [obj.coords[coord] for coord in coord_names]
392
+ if as_array:
393
+ coords = [array.to_numpy() for array in coords]
394
+
395
+ if len(coords) == 1:
396
+ coords = coords[0]
397
+ return coords
398
+
399
+ def resample_vars(
400
+ self,
401
+ other: "Data",
402
+ vars: str | list[str],
403
+ radius_of_influence: int = 10000,
404
+ engine: str | ResampleEngine = "pyresample",
405
+ engine_kwargs: dict | None = None,
406
+ ) -> xr.Dataset:
407
+ """Project ``other``'s vars onto ``self``'s spatial geometry.
408
+
409
+ Purely spatial: each variable of ``other`` is reprojected onto ``self``'s
410
+ geometry with the requested engine, keeping ``other``'s own time axis and any
411
+ extra dims (z, member, …) untouched, and inheriting ``self``'s spatial
412
+ coordinates (site / latitude / longitude / region / …). It does NOT align time
413
+ or convert units — those are the caller's concern. Two sources with different
414
+ time extents resampled onto the same target never clobber each other.
415
+
416
+ The result is memoized on the source (``other._resample_cache``), keyed by
417
+ ``(self.name, vars, radius, engine, engine_kwargs)``, so repeated
418
+ ``resample_to`` domains sharing a target recompute the projection once and hit
419
+ the cache thereafter. The cache is invalidated whenever ``other.obj`` is
420
+ reassigned; the target's geometry is assumed stable across a run.
421
+
422
+ Parameters
423
+ ----------
424
+ other : Data
425
+ Data whose vars are reprojected onto ``self``'s geometry.
426
+ vars : str | list[str]
427
+ Variable name or list of names to resample.
428
+ radius_of_influence : int, optional
429
+ Radius length in meters to use for resampling. by default 10000
430
+ engine : str | ResampleEngine, optional
431
+ Resample backend name or instance. Default ``"pyresample"``.
432
+ engine_kwargs : dict | None, optional
433
+ Extra kwargs forwarded to the engine constructor
434
+ (e.g. ``method``, ``sigmas``). Default None.
435
+
436
+ Returns
437
+ -------
438
+ xr.Dataset
439
+ One variable per input var, keyed ``"{var}__{other.name}"``, on
440
+ ``self``'s geometry and carrying ``other``'s time / extra dims.
441
+ """
442
+ if isinstance(vars, str):
443
+ vars = [vars]
444
+
445
+ engine_key = engine if isinstance(engine, str) else type(engine).__name__
446
+ cache_key = (
447
+ self.name,
448
+ tuple(sorted(vars)),
449
+ radius_of_influence,
450
+ engine_key,
451
+ tuple(sorted((engine_kwargs or {}).items())),
452
+ )
453
+ if cache_key in other._resample_cache:
454
+ log.debug("resample cache hit: %s -> %s (%s)", other.name, self.name, vars)
455
+ return other._resample_cache[cache_key]
456
+
457
+ resample_engine = get_engine(engine, **(engine_kwargs or {}))
458
+ info = resample_engine.prepare(
459
+ other.geom,
460
+ self.geom,
461
+ radius_of_influence=radius_of_influence,
462
+ )
463
+ _resample = resample_engine.make_resampler(
464
+ info, self.geom.shape, other.geom.shape
465
+ )
466
+
467
+ dst_geom_dims = [d for d in self.obj.dims if d in set(self.geom_dims)]
468
+ dst_sizes = {d: self.obj.sizes[d] for d in dst_geom_dims}
469
+ dst_coords = {
470
+ name: coord
471
+ for name, coord in self.obj.coords.items()
472
+ if set(coord.dims) <= set(self.geom_dims)
473
+ }
474
+
475
+ resampled_vars = {}
476
+ for var in vars:
477
+ var_src = other.get_var(var)
478
+ src_geom_dims = [d for d in var_src.dims if d in set(other.geom_dims)]
479
+
480
+ resampled = xr.apply_ufunc(
481
+ _resample,
482
+ var_src,
483
+ input_core_dims=[src_geom_dims],
484
+ output_core_dims=[dst_geom_dims],
485
+ dask="parallelized",
486
+ output_dtypes=[var_src.dtype],
487
+ dask_gufunc_kwargs={"output_sizes": dst_sizes},
488
+ )
489
+ resampled_vars[f"{var}__{other.name}"] = resampled.assign_coords(dst_coords)
490
+
491
+ result = xr.Dataset(resampled_vars)
492
+ # load so anything downstream doesn't re compute all processed until here
493
+ result = result.load()
494
+ _record(
495
+ result,
496
+ f"spatially resampled {list(resampled_vars)} from {other.name} "
497
+ f"({type(other).__name__}) to {self.name} ({type(self).__name__})",
498
+ )
499
+
500
+ save_to = self.reader_kwargs.get("save_resampled_to")
501
+ if save_to:
502
+ target = Path(save_to)
503
+ target.parent.mkdir(parents=True, exist_ok=True)
504
+ result.to_netcdf(target)
505
+ _record(result, f"saved resampled data to {target}")
506
+
507
+ other._resample_cache[cache_key] = result
508
+ return result
@@ -0,0 +1,91 @@
1
+ """Station and point-observation topology."""
2
+
3
+ from collections.abc import Sequence
4
+ from pathlib import Path
5
+
6
+ import cartopy.crs as ccrs
7
+ import numpy as np
8
+ import xarray as xr
9
+ from pyresample.geometry import SwathDefinition
10
+
11
+ from .data import Data
12
+
13
+
14
+ class _PrebuiltReader:
15
+ """No-op reader for in-memory PointSurface objects whose ``obj`` is set directly.
16
+
17
+ ``from_points`` builds the dataset itself, so ``read`` is never reached; it
18
+ only exists to satisfy ``Data.__init__``/``copy`` which carry a reader around.
19
+ """
20
+
21
+ @staticmethod
22
+ def read(spec):
23
+ raise RuntimeError(
24
+ "_PrebuiltReader has no files to read; its obj is set in-memory."
25
+ )
26
+
27
+
28
+ class PointSurface(Data):
29
+ """The PointSurface topology class.
30
+
31
+ A class that gives particular representation to Point Surface data. This data should be in ("time", "site") dimensions.
32
+ """
33
+
34
+ aliases = ["pt_sfc", "point_surface", "point"]
35
+ geom_dims = ("site",)
36
+
37
+ def _set_geom(self):
38
+ """Build the pyresample ``SwathDefinition`` over the one-dimensional site coordinates."""
39
+ lons, lats = self.get_coordinates(["longitude", "latitude"], as_array=True)
40
+ self._geom = SwathDefinition(lons=lons, lats=lats, crs=self.crs)
41
+
42
+ @classmethod
43
+ def from_points(
44
+ cls,
45
+ name: str,
46
+ names: Sequence[str],
47
+ lats: Sequence[float],
48
+ lons: Sequence[float],
49
+ crs: ccrs.CRS | None = None,
50
+ ) -> "PointSurface":
51
+ """Build an in-memory point target from named coordinates.
52
+
53
+ Used as the resample target of a ``Points`` domain: a coords-only
54
+ PointSurface whose ``site`` dimension holds the provided **names**, with
55
+ ``latitude``/``longitude`` on ``site`` (so ``_set_geom`` can build the
56
+ SwathDefinition). No data variables — the resample only needs the geometry
57
+ and the site-scoped coords. ``obj`` is set directly, so no reader runs.
58
+
59
+ Parameters
60
+ ----------
61
+ name : str
62
+ Identity for the target (used in the resample cache key / history).
63
+ names : Sequence[str]
64
+ Point names — become the ``site`` index (labels and ``.sel`` keys).
65
+ lats, lons : Sequence[float]
66
+ Latitude / longitude per point, in the ``crs`` (default lat/lon).
67
+ crs : ccrs.CRS | None, optional
68
+ Coordinate reference system. Defaults to ``PlateCarree`` (lat/lon).
69
+
70
+ Returns
71
+ -------
72
+ PointSurface
73
+ An in-memory point target ready to resample onto.
74
+ """
75
+ site = np.asarray(list(names), dtype=object)
76
+ ds = xr.Dataset(
77
+ coords={
78
+ "site": site,
79
+ "latitude": ("site", np.asarray(lats, dtype=float)),
80
+ "longitude": ("site", np.asarray(lons, dtype=float)),
81
+ }
82
+ )
83
+ instance = cls(
84
+ name=name,
85
+ path=Path(f"memory://{name}"),
86
+ vars=None,
87
+ reader=_PrebuiltReader,
88
+ crs=crs or ccrs.PlateCarree(),
89
+ )
90
+ instance.obj = ds
91
+ return instance
@@ -0,0 +1,25 @@
1
+ """Structured latitude/longitude grid topology."""
2
+
3
+ from pyresample import SwathDefinition
4
+
5
+ from .data import Data
6
+
7
+
8
+ class RegularGrid(Data):
9
+ """The RegularGrid topology class.
10
+
11
+ A class that implements Regular Grid specific logic. This data should be in ("time", "x", "y", "z") dimensions.
12
+ """
13
+
14
+ aliases = ["regular_grid", "grid", "regulargrid"]
15
+ geom_dims = ("x", "y")
16
+
17
+ def _set_geom(self):
18
+ """Build the pyresample geometry.
19
+
20
+ Uses a ``SwathDefinition`` rather than an ``AreaDefinition``, which does
21
+ not behave correctly here.
22
+ """
23
+ # TODO: change to AreaDefinition
24
+ lons, lats = self.get_coordinates(["longitude", "latitude"], as_array=True)
25
+ self._geom = SwathDefinition(lons=lons, lats=lats, crs=self.crs)
@@ -0,0 +1,21 @@
1
+ """Satellite swath topology.
2
+
3
+ not usable yet
4
+ """
5
+
6
+ from pyresample.geometry import SwathDefinition
7
+
8
+ from .data import Data
9
+
10
+
11
+ class SatelliteSwath(Data):
12
+ """The Satellite Swath topology class. Not being used and not recommended.
13
+
14
+ A class that gives particular representation to Satellite Swath data.
15
+ """
16
+
17
+ def _set_geom(self):
18
+ lons, lats = self.get_coordinates(["longitude", "latitude"])
19
+ swath_def = SwathDefinition(lons=lons, lats=lats, crs=self.crs)
20
+ self.geom = swath_def.compute_optimal_bb_area(self.crs.proj4_params)
21
+ return self.geom
@@ -0,0 +1,11 @@
1
+ """Spatial and attribute subsetting applied to datasets."""
2
+
3
+ import importlib
4
+ import pkgutil
5
+
6
+ for module in pkgutil.iter_modules(__path__):
7
+ importlib.import_module(f"{__name__}.{module.name}")
8
+
9
+ from .domain import Domain
10
+
11
+ __all__ = ["Domain"]