Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ Built-in Numba JIT and CUDA projection kernels bypass pyproj for per-pixel coord

| Name | Description | Source | NumPy xr.DataArray | Dask xr.DataArray | CuPy GPU xr.DataArray | Dask GPU xr.DataArray |
|:----------:|:------------|:------:|:----------------------:|:--------------------:|:-------------------:|:------:|
| [from_template](xrspatial/templates.py) | Empty study-area grid for a named region (CONUS, NYC, ...), a world city (London, Tokyo, ... in its UTM zone) or country code; `preserve='area'/'shape'` picks an EPSG projection by property | Custom | ✅ | 🔼 | 🔼 | 🔼 |
| [from_template](xrspatial/templates.py) | Empty study-area grid for a named region (CONUS, NYC, ...), a world city (London, Tokyo, ... in its UTM zone) or country code; `preserve='area'/'shape'` picks an EPSG projection by property; `list_templates()` lists every accepted name | Custom | ✅ | 🔼 | 🔼 | 🔼 |

-----------

Expand Down
4 changes: 4 additions & 0 deletions docs/source/reference/templates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ feeds straight into the rest of the library. Cities (national capitals, major
regional metros, and recognizable US secondary cities) come back as a metro
bounding box in their UTM zone.

Call :func:`~xrspatial.templates.list_templates` to discover every name
``from_template`` accepts (curated regions, world cities, and country codes).

From Template
=============
.. autosummary::
:toctree: _autosummary

xrspatial.templates.from_template
xrspatial.templates.list_templates
1 change: 1 addition & 0 deletions xrspatial/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
from xrspatial.surface_distance import surface_direction # noqa
from xrspatial.surface_distance import surface_distance # noqa
from xrspatial.templates import from_template # noqa
from xrspatial.templates import list_templates # noqa
from xrspatial.terrain import generate_terrain # noqa
from xrspatial.terrain_metrics import landforms # noqa
from xrspatial.terrain_metrics import LANDFORM_CLASSES # noqa
Expand Down
53 changes: 50 additions & 3 deletions xrspatial/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ def _resolve(name):
raise ValueError(
f"Unknown template {name!r}. Available named regions: {regions}. "
f"{len(_CITIES)} world cities are also supported (lowercase name, e.g. "
f"'london', 'tokyo'; see the templates reference). Countries must be an "
f"ISO-3166 / GADM alpha-3 code (e.g. 'USA', 'FRA', 'JPN'); "
f"{len(_COUNTRY_BBOXES)} are supported."
f"'london', 'tokyo'). Countries must be an ISO-3166 / GADM alpha-3 code "
f"(e.g. 'USA', 'FRA', 'JPN'); {len(_COUNTRY_BBOXES)} are supported. "
f"Call xrspatial.templates.list_templates() to list every accepted name."
)


Expand Down Expand Up @@ -199,6 +199,53 @@ def _cf_crs_attrs(crs):
if cf.get(k) is not None}


def list_templates(kind=None):
"""List the template names ``from_template`` accepts.

Every name in the result is a valid ``from_template`` argument: region and
city names are lowercase, country codes are uppercase ISO-3166 / GADM
alpha-3.

Parameters
----------
kind : {'regions', 'cities', 'countries'}, optional
Return just one group as a sorted list. When omitted (the default),
return a dict mapping each group to its sorted list of names.

Returns
-------
dict of str to list of str, or list of str
With ``kind=None``, ``{'regions': [...], 'cities': [...],
'countries': [...]}``. With ``kind`` set, the sorted list for that
group.

Examples
--------
.. sourcecode:: python

>>> from xrspatial.templates import list_templates
>>> names = list_templates()
>>> sorted(names)
['cities', 'countries', 'regions']
>>> 'conus' in names['regions']
True
>>> 'london' in list_templates('cities')
True
"""
groups = {
"regions": sorted(_REGIONS),
"cities": sorted(_CITIES),
"countries": sorted(_COUNTRY_BBOXES),
}
if kind is None:
return groups
if kind not in groups:
raise ValueError(
f"kind must be one of {tuple(groups)} or None, got {kind!r}."
)
return groups[kind]


def from_template(name, resolution=None, *, preserve=None, backend="numpy",
fill=np.nan, chunks="auto"):
"""Create an empty DataArray for a common study area.
Expand Down
39 changes: 38 additions & 1 deletion xrspatial/tests/test_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pytest
import xarray as xr

from xrspatial import from_template, slope
from xrspatial import from_template, list_templates, slope
from xrspatial._template_data import _CITIES, _CITY_DEFAULT_RESOLUTION, _COUNTRY_BBOXES, _REGIONS
from xrspatial.tests.general_checks import cuda_and_cupy_available, dask_array_available

Expand Down Expand Up @@ -134,6 +134,43 @@ def test_unknown_name_raises(bad):
from_template(bad)


def test_unknown_name_points_to_list_templates():
# the error tells the user how to discover valid names
with pytest.raises(ValueError, match="list_templates"):
from_template("does-not-exist")


def test_list_templates_grouped():
names = list_templates()
assert set(names) == {"regions", "cities", "countries"}
# each group lists exactly its registry keys, sorted
assert names["regions"] == sorted(_REGIONS)
assert names["cities"] == sorted(_CITIES)
assert names["countries"] == sorted(_COUNTRY_BBOXES)


@pytest.mark.parametrize(
"kind,registry",
[("regions", _REGIONS), ("cities", _CITIES), ("countries", _COUNTRY_BBOXES)],
)
def test_list_templates_kind_filter(kind, registry):
assert list_templates(kind) == sorted(registry)


def test_list_templates_bad_kind_raises():
with pytest.raises(ValueError, match="kind must be one of"):
list_templates("city")


def test_list_templates_names_resolve():
# every advertised name is a valid from_template argument; build one from
# each group to confirm the listed names map straight to a template
names = list_templates()
for kind in ("regions", "cities", "countries"):
agg = from_template(names[kind][0])
assert agg.dims == ("y", "x")


def test_nonpositive_resolution_raises():
with pytest.raises(ValueError, match="positive"):
from_template("conus", resolution=0)
Expand Down
Loading