Skip to content

Commit f7b6d6c

Browse files
seismanweiji14
andauthored
Wrap grdcut (#492)
Wrap the `grdcut` function. Ideally, `grdcut` should take a grid file or a `xarray.DataArray` as input, and store output as a grid file or return a `xarray.DataArray`. Currently supported features: - [X] input: grid file; output: grid file - [X] input: grid file; output: `xarray.DataArray` - [ ] input: `xarray.DataArray`; output: grid file - [ ] input: `xarray.DataArray`; output: `xarray.DataArray` Passing `xarray.DataArray` to `grdcut` is not implemented in GMT 6.0.0, only is only possible for GMT>=6.1.0. See GenericMappingTools/gmt#3532 for the feature request to the upstream GMT. Co-authored-by: Wei Ji <[email protected]>
1 parent 0afaa69 commit f7b6d6c

File tree

4 files changed

+185
-0
lines changed

4 files changed

+185
-0
lines changed

doc/api/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ Operations on grids:
7070
.. autosummary::
7171
:toctree: generated
7272

73+
grdcut
7374
grdinfo
7475
grdtrack
7576

pygmt/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from .sampling import grdtrack
2020
from .mathops import makecpt
2121
from .modules import config, info, grdinfo, which
22+
from .gridops import grdcut
2223
from . import datasets
2324

2425

pygmt/gridops.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""
2+
GMT modules for grid operations
3+
"""
4+
5+
import xarray as xr
6+
7+
8+
from .clib import Session
9+
from .helpers import (
10+
build_arg_string,
11+
fmt_docstring,
12+
kwargs_to_strings,
13+
GMTTempFile,
14+
use_alias,
15+
data_kind,
16+
dummy_context,
17+
)
18+
from .exceptions import GMTInvalidInput
19+
20+
21+
@fmt_docstring
22+
@use_alias(
23+
G="outgrid",
24+
R="region",
25+
J="projection",
26+
N="extend",
27+
S="circ_subregion",
28+
Z="z_subregion",
29+
)
30+
@kwargs_to_strings(R="sequence")
31+
def grdcut(grid, **kwargs):
32+
"""
33+
Extract subregion from a grid.
34+
35+
Produce a new *outgrid* file which is a subregion of *grid*. The
36+
subregion is specified with *region*; the specified range must not exceed
37+
the range of *grid* (but see *extend*). If in doubt, run
38+
:meth:`pygmt.grdinfo` to check range. Alternatively, define the subregion
39+
indirectly via a range check on the node values or via distances from a
40+
given point. Finally, you can give *projection* for oblique projections to
41+
determine the corresponding rectangular *region* setting that will give a
42+
grid that fully covers the oblique domain.
43+
44+
Full option list at :gmt-docs:`grdcut.html`
45+
46+
{aliases}
47+
48+
Parameters
49+
----------
50+
grid : str
51+
The name of the input grid file.
52+
outgrid : str or None
53+
The name of the output netCDF file with extension .nc to store the grid
54+
in.
55+
{J}
56+
{R}
57+
extend : bool or int or float
58+
Allow grid to be extended if new *region* exceeds existing boundaries.
59+
Give a value to initialize nodes outside current region.
60+
circ_subregion : str
61+
``'lon/lat/radius[unit][+n]'``.
62+
Specify an origin (*lon* and *lat*) and *radius*; append a distance
63+
*unit* and we determine the corresponding rectangular region so that
64+
all grid nodes on or inside the circle are contained in the subset.
65+
If **+n** is appended we set all nodes outside the circle to NaN.
66+
z_subregion : str
67+
``'[min/max][+n|N|r]'``.
68+
Determine a new rectangular region so that all nodes outside this
69+
region are also outside the given z-range [-inf/+inf]. To indicate no
70+
limit on *min* or *max* only, specify a hyphen (-). Normally, any NaNs
71+
encountered are simply skipped and not considered in the
72+
range-decision. Append **+n** to consider a NaN to be outside the given
73+
z-range. This means the new subset will be NaN-free. Alternatively,
74+
append **+r** to consider NaNs to be within the data range. In this
75+
case we stop shrinking the boundaries once a NaN is found [Default
76+
simply skips NaNs when making the range decision]. Finally, if your
77+
core subset grid is surrounded by rows and/or columns that are all
78+
NaNs, append **+N** to strip off such columns before (optionally)
79+
considering the range of the core subset for further reduction of the
80+
area.
81+
82+
Returns
83+
-------
84+
ret: xarray.DataArray or None
85+
Return type depends on whether the *outgrid* parameter is set:
86+
87+
- xarray.DataArray if *outgrid* is not set
88+
- None if *outgrid* is set (grid output will be stored in *outgrid*)
89+
"""
90+
kind = data_kind(grid)
91+
92+
with GMTTempFile(suffix=".nc") as tmpfile:
93+
with Session() as lib:
94+
if kind == "file":
95+
file_context = dummy_context(grid)
96+
elif kind == "grid":
97+
raise NotImplementedError(
98+
"xarray.DataArray is not supported as the input grid yet!"
99+
)
100+
# file_context = lib.virtualfile_from_grid(grid)
101+
# See https://github.com/GenericMappingTools/gmt/pull/3532
102+
# for a feature request.
103+
else:
104+
raise GMTInvalidInput("Unrecognized data type: {}".format(type(grid)))
105+
106+
with file_context as infile:
107+
if "G" not in kwargs.keys(): # if outgrid is unset, output to tempfile
108+
kwargs.update({"G": tmpfile.name})
109+
outgrid = kwargs["G"]
110+
arg_str = " ".join([infile, build_arg_string(kwargs)])
111+
lib.call_module("grdcut", arg_str)
112+
113+
if outgrid == tmpfile.name: # if user did not set outgrid, return DataArray
114+
with xr.open_dataarray(outgrid) as dataarray:
115+
result = dataarray.load()
116+
else:
117+
result = None # if user sets an outgrid, return None
118+
119+
return result

pygmt/tests/test_grdcut.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""
2+
Tests for grdcut
3+
"""
4+
import os
5+
import numpy as np
6+
import pytest
7+
import xarray as xr
8+
9+
from .. import grdcut, grdinfo
10+
from ..datasets import load_earth_relief
11+
from ..exceptions import GMTInvalidInput
12+
from ..helpers import GMTTempFile
13+
14+
15+
def test_grdcut_file_in_file_out():
16+
"grduct an input grid file, and output to a grid file"
17+
with GMTTempFile(suffix=".nc") as tmpfile:
18+
result = grdcut("@earth_relief_01d", outgrid=tmpfile.name, region="0/180/0/90")
19+
assert result is None # return value is None
20+
assert os.path.exists(path=tmpfile.name) # check that outgrid exists
21+
result = grdinfo(tmpfile.name, C=True)
22+
assert result == "0 180 0 90 -8592.5 5559 1 1 181 91\n"
23+
24+
25+
def test_grdcut_file_in_dataarray_out():
26+
"grdcut an input grid file, and output as DataArray"
27+
outgrid = grdcut("@earth_relief_01d", region="0/180/0/90")
28+
assert isinstance(outgrid, xr.DataArray)
29+
# check information of the output grid
30+
# the '@earth_relief_01d' is in pixel registration, so the grid range is
31+
# not exactly 0/180/0/90
32+
assert outgrid.coords["lat"].data.min() == 0.0
33+
assert outgrid.coords["lat"].data.max() == 90.0
34+
assert outgrid.coords["lon"].data.min() == 0.0
35+
assert outgrid.coords["lon"].data.max() == 180.0
36+
assert outgrid.data.min() == -8592.5
37+
assert outgrid.data.max() == 5559
38+
assert outgrid.sizes["lat"] == 91
39+
assert outgrid.sizes["lon"] == 181
40+
41+
42+
def test_grdcut_dataarray_in_file_out():
43+
"grdcut an input DataArray, and output to a grid file"
44+
# Not supported yet.
45+
# See https://github.com/GenericMappingTools/gmt/pull/3532
46+
47+
48+
def test_grdcut_dataarray_in_dataarray_out():
49+
"grdcut an input DataArray, and output to a grid file"
50+
# Not supported yet.
51+
# See https://github.com/GenericMappingTools/gmt/pull/3532
52+
53+
54+
def test_grdcut_dataarray_in_fail():
55+
"Make sure that grdcut fails correctly if DataArray is the input grid"
56+
with pytest.raises(NotImplementedError):
57+
grid = load_earth_relief()
58+
grdcut(grid, region="0/180/0/90")
59+
60+
61+
def test_grdcut_fails():
62+
"Check that grdcut fails correctly"
63+
with pytest.raises(GMTInvalidInput):
64+
grdcut(np.arange(10).reshape((5, 2)))

0 commit comments

Comments
 (0)