Skip to content

Commit 32c4d4a

Browse files
seismanweiji14
andauthored
Let load_earth_relief() support all resolutions and optional subregion
Redesign the load_earth_relief function to support all resolutions and also allow subregion for high-resolution grids. Changes in this PR: - For grid>05m, calls the `which` function. - For grids<=05m, calls the `grdcut` function. - For grids<=05m, users have to pass the region argument to select a subregion. - Remove the unused function `_shape_from_resolution` - Use two lists `tiled_resolutions` and `non_tiled_resolutions`, instead of the old `_is_valid_resolution` function Known issues: - grid>05m don't allow a subregion - grid<=05m don't allow slice operation Co-authored-by: Wei Ji <[email protected]>
1 parent 3b83889 commit 32c4d4a

File tree

3 files changed

+96
-99
lines changed

3 files changed

+96
-99
lines changed

.github/workflows/cache_data.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ jobs:
2929
- name: Download remote data
3030
shell: bash -l {0}
3131
run: |
32-
gmt which -Ga @earth_relief_10m_p @earth_relief_10m_g @earth_relief_30m_p @earth_relief_30m_g @earth_relief_01d_p @earth_relief_01d_g
33-
gmt which -Ga @ridge.txt @Table_5_11.txt @test.dat.nc @tut_bathy.nc @tut_quakes.ngdc @tut_ship.xyz @usgs_quakes_22.txt
32+
gmt which -Ga @earth_relief_10m_p @earth_relief_10m_g \
33+
@earth_relief_30m_p @earth_relief_30m_g \
34+
@earth_relief_01d_p @earth_relief_01d_g \
35+
@earth_relief_05m_g
36+
gmt which -Ga @ridge.txt @Table_5_11.txt @test.dat.nc \
37+
@tut_bathy.nc @tut_quakes.ngdc @tut_ship.xyz \
38+
@usgs_quakes_22.txt
3439
3540
# Upload the downloaded files as artifacts to Github
3641
- name: Upload artifacts to Github

pygmt/datasets/earth_relief.py

Lines changed: 61 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,40 @@
11
"""
2-
Functions to download the Earth relief datasets from the GMT data server.
3-
The grids are available in various resolutions.
2+
Function to download the Earth relief datasets from the GMT data server,
3+
and load as DataArray. The grids are available in various resolutions.
44
"""
55
import xarray as xr
66

7-
from .. import which
7+
from .. import grdcut, which
88
from ..exceptions import GMTInvalidInput
9+
from ..helpers import kwargs_to_strings
910

1011

11-
def load_earth_relief(resolution="01d", registration=None):
12+
@kwargs_to_strings(region="sequence")
13+
def load_earth_relief(resolution="01d", region=None, registration=None):
1214
"""
1315
Load Earth relief grids (topography and bathymetry) in various resolutions.
1416
15-
The grids are downloaded to a user data directory (usually ``~/.gmt/``) the
16-
first time you invoke this function. Afterwards, it will load the data from
17-
the cache. So you'll need an internet connection the first time around.
17+
The grids are downloaded to a user data directory
18+
(usually ``~/.gmt/server/earth/earth_relief/``) the first time you invoke
19+
this function. Afterwards, it will load the grid from the data directory.
20+
So you'll need an internet connection the first time around.
1821
1922
These grids can also be accessed by passing in the file name
20-
``'@earth_relief_XXm'`` or ``'@earth_relief_XXs'`` to any grid
21-
plotting/processing function.
23+
``'@earth_relief_rru[_reg]'`` to any grid plotting/processing function.
24+
Refer to :gmt-docs:`datasets/remote-data.html` for more details.
2225
2326
Parameters
2427
----------
2528
resolution : str
2629
The grid resolution. The suffix ``d``, ``m`` and ``s`` stand for
2730
arc-degree, arc-minute and arc-second. It can be ``'01d'``, ``'30m'``,
2831
``'20m'``, ``'15m'``, ``'10m'``, ``'06m'``, ``'05m'``, ``'04m'``,
29-
``'03m'``, ``'02m'``, ``'01m'``, ``'30s'`` or ``'15s'``.
32+
``'03m'``, ``'02m'``, ``'01m'``, ``'30s'``, ``'15s'``, ``'03s'``,
33+
or ``'01s'``.
34+
35+
region : str or list
36+
The subregion of the grid to load. Required for Earth relief grids with
37+
resolutions <= 05m.
3038
3139
registration : str
3240
Grid registration type. Either ``pixel`` for pixel registration or
@@ -40,8 +48,29 @@ def load_earth_relief(resolution="01d", registration=None):
4048
The Earth relief grid. Coordinates are latitude and longitude in
4149
degrees. Relief is in meters.
4250
51+
Notes
52+
-----
53+
The DataArray doesn's support slice operation, for Earth relief data with
54+
resolutions higher than "05m", which are stored as smaller tiles.
55+
56+
Examples
57+
--------
58+
59+
>>> # load the default grid (pixel-registered 01d grid)
60+
>>> grid = load_earth_relief()
61+
>>> # load the 30m grid with "gridline" registration
62+
>>> grid = load_earth_relief("30m", registration="gridline")
63+
>>> # load high-resolution grid for a specific region
64+
>>> grid = load_earth_relief(
65+
... "05m", region=[120, 160, 30, 60], registration="gridline"
66+
... )
67+
4368
"""
44-
_is_valid_resolution(resolution)
69+
70+
# earth relief data stored as single grids for low resolutions
71+
non_tiled_resolutions = ["01d", "30m", "20m", "15m", "10m", "06m"]
72+
# earth relief data stored as tiles for high resolutions
73+
tiled_resolutions = ["05m", "04m", "03m", "02m", "01m", "30s", "15s", "03s", "01s"]
4574

4675
if registration in ("pixel", "gridline", None):
4776
# If None, let GMT decide on Pixel/Gridline type
@@ -54,8 +83,27 @@ def load_earth_relief(resolution="01d", registration=None):
5483
"gridline-registered grid is available."
5584
)
5685

57-
fname = which(f"@earth_relief_{resolution}{reg}", download="a")
58-
grid = xr.open_dataarray(fname)
86+
# different ways to load tiled and non-tiled earth relief data
87+
if resolution in non_tiled_resolutions:
88+
if region is not None:
89+
raise NotImplementedError(
90+
f"'region' is not supported for Earth relief resolution '{resolution}'"
91+
)
92+
fname = which(f"@earth_relief_{resolution}{reg}", download="a")
93+
with xr.open_dataarray(fname) as dataarray:
94+
grid = dataarray.load()
95+
_ = grid.gmt # load GMTDataArray accessor information
96+
elif resolution in tiled_resolutions:
97+
# Titled grid can't be sliced.
98+
# See https://github.com/GenericMappingTools/pygmt/issues/524
99+
if region is None:
100+
raise GMTInvalidInput(
101+
f"'region' is required for Earth relief resolution '{resolution}'"
102+
)
103+
grid = grdcut(f"@earth_relief_{resolution}{reg}", region=region)
104+
else:
105+
raise GMTInvalidInput(f'Invalid Earth relief resolution "{resolution}"')
106+
59107
# Add some metadata to the grid
60108
grid.name = "elevation"
61109
grid.attrs["long_name"] = "elevation relative to the geoid"
@@ -69,86 +117,3 @@ def load_earth_relief(resolution="01d", registration=None):
69117
for coord in grid.coords:
70118
grid[coord].attrs.pop("actual_range")
71119
return grid
72-
73-
74-
def _is_valid_resolution(resolution):
75-
"""
76-
Check if a resolution is valid for the global Earth relief grid.
77-
78-
Parameters
79-
----------
80-
resolution : str
81-
Same as the input for load_earth_relief
82-
83-
Raises
84-
------
85-
GMTInvalidInput
86-
If given resolution is not valid.
87-
88-
Examples
89-
--------
90-
91-
>>> _is_valid_resolution("01d")
92-
>>> _is_valid_resolution("60m")
93-
>>> _is_valid_resolution("5m")
94-
Traceback (most recent call last):
95-
...
96-
pygmt.exceptions.GMTInvalidInput: Invalid Earth relief resolution '5m'.
97-
>>> _is_valid_resolution("15s")
98-
>>> _is_valid_resolution("01s")
99-
Traceback (most recent call last):
100-
...
101-
pygmt.exceptions.GMTInvalidInput: Invalid Earth relief resolution '01s'.
102-
103-
"""
104-
valid_resolutions = ["01d"]
105-
valid_resolutions.extend(
106-
[f"{res:02d}m" for res in [60, 30, 20, 15, 10, 6, 5, 4, 3, 2, 1]]
107-
)
108-
valid_resolutions.extend([f"{res:02d}s" for res in [30, 15]])
109-
if resolution not in valid_resolutions:
110-
raise GMTInvalidInput(
111-
"Invalid Earth relief resolution '{}'.".format(resolution)
112-
)
113-
114-
115-
def _shape_from_resolution(resolution):
116-
"""
117-
Calculate the shape of the global Earth relief grid given a resolution.
118-
119-
Parameters
120-
----------
121-
resolution : str
122-
Same as the input for load_earth_relief
123-
124-
Returns
125-
-------
126-
shape : (nlat, nlon)
127-
The calculated shape.
128-
129-
Examples
130-
--------
131-
132-
>>> _shape_from_resolution('60m')
133-
(181, 361)
134-
>>> _shape_from_resolution('30m')
135-
(361, 721)
136-
>>> _shape_from_resolution('10m')
137-
(1081, 2161)
138-
>>> _shape_from_resolution('30s')
139-
(21601, 43201)
140-
>>> _shape_from_resolution('15s')
141-
(43201, 86401)
142-
143-
"""
144-
_is_valid_resolution(resolution)
145-
unit = resolution[2]
146-
if unit == "d":
147-
seconds = int(resolution[:2]) * 60 * 60
148-
elif unit == "m":
149-
seconds = int(resolution[:2]) * 60
150-
elif unit == "s":
151-
seconds = int(resolution[:2])
152-
nlat = 180 * 60 * 60 // seconds + 1
153-
nlon = 360 * 60 * 60 // seconds + 1
154-
return (nlat, nlon)

pygmt/tests/test_datasets.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def test_usgs_quakes():
6060

6161
def test_earth_relief_fails():
6262
"Make sure earth relief fails for invalid resolutions"
63-
resolutions = "1m 1d bla 60d 01s 03s 001m 03".split()
63+
resolutions = "1m 1d bla 60d 001m 03".split()
6464
resolutions.append(60)
6565
for resolution in resolutions:
6666
with pytest.raises(GMTInvalidInput):
@@ -78,6 +78,12 @@ def test_earth_relief_01d():
7878
npt.assert_allclose(data.max(), 5559.0)
7979

8080

81+
def test_earth_relief_01d_with_region():
82+
"Test loading low-resolution earth relief with 'region'"
83+
with pytest.raises(NotImplementedError):
84+
load_earth_relief("01d", region=[0, 180, 0, 90])
85+
86+
8187
def test_earth_relief_30m():
8288
"Test some properties of the earth relief 30m data"
8389
data = load_earth_relief(resolution="30m", registration="gridline")
@@ -88,6 +94,27 @@ def test_earth_relief_30m():
8894
npt.assert_allclose(data.max(), 5887.5)
8995

9096

97+
def test_earth_relief_05m_with_region():
98+
"Test loading a subregion of high-resolution earth relief grid"
99+
data = load_earth_relief(
100+
resolution="05m", region=[120, 160, 30, 60], registration="gridline"
101+
)
102+
assert data.coords["lat"].data.min() == 30.0
103+
assert data.coords["lat"].data.max() == 60.0
104+
assert data.coords["lon"].data.min() == 120.0
105+
assert data.coords["lon"].data.max() == 160.0
106+
assert data.data.min() == -9633.0
107+
assert data.data.max() == 2532.0
108+
assert data.sizes["lat"] == 361
109+
assert data.sizes["lon"] == 481
110+
111+
112+
def test_earth_relief_05m_without_region():
113+
"Test loading high-resolution earth relief without passing 'region'"
114+
with pytest.raises(GMTInvalidInput):
115+
load_earth_relief("05m")
116+
117+
91118
def test_earth_relief_incorrect_registration():
92119
"Test loading earth relief with incorrect registration type"
93120
with pytest.raises(GMTInvalidInput):

0 commit comments

Comments
 (0)