grid-process 1.0.0__tar.gz → 1.1.0__tar.gz
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.
- grid_process-1.1.0/CHANGELOG.rst +96 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/LICENSE +0 -1
- {grid_process-1.0.0 → grid_process-1.1.0}/PKG-INFO +2 -2
- {grid_process-1.0.0 → grid_process-1.1.0}/README.rst +1 -1
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/config/grid_process.yaml +6 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/config/library_settings.py +1 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/data_model.py +132 -12
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/grid_interp.py +542 -200
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/grid_interp_utils.py +20 -11
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/helpers.py +71 -1
- grid_process-1.1.0/grid_process/optimization.py +114 -0
- grid_process-1.1.0/grid_process/py.typed +0 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/pyproject.toml +17 -2
- grid_process-1.0.0/CHANGELOG.rst +0 -15
- grid_process-1.0.0/grid_process/optimization.py +0 -50
- {grid_process-1.0.0 → grid_process-1.1.0}/AUTHORS +0 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/__init__.py +0 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/aggreg.py +0 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/blending.py +0 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/config/__init__.py +0 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/da_utils.py +0 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/ds_utils.py +0 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/ens_stats.py +0 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/sun.py +0 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/time_utils.py +0 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/timeseries.py +0 -0
- {grid_process-1.0.0 → grid_process-1.1.0}/grid_process/units_conv.py +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
=========
|
|
2
|
+
Changelog
|
|
3
|
+
=========
|
|
4
|
+
|
|
5
|
+
1.1.0 (2026-11-12)
|
|
6
|
+
------------------
|
|
7
|
+
|
|
8
|
+
**Changes of results** (compared to 1.0.0, see the entries below for details)
|
|
9
|
+
|
|
10
|
+
* ``interp2poi`` and ``interp2grid`` (outside of the regular grid interpolation) search nearest neighbors and
|
|
11
|
+
triangulate in meters instead of latitude/longitude degrees: neighbors, IDW weights and ``linear``/``cubic``
|
|
12
|
+
results change slightly for grids that were searched in degrees, including native LV95 grids with
|
|
13
|
+
latitude/longitude coordinates
|
|
14
|
+
* grids in rotated pole or latitude/longitude on a sphere without datum (e.g. a CF grid mapping with
|
|
15
|
+
``earth_radius``) now get the datum shift to Swiss coordinates (about 180 m)
|
|
16
|
+
* wrong values or NaN are fixed for grids with integer coordinates and for latitude/longitude grids whose longitude
|
|
17
|
+
convention (0..360 or -180..180) differs from the one of the POIs or destination grid
|
|
18
|
+
* ``create_dst_grid`` (with ``topright`` and ``resolution``) and ``create_dst_grid_from_src`` return different
|
|
19
|
+
destination grids: exact resolution, and the extent of the source domain reprojected in the destination CRS
|
|
20
|
+
* ``grid_interp._get_buffer`` is renamed to ``get_buffer`` (now public)
|
|
21
|
+
|
|
22
|
+
**Changes**
|
|
23
|
+
|
|
24
|
+
* added a ``py.typed`` marker so that type checkers use the inline type hints of ``grid-process`` (PEP 561)
|
|
25
|
+
* added example notebooks to the documentation illustrating ``interp2poi`` and ``interp2grid`` with open data, with
|
|
26
|
+
their dependencies in the optional ``example`` dependency group. Two of them are global: GFS 2 m temperature to
|
|
27
|
+
stations worldwide (compared with GHCN-hourly observations) and ERA5 2 m temperature to the IFS 0.1° grid
|
|
28
|
+
* fixed ``interp2grid`` and ``interp2poi`` truncating reprojected coordinates when the grid coordinates are
|
|
29
|
+
integers (e.g. an LV95 destination grid built with ``np.arange``), which gave wrong values or NaN
|
|
30
|
+
* fixed ``create_dst_grid`` with ``topright`` and ``resolution``: the resolution is now exact and ``topright``
|
|
31
|
+
a closed upper bound, instead of the grid spacing being stretched to end exactly on ``topright``
|
|
32
|
+
* fixed ``interp2poi`` and ``interp2grid`` (outside of the regular grid interpolation) searching nearest neighbors
|
|
33
|
+
and triangulating (``linear``, ``cubic``) in latitude/longitude degrees, where a degree of longitude is shorter
|
|
34
|
+
than a degree of latitude (about 0.68 times in Switzerland). Distances are now always in meters: native
|
|
35
|
+
coordinates projected in meters are used even if latitude/longitude coordinates are present (e.g. LV95 grids),
|
|
36
|
+
and latitude/longitude grids are converted to Swiss LV95, or to earth-centered earth-fixed coordinates (ECEF) far
|
|
37
|
+
from Switzerland. Neighbors, IDW weights and ``linear``/``cubic`` results change slightly for grids that were
|
|
38
|
+
searched in latitude/longitude, including native LV95 grids with latitude/longitude coordinates
|
|
39
|
+
* fixed the reprojection of grids in rotated pole (or latitude/longitude) on a sphere without datum, e.g. from a CF
|
|
40
|
+
grid mapping with ``earth_radius``: their latitudes and longitudes are now considered as WGS84 ones, as their
|
|
41
|
+
latitude/longitude coordinates usually are. The datum shift to Swiss coordinates (about 180 m) was left out.
|
|
42
|
+
``parse_grid_mapping`` (and ``get_grid_crs``) now return such CF mappings directly on WGS84, without
|
|
43
|
+
``pyproj.CRS.from_cf``, which took about 0.3 s for a rotated pole; other mappings parsed by ``from_cf`` are cached
|
|
44
|
+
* added the support of global and other non-Swiss latitude/longitude grids by ``interp2poi`` and ``interp2grid``
|
|
45
|
+
(except ``linear``, ``linear_3D`` and ``cubic``, which raise an error), with nearest neighbor searches in ECEF
|
|
46
|
+
coordinates. The maximum distance of the destination to the LV95 central northing for using LV95 is set by the
|
|
47
|
+
new setting ``lv95_max_offset`` (environment variable ``GRID_PROCESS__lv95_max_offset``, default 700 km)
|
|
48
|
+
* changed the methods with altitude, which no longer reproject to Swiss coordinates when the source grid or the
|
|
49
|
+
destination has another CRS projected in meters (e.g. UTM)
|
|
50
|
+
* added an explicit error when the destination grid of ``interp2grid`` is unstructured, which is not supported
|
|
51
|
+
* fixed ``interp2poi`` and ``interp2grid`` with latitude/longitude source grids whose longitude convention
|
|
52
|
+
(0..360 or -180..180) differs from the one of the POIs or destination grid: the source was sliced to the
|
|
53
|
+
wrong longitudes (wrong values in the generic path, e.g. for a global destination grid) and the regular grid
|
|
54
|
+
interpolation (``try_reg=True``) returned NaN. Global latitude/longitude grids are also interpolated across
|
|
55
|
+
their 360 degree seam. When slicing a latitude/longitude source grid, the longitude buffer is widened by
|
|
56
|
+
1 / cos(latitude) (as nearest neighbors are now searched in meters) and all longitudes are kept close to the
|
|
57
|
+
poles
|
|
58
|
+
* changed the ``temp`` method of ``interp2poi``: POIs whose nearest grid points all have the same altitude (e.g.
|
|
59
|
+
close to the poles of a latitude/longitude grid, whose pole rows repeat the same point, or on flat orography)
|
|
60
|
+
use the fixed lapse rate fallback (as POIs far above or below their neighborhood) instead of raising an error
|
|
61
|
+
* fixed ``create_dst_grid_from_src``: the destination grid now spans exactly the bounding box of the source domain
|
|
62
|
+
reprojected in the destination CRS (from native coordinates projected in meters when available), instead of the
|
|
63
|
+
reprojected corners of its latitude/longitude bounding box, which could be much larger (e.g. +80 km for ICON-CH2
|
|
64
|
+
in LV95) or miss parts of the domain (e.g. 28 km for IFS in LV95)
|
|
65
|
+
* fixed ``set_grid_mapping`` with CF grid mappings holding numpy arrays or non-float64 numpy values (e.g. a
|
|
66
|
+
``standard_parallel`` array read from a netCDF file), which raised a ``TypeError``
|
|
67
|
+
* changed ``memoize``: the least recently used result is evicted (instead of the oldest one), the cache key can be
|
|
68
|
+
computed by an optional ``key`` function (only when caching is active), a cache size of 0 deactivates caching
|
|
69
|
+
without overhead, and the cache is safe to use from several threads, also when the process forks (the fork waits
|
|
70
|
+
until no thread is using the cache). The ``hkey`` keyword argument still works
|
|
71
|
+
* fixed the in-memory caches of the POI and grid reprojections, whose keys left out the source CRS, and the cache
|
|
72
|
+
of the regular grid nearest interpolation, which failed with a POI dimension name other than ``poi``
|
|
73
|
+
(``GRID_PROCESS__data_model__poi__dim``)
|
|
74
|
+
* documented the in-memory caches of ``interp2poi`` and ``interp2grid`` and how to set their sizes or deactivate
|
|
75
|
+
them with the environment variables ``GRID_PROCESS__cache_size__<name>`` (Usage page)
|
|
76
|
+
* changed ``interp2poi`` and ``interp2grid`` outside of the regular grid interpolation to flatten structured
|
|
77
|
+
grids with numpy instead of ``xarray.stack``, and ``interp2poi`` to look for the grid points with NaN in all
|
|
78
|
+
fields (excluded, as before) only among those with NaN in the middle field. Later calls with cached neighbors are
|
|
79
|
+
faster on large grids (e.g. 0.13 s to 0.05 s for a global 0.25 degree grid to 33 POIs, 1.6 s to 0.5 s from this
|
|
80
|
+
grid to a global 0.1 degree grid), with identical results
|
|
81
|
+
* fixed ``interp2poi`` raising an ``IndexError`` when all POIs are outside an unstructured grid (no grid point left
|
|
82
|
+
after slicing): they now get NaN, as outside of the regular grid interpolation
|
|
83
|
+
* made ``grid_interp.get_buffer`` public (renamed from ``_get_buffer``): the slicing buffer in grid points of an
|
|
84
|
+
interpolation method and its arguments, e.g. to slice the input data with ``slice_boundingbox`` beforehand
|
|
85
|
+
|
|
86
|
+
1.0.0
|
|
87
|
+
-----
|
|
88
|
+
|
|
89
|
+
First open-source release of ``grid-process``, the continuation of the internal MeteoSwiss library
|
|
90
|
+
``gridefix-process`` (last internal version: 6.1.0).
|
|
91
|
+
|
|
92
|
+
**Breaking Change** (compared to ``gridefix-process`` 6.1.0)
|
|
93
|
+
|
|
94
|
+
* renamed the package from ``gridefix-process`` to ``grid-process`` (import name ``grid_process``)
|
|
95
|
+
* renamed the environment variable prefix from ``GRIDEFIX_PROCESS__`` to ``GRID_PROCESS__``
|
|
96
|
+
* versioning restarts at ``1.0.0``
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: grid-process
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Weather forecast oriented Python library to process xarray Datasets
|
|
5
5
|
License-Expression: BSD-3-Clause
|
|
6
6
|
License-File: AUTHORS
|
|
@@ -138,7 +138,7 @@ Migrating from gridefix-process
|
|
|
138
138
|
* Package and import name: ``gridefix-process`` / ``gridefix_process`` became ``grid-process`` / ``grid_process``.
|
|
139
139
|
* Environment variable prefix: ``GRIDEFIX_PROCESS__`` became ``GRID_PROCESS__``
|
|
140
140
|
(e.g. ``GRID_PROCESS__data_model__validtime``).
|
|
141
|
-
* Versioning restarts at ``1.0.0``; the last internal release was ``gridefix-process`` 6.0.
|
|
141
|
+
* Versioning restarts at ``1.0.0``; the last internal release was ``gridefix-process`` 6.1.0.
|
|
142
142
|
|
|
143
143
|
Release the Project
|
|
144
144
|
-------------------
|
|
@@ -106,7 +106,7 @@ Migrating from gridefix-process
|
|
|
106
106
|
* Package and import name: ``gridefix-process`` / ``gridefix_process`` became ``grid-process`` / ``grid_process``.
|
|
107
107
|
* Environment variable prefix: ``GRIDEFIX_PROCESS__`` became ``GRID_PROCESS__``
|
|
108
108
|
(e.g. ``GRID_PROCESS__data_model__validtime``).
|
|
109
|
-
* Versioning restarts at ``1.0.0``; the last internal release was ``gridefix-process`` 6.0.
|
|
109
|
+
* Versioning restarts at ``1.0.0``; the last internal release was ``gridefix-process`` 6.1.0.
|
|
110
110
|
|
|
111
111
|
Release the Project
|
|
112
112
|
-------------------
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
# number of results kept by each in-memory cache (least recently used evicted), 0 to deactivate it. Override with
|
|
2
|
+
# the environment variables GRID_PROCESS__cache_size__<name>, set before importing grid_process
|
|
1
3
|
cache_size:
|
|
2
4
|
nn_lookup: 10
|
|
3
5
|
nn_lookup_regular: 10
|
|
4
6
|
reproject_poi: 6
|
|
5
7
|
reproject_grid: 6
|
|
6
8
|
|
|
9
|
+
# [m] max |N_LV95 - 1 200 000| of the destination for interpolating in LV95 when both grids are
|
|
10
|
+
# in lat/lon (otherwise ECEF). 700 km ≈ 0.6 % scale error, covers ICON-CH1/CH2 (523 km)
|
|
11
|
+
lv95_max_offset: 700000
|
|
12
|
+
|
|
7
13
|
data_model: # supported names
|
|
8
14
|
grid:
|
|
9
15
|
unstructured_dim: ["cell", "points"]
|
|
@@ -10,12 +10,15 @@ Module with utility functions to deal with the numerous supported data models
|
|
|
10
10
|
|
|
11
11
|
import logging
|
|
12
12
|
import json
|
|
13
|
+
from functools import lru_cache
|
|
13
14
|
from typing import Any, TypeVar, Literal, overload
|
|
14
15
|
from collections.abc import Callable
|
|
15
16
|
|
|
16
17
|
import numpy as np
|
|
17
18
|
from numpy.typing import NDArray
|
|
18
19
|
from pyproj import CRS
|
|
20
|
+
from pyproj.crs import DerivedGeographicCRS
|
|
21
|
+
from pyproj.crs.coordinate_operation import PoleRotationNetCDFCFConversion
|
|
19
22
|
import geopandas as gpd
|
|
20
23
|
import xarray as xr
|
|
21
24
|
|
|
@@ -203,12 +206,6 @@ def set_grid_mapping(ds: xr.Dataset) -> xr.Dataset:
|
|
|
203
206
|
data variable encoding the grid mapping is removed.
|
|
204
207
|
"""
|
|
205
208
|
|
|
206
|
-
def serializable_mapping() -> dict[str, Any]:
|
|
207
|
-
return {
|
|
208
|
-
key: float(value) if isinstance(value, np.floating) else value
|
|
209
|
-
for key, value in ds_out[mapname].attrs.items()
|
|
210
|
-
}
|
|
211
|
-
|
|
212
209
|
ds_out = ds.copy(deep=False)
|
|
213
210
|
grid_map = set()
|
|
214
211
|
for varname, da in ds_out.items():
|
|
@@ -224,7 +221,7 @@ def set_grid_mapping(ds: xr.Dataset) -> xr.Dataset:
|
|
|
224
221
|
|
|
225
222
|
mapname = grid_map.pop()
|
|
226
223
|
if {"grid_mapping", "crs"}.isdisjoint(ds_out.attrs) and mapname in ds_out:
|
|
227
|
-
ds_out.attrs["grid_mapping"] =
|
|
224
|
+
ds_out.attrs["grid_mapping"] = _grid_mapping_json(ds_out[mapname].attrs)
|
|
228
225
|
|
|
229
226
|
return ds_out.drop_vars(mapname, errors="ignore")
|
|
230
227
|
|
|
@@ -452,23 +449,106 @@ def parse_grid_mapping(grid_mapping: str | dict[str, Any]) -> CRS:
|
|
|
452
449
|
See also
|
|
453
450
|
--------
|
|
454
451
|
:func:`get_grid_crs`
|
|
452
|
+
|
|
453
|
+
Note
|
|
454
|
+
----
|
|
455
|
+
CF ``latitude_longitude`` and ``rotated_latitude_longitude`` mappings without ellipsoid parameters other than
|
|
456
|
+
``earth_radius`` (as NWP models on a sphere) are built directly on WGS84, which is much faster than
|
|
457
|
+
pyproj.CRS.from_cf(). The result is the same as ``sphere_as_wgs84(pyproj.CRS.from_cf(grid_mapping))``, see
|
|
458
|
+
:func:`~grid_process.helpers.sphere_as_wgs84`.
|
|
455
459
|
"""
|
|
456
460
|
|
|
461
|
+
# pyproj.CRS.from_cf() takes ~0.3 s for mappings with ellipsoid parameters (custom datum): cached on the JSON
|
|
457
462
|
if isinstance(grid_mapping, str):
|
|
458
|
-
|
|
463
|
+
return _parse_grid_mapping_str(grid_mapping)
|
|
464
|
+
try:
|
|
465
|
+
return _parse_grid_mapping_str(_grid_mapping_json(grid_mapping))
|
|
466
|
+
except TypeError: # not serializable: parse without cache
|
|
467
|
+
return _parse_grid_mapping_dict(grid_mapping)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _grid_mapping_json(grid_mapping: dict[str, Any]) -> str:
|
|
471
|
+
"""
|
|
472
|
+
JSON string of a grid mapping dictionary, with sorted keys and numpy scalars and arrays (e.g. attributes read
|
|
473
|
+
from a netCDF file such as "standard_parallel") converted to numbers and lists.
|
|
474
|
+
"""
|
|
475
|
+
|
|
476
|
+
return json.dumps(grid_mapping, sort_keys=True, default=lambda value: value.tolist())
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@lru_cache(maxsize=32)
|
|
480
|
+
def _parse_grid_mapping_str(grid_mapping: str) -> CRS:
|
|
481
|
+
"""
|
|
482
|
+
Cached parsing of a JSON "grid_mapping" attribute, see :func:`parse_grid_mapping`.
|
|
483
|
+
"""
|
|
484
|
+
|
|
485
|
+
return _parse_grid_mapping_dict(json.loads(grid_mapping))
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _parse_grid_mapping_dict(grid_mapping: dict[str, Any]) -> CRS:
|
|
489
|
+
"""
|
|
490
|
+
Parsing of a "grid_mapping" dictionary, see :func:`parse_grid_mapping`.
|
|
491
|
+
"""
|
|
492
|
+
|
|
459
493
|
if "epsg_code" in grid_mapping:
|
|
460
|
-
return CRS.from_string(grid_mapping["epsg_code"])
|
|
494
|
+
return CRS.from_string(grid_mapping["epsg_code"])
|
|
495
|
+
|
|
496
|
+
if _ELLIPSOID_KEYS.isdisjoint(grid_mapping):
|
|
497
|
+
mapping_name = grid_mapping.get("grid_mapping_name")
|
|
498
|
+
if mapping_name == "latitude_longitude":
|
|
499
|
+
return CRS.from_epsg(4326)
|
|
500
|
+
if mapping_name == "rotated_latitude_longitude":
|
|
501
|
+
return DerivedGeographicCRS(
|
|
502
|
+
base_crs=CRS.from_epsg(4326),
|
|
503
|
+
conversion=PoleRotationNetCDFCFConversion(
|
|
504
|
+
grid_north_pole_latitude=grid_mapping["grid_north_pole_latitude"],
|
|
505
|
+
grid_north_pole_longitude=grid_mapping["grid_north_pole_longitude"],
|
|
506
|
+
north_pole_grid_longitude=grid_mapping.get("north_pole_grid_longitude", 0.0),
|
|
507
|
+
),
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
return CRS.from_cf(grid_mapping)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
# CF grid mapping attributes defining the ellipsoid or datum (other than earth_radius for a sphere)
|
|
514
|
+
_ELLIPSOID_KEYS = frozenset(
|
|
515
|
+
(
|
|
516
|
+
"semi_major_axis",
|
|
517
|
+
"semi_minor_axis",
|
|
518
|
+
"inverse_flattening",
|
|
519
|
+
"reference_ellipsoid_name",
|
|
520
|
+
"horizontal_datum_name",
|
|
521
|
+
"geographic_crs_name",
|
|
522
|
+
"prime_meridian_name",
|
|
523
|
+
"longitude_of_prime_meridian",
|
|
524
|
+
"towgs84",
|
|
525
|
+
"crs_wkt",
|
|
526
|
+
"spatial_ref", # WKT written by GDAL
|
|
527
|
+
)
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def is_metric_crs(crs: CRS) -> bool:
|
|
532
|
+
"""
|
|
533
|
+
Whether ``crs`` is a projected CRS with all axes in meters (e.g. LV95, LV03 or UTM), i.e. in which euclidean
|
|
534
|
+
distances can be computed directly from the coordinates. The axis units are compared by their conversion factor
|
|
535
|
+
to the meter, so that units written with another name (e.g. "m" or "Meter" in a WKT without authority) are
|
|
536
|
+
recognized, while kilometers or feet are not.
|
|
537
|
+
"""
|
|
461
538
|
|
|
462
|
-
return
|
|
539
|
+
return bool(crs.is_projected) and all(axis.unit_conversion_factor == 1.0 for axis in crs.axis_info)
|
|
463
540
|
|
|
464
541
|
|
|
465
542
|
def get_coords_name_crs(
|
|
466
|
-
grid: xr.Dataset | xr.DataArray,
|
|
543
|
+
grid: xr.Dataset | xr.DataArray,
|
|
544
|
+
spatial_dims: tuple[str, ...] | None = None,
|
|
545
|
+
prefer_projected: bool = False,
|
|
467
546
|
) -> tuple[str, str, CRS]:
|
|
468
547
|
"""
|
|
469
548
|
Get correct coordinate names and CRS according to supported conventions. When
|
|
470
549
|
non-dimensional longitude and latitude coordinates arrays are present, these
|
|
471
|
-
are used instead of the native spatial dimension coordinates
|
|
550
|
+
are used instead of the native spatial dimension coordinates, unless ``prefer_projected``
|
|
551
|
+
is True and the native CRS is projected with axes in meters.
|
|
472
552
|
|
|
473
553
|
Parameters
|
|
474
554
|
----------
|
|
@@ -480,6 +560,10 @@ def get_coords_name_crs(
|
|
|
480
560
|
name of the ``grid`` spatial dimension(s). Has size one in case of
|
|
481
561
|
unstructured grid otherwise contains dimension names in the order (y, x).
|
|
482
562
|
If not provided, calls :func:`~grid_process.helpers.get_spatial_dims`
|
|
563
|
+
prefer_projected : bool, optional
|
|
564
|
+
If True, return the native spatial coordinates when their CRS is projected with axes in meters (e.g. LV95),
|
|
565
|
+
even if longitude and latitude coordinates are also present. Native coordinates in degrees (e.g. rotated
|
|
566
|
+
pole) still give the longitude and latitude coordinates when present. Default is False.
|
|
483
567
|
|
|
484
568
|
Returns
|
|
485
569
|
-------
|
|
@@ -491,6 +575,13 @@ def get_coords_name_crs(
|
|
|
491
575
|
pyproj representation of input grid coordinate reference system
|
|
492
576
|
"""
|
|
493
577
|
|
|
578
|
+
if prefer_projected:
|
|
579
|
+
if spatial_dims is None:
|
|
580
|
+
spatial_dims = get_spatial_dims(grid)
|
|
581
|
+
native = _get_native_metric_coords(grid, spatial_dims)
|
|
582
|
+
if native is not None:
|
|
583
|
+
return native
|
|
584
|
+
|
|
494
585
|
latlon = get_latlon_coords(grid)
|
|
495
586
|
if latlon is not None:
|
|
496
587
|
grid_y, grid_x = latlon
|
|
@@ -510,6 +601,35 @@ def get_coords_name_crs(
|
|
|
510
601
|
raise ValueError("Missing spatial coordinates")
|
|
511
602
|
|
|
512
603
|
|
|
604
|
+
def _get_native_metric_coords(
|
|
605
|
+
grid: xr.Dataset | xr.DataArray, spatial_dims: tuple[str, ...]
|
|
606
|
+
) -> tuple[str, str, CRS] | None:
|
|
607
|
+
"""
|
|
608
|
+
Names of the native (y, x) spatial coordinates and their CRS if this CRS is projected with axes in meters,
|
|
609
|
+
otherwise None. Used by :func:`get_coords_name_crs` with ``prefer_projected=True``.
|
|
610
|
+
"""
|
|
611
|
+
|
|
612
|
+
if len(spatial_dims) == 1:
|
|
613
|
+
try:
|
|
614
|
+
grid_y, grid_x = get_spatial_coords(grid)
|
|
615
|
+
except ValueError:
|
|
616
|
+
return None
|
|
617
|
+
else:
|
|
618
|
+
grid_y, grid_x = spatial_dims
|
|
619
|
+
if grid_y not in grid.coords or grid_x not in grid.coords:
|
|
620
|
+
return None
|
|
621
|
+
|
|
622
|
+
if (grid_y, grid_x) == get_latlon_coords(grid, nondim=False):
|
|
623
|
+
return None
|
|
624
|
+
|
|
625
|
+
try:
|
|
626
|
+
crs = get_grid_crs(grid)
|
|
627
|
+
except ValueError: # no CRS attribute: only longitude and latitude can be used
|
|
628
|
+
return None
|
|
629
|
+
|
|
630
|
+
return (grid_y, grid_x, crs) if is_metric_crs(crs) else None
|
|
631
|
+
|
|
632
|
+
|
|
513
633
|
def is_decreasing1d(coords: xr.DataArray) -> bool:
|
|
514
634
|
"""
|
|
515
635
|
Whether 1 dimensional ``coords`` are in decreasing order.
|