Skip to content

Commit a11fc15

Browse files
authored
pygmt.grdfill: Add new parameter 'inquire' to inquire the bounds of holes (#3880)
1 parent c6aeadf commit a11fc15

File tree

2 files changed

+124
-47
lines changed

2 files changed

+124
-47
lines changed

pygmt/src/grdfill.py

Lines changed: 105 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import warnings
66

7+
import numpy as np
78
import xarray as xr
89
from pygmt.clib import Session
910
from pygmt.exceptions import GMTInvalidInput
@@ -18,6 +19,57 @@
1819
__doctest_skip__ = ["grdfill"]
1920

2021

22+
def _validate_params(
23+
constantfill=None,
24+
gridfill=None,
25+
neighborfill=None,
26+
splinefill=None,
27+
inquire=False,
28+
mode=None,
29+
):
30+
"""
31+
Validate the fill/inquire parameters.
32+
33+
>>> _validate_params(constantfill=20.0)
34+
>>> _validate_params(inquire=True)
35+
>>> _validate_params(mode="c20.0")
36+
>>> _validate_params(constantfill=20.0, gridfill="bggrid.nc")
37+
Traceback (most recent call last):
38+
...
39+
pygmt.exceptions.GMTInvalidInput: Parameters ... are mutually exclusive.
40+
>>> _validate_params(constantfill=20.0, inquire=True)
41+
Traceback (most recent call last):
42+
...
43+
pygmt.exceptions.GMTInvalidInput: Parameters ... are mutually exclusive.
44+
>>> _validate_params()
45+
Traceback (most recent call last):
46+
...
47+
pygmt.exceptions.GMTInvalidInput: Need to specify parameter ...
48+
"""
49+
_fill_params = "'constantfill'/'gridfill'/'neighborfill'/'splinefill'"
50+
# The deprecated 'mode' parameter is given.
51+
if mode is not None:
52+
msg = (
53+
"The 'mode' parameter is deprecated since v0.15.0 and will be removed in "
54+
f"v0.19.0. Use {_fill_params} instead."
55+
)
56+
warnings.warn(msg, FutureWarning, stacklevel=2)
57+
58+
n_given = sum(
59+
param is not None and param is not False
60+
for param in [constantfill, gridfill, neighborfill, splinefill, inquire, mode]
61+
)
62+
if n_given > 1: # More than one mutually exclusive parameter is given.
63+
msg = f"Parameters {_fill_params}/'inquire'/'mode' are mutually exclusive."
64+
raise GMTInvalidInput(msg)
65+
if n_given == 0: # No parameters are given.
66+
msg = (
67+
f"Need to specify parameter {_fill_params} for filling holes or "
68+
"'inquire' for inquiring the bounds of each hole."
69+
)
70+
raise GMTInvalidInput(msg)
71+
72+
2173
def _parse_fill_mode(
2274
constantfill=None, gridfill=None, neighborfill=None, splinefill=None
2375
) -> str | None:
@@ -40,19 +92,7 @@ def _parse_fill_mode(
4092
's0.5'
4193
>>> _parse_fill_mode(splinefill=True)
4294
's'
43-
>>> _parse_fill_mode(constantfill=20, gridfill="bggrid.nc")
44-
Traceback (most recent call last):
45-
...
46-
pygmt.exceptions.GMTInvalidInput: The ... parameters are mutually exclusive.
4795
"""
48-
fill_params = [constantfill, gridfill, neighborfill, splinefill]
49-
if sum(param is not None for param in fill_params) > 1:
50-
msg = (
51-
"The 'constantfill', 'gridfill', 'neighborfill', and 'splinefill' "
52-
"parameters are mutually exclusive."
53-
)
54-
raise GMTInvalidInput(msg)
55-
5696
if constantfill is not None:
5797
return f"c{constantfill}"
5898
if gridfill is not None:
@@ -66,8 +106,9 @@ def _parse_fill_mode(
66106

67107
@fmt_docstring
68108
# TODO(PyGMT>=0.19.0): Remove the deprecated 'no_data' parameter.
109+
# TODO(PyGMT>=0.19.0): Remove the deprecated 'mode' parameter.
69110
@deprecate_parameter("no_data", "hole", "v0.15.0", remove_version="v0.19.0")
70-
@use_alias(A="mode", N="hole", R="region", V="verbose", f="coltypes")
111+
@use_alias(N="hole", R="region", V="verbose", f="coltypes")
71112
@kwargs_to_strings(R="sequence")
72113
def grdfill(
73114
grid: str | xr.DataArray,
@@ -76,8 +117,10 @@ def grdfill(
76117
gridfill: str | xr.DataArray | None = None,
77118
neighborfill: float | bool | None = None,
78119
splinefill: float | bool | None = None,
120+
inquire: bool = False,
121+
mode: str | None = None,
79122
**kwargs,
80-
) -> xr.DataArray | None:
123+
) -> xr.DataArray | np.ndarray | None:
81124
r"""
82125
Interpolate across holes in a grid.
83126
@@ -111,7 +154,11 @@ def grdfill(
111154
hole : float
112155
Set the node value used to identify a point as a member of a hole [Default is
113156
NaN].
114-
mode : str
157+
inquire
158+
Output the bounds of each hole. The bounds are returned as a 2-D numpy array in
159+
the form of (west, east, south, north). No grid fill takes place and ``outgrid``
160+
is ignored.
161+
mode
115162
Specify the hole-filling algorithm to use. Choose from **c** for constant fill
116163
and append the constant value, **n** for nearest neighbor (and optionally append
117164
a search radius in pixels [default radius is :math:`r^2 = \sqrt{{ X^2 + Y^2 }}`,
@@ -128,50 +175,62 @@ def grdfill(
128175
Returns
129176
-------
130177
ret
131-
Return type depends on whether the ``outgrid`` parameter is set:
178+
If ``inquire`` is ``True``, return the bounds of each hole as a 2-D numpy array.
179+
Otherwise, the return type depends on whether the ``outgrid`` parameter is set:
132180
133181
- :class:`xarray.DataArray` if ``outgrid`` is not set
134182
- ``None`` if ``outgrid`` is set (grid output will be stored in the file set by
135183
``outgrid``)
136184
137185
Example
138186
-------
187+
Fill holes in a bathymetric grid with a constant value of 20.
139188
>>> import pygmt
140189
>>> # Load a bathymetric grid with missing data
141190
>>> earth_relief_holes = pygmt.datasets.load_sample_data(name="earth_relief_holes")
142191
>>> # Fill the holes with a constant value of 20
143192
>>> filled_grid = pygmt.grdfill(grid=earth_relief_holes, constantfill=20)
193+
194+
Inquire the bounds of each hole.
195+
>>> pygmt.grdfill(grid=earth_relief_holes, inquire=True)
196+
array([[1.83333333, 6.16666667, 3.83333333, 8.16666667],
197+
[6.16666667, 7.83333333, 0.5 , 2.5 ]])
144198
"""
145-
# TODO(PyGMT>=0.19.0): Remove the deprecated 'mode' parameter.
146-
if kwargs.get("A") is not None: # The deprecated 'mode' parameter is given.
147-
warnings.warn(
148-
"The 'mode' parameter is deprecated since v0.15.0 and will be removed in "
149-
"v0.19.0. Use 'constantfill'/'gridfill'/'neighborfill'/'splinefill' "
150-
"instead.",
151-
FutureWarning,
152-
stacklevel=1,
153-
)
154-
else:
155-
# Determine the -A option from the fill parameters.
156-
kwargs["A"] = _parse_fill_mode(constantfill, gridfill, neighborfill, splinefill)
199+
# Validate the fill/inquire parameters.
200+
_validate_params(constantfill, gridfill, neighborfill, splinefill, inquire, mode)
157201

158-
if kwargs.get("A") is None and kwargs.get("L") is None:
159-
msg = "At least parameter 'mode' or 'L' must be specified."
160-
raise GMTInvalidInput(msg)
202+
# Parse the fill parameters and return the appropriate string for the -A option.
203+
kwargs["A"] = (
204+
_parse_fill_mode(constantfill, gridfill, neighborfill, splinefill)
205+
if mode is None
206+
else mode
207+
)
161208

162209
with Session() as lib:
163-
with (
164-
lib.virtualfile_in(check_kind="raster", data=grid) as vingrd,
165-
lib.virtualfile_in(
166-
check_kind="raster", data=gridfill, required_data=False
167-
) as vbggrd,
168-
lib.virtualfile_out(kind="grid", fname=outgrid) as voutgrd,
169-
):
170-
if gridfill is not None:
171-
# Fill by a grid. Append the actual or virtual grid file name.
172-
kwargs["A"] = f"g{vbggrd}"
173-
kwargs["G"] = voutgrd
174-
lib.call_module(
175-
module="grdfill", args=build_arg_list(kwargs, infile=vingrd)
176-
)
177-
return lib.virtualfile_to_raster(vfname=voutgrd, outgrid=outgrid)
210+
with lib.virtualfile_in(check_kind="raster", data=grid) as vingrd:
211+
if inquire: # Inquire mode.
212+
kwargs["L"] = True
213+
with lib.virtualfile_out(kind="dataset") as vouttbl:
214+
lib.call_module(
215+
module="grdfill",
216+
args=build_arg_list(kwargs, infile=vingrd, outfile=vouttbl),
217+
)
218+
return lib.virtualfile_to_dataset(
219+
vfname=vouttbl, output_type="numpy"
220+
)
221+
222+
# Fill mode.
223+
with (
224+
lib.virtualfile_in(
225+
check_kind="raster", data=gridfill, required_data=False
226+
) as vbggrd,
227+
lib.virtualfile_out(kind="grid", fname=outgrid) as voutgrd,
228+
):
229+
if gridfill is not None:
230+
# Fill by a grid. Append the actual or virtual grid file name.
231+
kwargs["A"] = f"g{vbggrd}"
232+
kwargs["G"] = voutgrd
233+
lib.call_module(
234+
module="grdfill", args=build_arg_list(kwargs, infile=vingrd)
235+
)
236+
return lib.virtualfile_to_raster(vfname=voutgrd, outgrid=outgrid)

pygmt/tests/test_grdfill.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,32 @@ def test_grdfill_gridfill_dataarray(grid):
114114
npt.assert_array_equal(result[3:6, 3:5], bggrid[3:6, 3:5])
115115

116116

117+
def test_grdfill_inquire(grid):
118+
"""
119+
Test grdfill with inquire mode.
120+
"""
121+
bounds = grdfill(grid=grid, inquire=True)
122+
assert isinstance(bounds, np.ndarray)
123+
assert bounds.shape == (1, 4)
124+
npt.assert_allclose(bounds, [[-52.0, -50.0, -21.0, -18.0]])
125+
126+
117127
def test_grdfill_required_args(grid):
118128
"""
119-
Test that grdfill fails without arguments for `mode` and `L`.
129+
Test that grdfill fails without filling parameters or 'inquire'.
120130
"""
121131
with pytest.raises(GMTInvalidInput):
122132
grdfill(grid=grid)
123133

124134

135+
def test_grdfill_inquire_and_fill(grid):
136+
"""
137+
Test that grdfill fails if both inquire and fill parameters are given.
138+
"""
139+
with pytest.raises(GMTInvalidInput):
140+
grdfill(grid=grid, inquire=True, constantfill=20)
141+
142+
125143
# TODO(PyGMT>=0.19.0): Remove this test.
126144
def test_grdfill_deprecated_mode(grid, expected_grid):
127145
"""

0 commit comments

Comments
 (0)