From b3671a642dccbd619811ee94f6615a5ef5c493fc Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 28 Mar 2025 00:39:31 +0800 Subject: [PATCH 1/6] pygmt.grdfill: Add parameter 'inquire' for inquiring bounds of holes --- pygmt/src/grdfill.py | 63 +++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index 0162e152569..8e154c21685 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -4,6 +4,7 @@ import warnings +import numpy as np import xarray as xr from pygmt.clib import Session from pygmt.exceptions import GMTInvalidInput @@ -76,8 +77,9 @@ def grdfill( gridfill: str | xr.DataArray | None = None, neighborfill: float | bool | None = None, splinefill: float | bool | None = None, + inquire: bool = False, **kwargs, -) -> xr.DataArray | None: +) -> xr.DataArray | np.ndarray | None: r""" Interpolate across holes in a grid. @@ -111,6 +113,10 @@ def grdfill( hole : float Set the node value used to identify a point as a member of a hole [Default is NaN]. + inquire + Output the bounds of each hole. The bounds are returned as a 2-D numpy array in + the form of (west, east, south, north). No grid fill takes place and ``outgrid`` + is ignored. mode : str Specify the hole-filling algorithm to use. Choose from **c** for constant fill and append the constant value, **n** for nearest neighbor (and optionally append @@ -128,7 +134,8 @@ def grdfill( Returns ------- ret - Return type depends on whether the ``outgrid`` parameter is set: + If ``inquire`` is ``True``, return the bounds of each hole as a 2-D numpy array. + Otherwise, return type depends on whether the ``outgrid`` parameter is set: - :class:`xarray.DataArray` if ``outgrid`` is not set - ``None`` if ``outgrid`` is set (grid output will be stored in the file set by @@ -136,11 +143,17 @@ def grdfill( Example ------- + Fill holes in a bathymetric grid with a constant value of 20. >>> import pygmt >>> # Load a bathymetric grid with missing data >>> earth_relief_holes = pygmt.datasets.load_sample_data(name="earth_relief_holes") >>> # Fill the holes with a constant value of 20 >>> filled_grid = pygmt.grdfill(grid=earth_relief_holes, constantfill=20) + + Inquire the bounds of each hole. + >>> pygmt.grdfill(grid=earth_relief_holes, inquire=True) + array([[1.83333333, 6.16666667, 3.83333333, 8.16666667], + [6.16666667, 7.83333333, 0.5 , 2.5 ]]) """ # TODO(PyGMT>=0.19.0): Remove the deprecated 'mode' parameter. if kwargs.get("A") is not None: # The deprecated 'mode' parameter is given. @@ -155,23 +168,35 @@ def grdfill( # Determine the -A option from the fill parameters. kwargs["A"] = _parse_fill_mode(constantfill, gridfill, neighborfill, splinefill) - if kwargs.get("A") is None and kwargs.get("L") is None: - msg = "At least parameter 'mode' or 'L' must be specified." + if kwargs.get("A") is None and inquire is False: + msg = "At least parameter 'mode' or 'inquire' must be specified." raise GMTInvalidInput(msg) with Session() as lib: - with ( - lib.virtualfile_in(check_kind="raster", data=grid) as vingrd, - lib.virtualfile_in( - check_kind="raster", data=gridfill, required_data=False - ) as vbggrd, - lib.virtualfile_out(kind="grid", fname=outgrid) as voutgrd, - ): - if gridfill is not None: - # Fill by a grid. Append the actual or virtual grid file name. - kwargs["A"] = f"g{vbggrd}" - kwargs["G"] = voutgrd - lib.call_module( - module="grdfill", args=build_arg_list(kwargs, infile=vingrd) - ) - return lib.virtualfile_to_raster(vfname=voutgrd, outgrid=outgrid) + with lib.virtualfile_in(check_kind="raster", data=grid) as vingrd: + if inquire: # Inquire mode. + kwargs["L"] = True + with lib.virtualfile_out(kind="dataset") as vouttbl: + lib.call_module( + module="grdfill", + args=build_arg_list(kwargs, infile=vingrd, outfile=vouttbl), + ) + return lib.virtualfile_to_dataset( + vfname=vouttbl, output_type="numpy" + ) + + # Fill mode. + with ( + lib.virtualfile_in( + check_kind="raster", data=gridfill, required_data=False + ) as vbggrd, + lib.virtualfile_out(kind="grid", fname=outgrid) as voutgrd, + ): + if gridfill is not None: + # Fill by a grid. Append the actual or virtual grid file name. + kwargs["A"] = f"g{vbggrd}" + kwargs["G"] = voutgrd + lib.call_module( + module="grdfill", args=build_arg_list(kwargs, infile=vingrd) + ) + return lib.virtualfile_to_raster(vfname=voutgrd, outgrid=outgrid) From 385470dd2b10be59870256efd6c77ecca3c4e114 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 28 Mar 2025 00:41:59 +0800 Subject: [PATCH 2/6] Add tests for the inquire parameter --- pygmt/tests/test_grdfill.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pygmt/tests/test_grdfill.py b/pygmt/tests/test_grdfill.py index 8763a482383..e7b6faadaa2 100644 --- a/pygmt/tests/test_grdfill.py +++ b/pygmt/tests/test_grdfill.py @@ -114,14 +114,32 @@ def test_grdfill_gridfill_dataarray(grid): npt.assert_array_equal(result[3:6, 3:5], bggrid[3:6, 3:5]) +def test_grdfill_inquire(grid): + """ + Test grdfill with inquire mode. + """ + bounds = grdfill(grid=grid, inquire=True) + assert isinstance(bounds, np.ndarray) + assert bounds.shape == (1, 4) + npt.assert_allclose(bounds, [[-52.0, -50.0, -21.0, -18.0]]) + + def test_grdfill_required_args(grid): """ - Test that grdfill fails without arguments for `mode` and `L`. + Test that grdfill fails without filling parameters or 'inquire'. """ with pytest.raises(GMTInvalidInput): grdfill(grid=grid) +def test_grdfill_inquire_and_fill(grid): + """ + Test that grdfill fails if both inquire and fill parameters are given. + """ + with pytest.raises(GMTInvalidInput): + grdfill(grid=grid, inquire=True, constantfill=20) + + # TODO(PyGMT>=0.19.0): Remove this test. def test_grdfill_deprecated_mode(grid, expected_grid): """ From cc13f005cd87c40e7fbeb8f44a7329cdb44b50a8 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 28 Mar 2025 00:49:47 +0800 Subject: [PATCH 3/6] Remove the deprecated 'mode' parameter from aliases --- pygmt/src/grdfill.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index 8e154c21685..f21fbf05cde 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -67,8 +67,9 @@ def _parse_fill_mode( @fmt_docstring # TODO(PyGMT>=0.19.0): Remove the deprecated 'no_data' parameter. +# TODO(PyGMT>=0.19.0): Remove the deprecated 'mode' parameter. @deprecate_parameter("no_data", "hole", "v0.15.0", remove_version="v0.19.0") -@use_alias(A="mode", N="hole", R="region", V="verbose", f="coltypes") +@use_alias(N="hole", R="region", V="verbose", f="coltypes") @kwargs_to_strings(R="sequence") def grdfill( grid: str | xr.DataArray, @@ -78,6 +79,7 @@ def grdfill( neighborfill: float | bool | None = None, splinefill: float | bool | None = None, inquire: bool = False, + mode: str | None = None, **kwargs, ) -> xr.DataArray | np.ndarray | None: r""" @@ -117,7 +119,7 @@ def grdfill( Output the bounds of each hole. The bounds are returned as a 2-D numpy array in the form of (west, east, south, north). No grid fill takes place and ``outgrid`` is ignored. - mode : str + mode Specify the hole-filling algorithm to use. Choose from **c** for constant fill and append the constant value, **n** for nearest neighbor (and optionally append a search radius in pixels [default radius is :math:`r^2 = \sqrt{{ X^2 + Y^2 }}`, @@ -155,8 +157,7 @@ def grdfill( array([[1.83333333, 6.16666667, 3.83333333, 8.16666667], [6.16666667, 7.83333333, 0.5 , 2.5 ]]) """ - # TODO(PyGMT>=0.19.0): Remove the deprecated 'mode' parameter. - if kwargs.get("A") is not None: # The deprecated 'mode' parameter is given. + if mode is not None: # The deprecated 'mode' parameter is given. warnings.warn( "The 'mode' parameter is deprecated since v0.15.0 and will be removed in " "v0.19.0. Use 'constantfill'/'gridfill'/'neighborfill'/'splinefill' " @@ -164,8 +165,8 @@ def grdfill( FutureWarning, stacklevel=1, ) + kwargs["A"] = mode else: - # Determine the -A option from the fill parameters. kwargs["A"] = _parse_fill_mode(constantfill, gridfill, neighborfill, splinefill) if kwargs.get("A") is None and inquire is False: From 177048a4634fa6ad0af2f607c6dbc8a7f5830ec5 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 28 Mar 2025 01:02:19 +0800 Subject: [PATCH 4/6] Add private function _validate_params to simplify main codes --- pygmt/src/grdfill.py | 87 ++++++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 27 deletions(-) diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index f21fbf05cde..4adece5894a 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -19,6 +19,57 @@ __doctest_skip__ = ["grdfill"] +def _validate_params( + constantfill=None, + gridfill=None, + neighborfill=None, + splinefill=None, + inquire=False, + mode=None, +): + """ + Validate the fill/inquire parameters. + + >>> _validate_params(constantfill=20.0) + >>> _validate_params(inquire=True) + >>> _validate_params(mode="c20.0") + >>> _validate_params(constantfill=20.0, gridfill="bggrid.nc") + Traceback (most recent call last): + ... + pygmt.exceptions.GMTInvalidInput: Parameters ... are mutually exclusive. + >>> _validate_params(constantfill=20.0, inquire=True) + Traceback (most recent call last): + ... + pygmt.exceptions.GMTInvalidInput: Parameters ... are mutually exclusive. + >>> _validate_params() + Traceback (most recent call last): + ... + pygmt.exceptions.GMTInvalidInput: Need to specify parameter ... + """ + _fill_params = "'constantfill'/'gridfill'/'neighborfill'/'splinefill'" + # The deprecated 'mode' parameter is given. + if mode is not None: + msg = ( + "The 'mode' parameter is deprecated since v0.15.0 and will be removed in " + f"v0.19.0. Use {_fill_params} instead." + ) + warnings.warn(msg, FutureWarning, stacklevel=2) + + n_given = sum( + param is not None and param is not False + for param in [constantfill, gridfill, neighborfill, splinefill, inquire, mode] + ) + if n_given > 1: # More than one mutually exclusive parameters are given. + msg = f"Parameters {_fill_params}/'inquire'/'mode' are mutually exclusive." + raise GMTInvalidInput(msg) + if n_given == 0: # No parameters are given. + msg = ( + "Need to specify parameter {_fill_params} for filling holes or " + "'inquire' for inquiring the bounds of each hole." + ) + raise GMTInvalidInput(msg) + + def _parse_fill_mode( constantfill=None, gridfill=None, neighborfill=None, splinefill=None ) -> str | None: @@ -41,19 +92,7 @@ def _parse_fill_mode( 's0.5' >>> _parse_fill_mode(splinefill=True) 's' - >>> _parse_fill_mode(constantfill=20, gridfill="bggrid.nc") - Traceback (most recent call last): - ... - pygmt.exceptions.GMTInvalidInput: The ... parameters are mutually exclusive. """ - fill_params = [constantfill, gridfill, neighborfill, splinefill] - if sum(param is not None for param in fill_params) > 1: - msg = ( - "The 'constantfill', 'gridfill', 'neighborfill', and 'splinefill' " - "parameters are mutually exclusive." - ) - raise GMTInvalidInput(msg) - if constantfill is not None: return f"c{constantfill}" if gridfill is not None: @@ -157,21 +196,15 @@ def grdfill( array([[1.83333333, 6.16666667, 3.83333333, 8.16666667], [6.16666667, 7.83333333, 0.5 , 2.5 ]]) """ - if mode is not None: # The deprecated 'mode' parameter is given. - warnings.warn( - "The 'mode' parameter is deprecated since v0.15.0 and will be removed in " - "v0.19.0. Use 'constantfill'/'gridfill'/'neighborfill'/'splinefill' " - "instead.", - FutureWarning, - stacklevel=1, - ) - kwargs["A"] = mode - else: - kwargs["A"] = _parse_fill_mode(constantfill, gridfill, neighborfill, splinefill) - - if kwargs.get("A") is None and inquire is False: - msg = "At least parameter 'mode' or 'inquire' must be specified." - raise GMTInvalidInput(msg) + # Validate the fill/inquire parameters. + _validate_params(constantfill, gridfill, neighborfill, splinefill, inquire, mode) + + # Parse the fill parameters and return the appropriate string for the -A option. + kwargs["A"] = ( + _parse_fill_mode(constantfill, gridfill, neighborfill, splinefill) + if mode is None + else mode + ) with Session() as lib: with lib.virtualfile_in(check_kind="raster", data=grid) as vingrd: From 54301e58793f1a0e03a46730f74658571861550d Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 28 Mar 2025 16:14:59 +0800 Subject: [PATCH 5/6] Fix typos Co-authored-by: Michael Grund <23025878+michaelgrund@users.noreply.github.com> --- pygmt/src/grdfill.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index 4adece5894a..83686b4dced 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -59,7 +59,7 @@ def _validate_params( param is not None and param is not False for param in [constantfill, gridfill, neighborfill, splinefill, inquire, mode] ) - if n_given > 1: # More than one mutually exclusive parameters are given. + if n_given > 1: # More than one mutually exclusive parameter is given. msg = f"Parameters {_fill_params}/'inquire'/'mode' are mutually exclusive." raise GMTInvalidInput(msg) if n_given == 0: # No parameters are given. @@ -176,7 +176,7 @@ def grdfill( ------- ret If ``inquire`` is ``True``, return the bounds of each hole as a 2-D numpy array. - Otherwise, return type depends on whether the ``outgrid`` parameter is set: + Otherwise, the return type depends on whether the ``outgrid`` parameter is set: - :class:`xarray.DataArray` if ``outgrid`` is not set - ``None`` if ``outgrid`` is set (grid output will be stored in the file set by From e39fe9ff690ec0b2d670a687dc82ebc894559ba3 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 28 Mar 2025 16:17:17 +0800 Subject: [PATCH 6/6] Fix one more typo --- pygmt/src/grdfill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdfill.py b/pygmt/src/grdfill.py index 83686b4dced..300cfefede7 100644 --- a/pygmt/src/grdfill.py +++ b/pygmt/src/grdfill.py @@ -64,7 +64,7 @@ def _validate_params( raise GMTInvalidInput(msg) if n_given == 0: # No parameters are given. msg = ( - "Need to specify parameter {_fill_params} for filling holes or " + f"Need to specify parameter {_fill_params} for filling holes or " "'inquire' for inquiring the bounds of each hole." ) raise GMTInvalidInput(msg)