From dd3abe13640a80b43ba42c33df351bb075c1b898 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 25 Feb 2026 20:55:13 +0800 Subject: [PATCH 01/62] add grdmask for required arguments --- pygmt/__init__.py | 1 + pygmt/src/__init__.py | 1 + pygmt/src/grdmask.py | 110 ++++++++++++++++++++++++++++++++++++ pygmt/tests/test_grdmask.py | 107 +++++++++++++++++++++++++++++++++++ 4 files changed, 219 insertions(+) create mode 100644 pygmt/src/grdmask.py create mode 100644 pygmt/tests/test_grdmask.py diff --git a/pygmt/__init__.py b/pygmt/__init__.py index f10c4fad8a5..8adecdf429a 100644 --- a/pygmt/__init__.py +++ b/pygmt/__init__.py @@ -45,6 +45,7 @@ grdhisteq, grdinfo, grdlandmask, + grdmask, grdpaste, grdproject, grdsample, diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py index 3c3fb2c39e3..b3a41b3f637 100644 --- a/pygmt/src/__init__.py +++ b/pygmt/src/__init__.py @@ -25,6 +25,7 @@ from pygmt.src.grdimage import grdimage from pygmt.src.grdinfo import grdinfo from pygmt.src.grdlandmask import grdlandmask +from pygmt.src.grdmask import grdmask from pygmt.src.grdpaste import grdpaste from pygmt.src.grdproject import grdproject from pygmt.src.grdsample import grdsample diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py new file mode 100644 index 00000000000..7561ac7f8ca --- /dev/null +++ b/pygmt/src/grdmask.py @@ -0,0 +1,110 @@ +""" +grdmask - Create mask grid from polygons or point coverage. +""" + +from collections.abc import Sequence +from typing import Literal + +import xarray as xr +from pygmt._typing import PathLike +from pygmt.alias import Alias, AliasSystem +from pygmt.clib import Session +from pygmt.exceptions import GMTParameterError +from pygmt.helpers import build_arg_list, fmt_docstring + +__doctest_skip__ = ["grdmask"] + + +@fmt_docstring +def grdmask( + data, + outgrid: PathLike | None = None, + spacing: Sequence[float | str] | None = None, + region: Sequence[float | str] | str | None = None, + mask_values: Sequence[float] | None = None, + verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] + | bool = False, + **kwargs, +) -> xr.DataArray | None: + r""" + Create mask grid from polygons or point coverage. + + Reads one or more files (or standard input) containing polygon or data point + coordinates, and creates a binary grid file where nodes that fall inside, on the + edge, or outside the polygons (or within the search radius from data points) are + assigned values based on ``mask_values``. + + The mask grid can be used to mask out specific regions in other grids using + :func:`pygmt.grdmath` or similar tools. For masking based on coastline features, + consider using :func:`pygmt.grdlandmask` instead. + + Full GMT docs at :gmt-docs:`grdmask.html`. + + $aliases + - G = outgrid + - I = spacing + - N = mask_values + - R = region + - V = verbose + + Parameters + ---------- + data + Pass in either a file name, :class:`pandas.DataFrame`, :class:`numpy.ndarray`, + or a list of file names containing the polygon(s) or data points. Input can be: + + - **Polygon mode**: One or more files containing closed polygon coordinates + - **Point coverage mode**: Data points (used with ``search_radius`` parameter) + $outgrid + $spacing + mask_values : list of float, optional + Set the values that will be assigned to nodes. Provide three values in the form + [*outside*, *edge*, *inside*]. Default is ``[0, 0, 1]``, meaning nodes outside + and on the edge are set to 0, and nodes inside are set to 1. + + Values can be any number, or one of ``None``, ``"NaN"``, and ``np.nan`` for + setting nodes to NaN. + $region + $verbose + + Returns + ------- + ret + 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 + ``outgrid``) + + Example + ------- + >>> import pygmt + >>> import numpy as np + >>> # Create a simple polygon as a triangle + >>> polygon = np.array([[125, 30], [130, 30], [130, 35], [125, 30]]) + >>> # Create a mask grid with 1 arc-degree spacing + >>> mask = pygmt.grdmask(data=polygon, spacing=1, region=[125, 130, 30, 35]) + """ + if spacing is None or region is None: + raise GMTParameterError(required=["region", "spacing"]) + + aliasdict = AliasSystem( + I=Alias(spacing, name="spacing", sep="/", size=2), + N=Alias(mask_values, name="mask_values", sep="/", size=3), + ).add_common( + R=region, + V=verbose, + ) + aliasdict.merge(kwargs) + + with Session() as lib: + with ( + lib.virtualfile_in(check_kind="vector", data=data) as vintbl, + lib.virtualfile_out(kind="grid", fname=outgrid) as voutgrd, + ): + aliasdict["G"] = voutgrd + lib.call_module( + module="grdmask", + args=build_arg_list(aliasdict, infile=vintbl), + ) + return lib.virtualfile_to_raster(vfname=voutgrd, outgrid=outgrid) diff --git a/pygmt/tests/test_grdmask.py b/pygmt/tests/test_grdmask.py new file mode 100644 index 00000000000..15452ddaa98 --- /dev/null +++ b/pygmt/tests/test_grdmask.py @@ -0,0 +1,107 @@ +""" +Test pygmt.grdmask. +""" + +from pathlib import Path + +import numpy as np +import pytest +import xarray as xr +from pygmt import grdmask +from pygmt.enums import GridRegistration, GridType +from pygmt.exceptions import GMTParameterError +from pygmt.helpers import GMTTempFile + + +@pytest.fixture(scope="module", name="polygon_data") +def fixture_polygon_data(): + """ + Create a simple polygon for testing. + A triangle polygon covering the region [125, 130, 30, 35]. + """ + return np.array([[125, 30], [130, 30], [130, 35], [125, 30]]) + + +@pytest.fixture(scope="module", name="expected_grid") +def fixture_expected_grid(): + """ + Load the expected grdmask grid result. + """ + return xr.DataArray( + data=[ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 1.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + ], + coords={ + "x": [125.0, 126.0, 127.0, 128.0, 129.0, 130.0], + "y": [30.0, 31.0, 32.0, 33.0, 34.0, 35.0], + }, + dims=["y", "x"], + ) + + +def test_grdmask_outgrid(polygon_data, expected_grid): + """ + Creates a mask grid with an outgrid argument. + """ + with GMTTempFile(suffix=".nc") as tmpfile: + result = grdmask( + data=polygon_data, + outgrid=tmpfile.name, + spacing=1, + region=[125, 130, 30, 35], + ) + assert result is None # return value is None + assert Path(tmpfile.name).stat().st_size > 0 # check that outgrid exists + temp_grid = xr.load_dataarray(tmpfile.name, engine="gmt", raster_kind="grid") + # Check grid properties + assert temp_grid.dims == ("y", "x") + assert temp_grid.gmt.gtype is GridType.CARTESIAN + assert temp_grid.gmt.registration is GridRegistration.GRIDLINE + # Check grid values + xr.testing.assert_allclose(a=temp_grid, b=expected_grid) + + +@pytest.mark.benchmark +def test_grdmask_no_outgrid(polygon_data, expected_grid): + """ + Test grdmask with no set outgrid. + """ + result = grdmask(data=polygon_data, spacing=1, region=[125, 130, 30, 35]) + # Check grid properties + assert isinstance(result, xr.DataArray) + assert result.dims == ("y", "x") + assert result.gmt.gtype is GridType.CARTESIAN + assert result.gmt.registration is GridRegistration.GRIDLINE + # Check grid values + xr.testing.assert_allclose(a=result, b=expected_grid) + + +def test_grdmask_custom_mask_values(polygon_data): + """ + Test grdmask with custom mask_values. + """ + result = grdmask( + data=polygon_data, + spacing=1, + region=[125, 130, 30, 35], + mask_values=[10, 20, 30], # outside, edge, inside + ) + assert isinstance(result, xr.DataArray) + # Check that the grid has the right dimensions + assert result.shape == (6, 6) + # Check that we have values in the expected range + assert result.values.max() <= 30.0 + assert result.values.min() >= 0.0 + + +def test_grdmask_fails(): + """ + Check that grdmask fails correctly when region and spacing are not given. + """ + with pytest.raises(GMTParameterError): + grdmask(data=np.array([[0, 0], [1, 1], [1, 0], [0, 0]])) From caedc1a644284d34f7cfa1324d2efb3f260cca40 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 25 Feb 2026 21:15:57 +0800 Subject: [PATCH 02/62] add grdmask doc api --- doc/api/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/api/index.rst b/doc/api/index.rst index 6fad08b8165..1b886914aa6 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -155,6 +155,7 @@ Operations on raster data grdhisteq.equalize_grid grdhisteq.compute_bins grdlandmask + grdmask grdpaste grdproject grdsample From 44d969c59849cc398f955344b16bd4324e34071b Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 25 Feb 2026 22:01:56 +0800 Subject: [PATCH 03/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 7561ac7f8ca..ec35fb7fa0f 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -26,7 +26,7 @@ def grdmask( | bool = False, **kwargs, ) -> xr.DataArray | None: - r""" + """ Create mask grid from polygons or point coverage. Reads one or more files (or standard input) containing polygon or data point From fdee10959ba574cc4e6fdb4ca8bdcfa126ce76d4 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 25 Feb 2026 22:02:01 +0800 Subject: [PATCH 04/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index ec35fb7fa0f..7631efd61af 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -57,7 +57,7 @@ def grdmask( - **Point coverage mode**: Data points (used with ``search_radius`` parameter) $outgrid $spacing - mask_values : list of float, optional + mask_values Set the values that will be assigned to nodes. Provide three values in the form [*outside*, *edge*, *inside*]. Default is ``[0, 0, 1]``, meaning nodes outside and on the edge are set to 0, and nodes inside are set to 1. From 7f0ddfff51af111c71cf903104490d300d1f83e0 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 25 Feb 2026 22:02:27 +0800 Subject: [PATCH 05/62] change grdmask category --- doc/api/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/index.rst b/doc/api/index.rst index 1b886914aa6..7c61b0f4738 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -126,6 +126,7 @@ Operations on tabular data blockmedian blockmode filter1d + grdmask nearneighbor project select @@ -155,7 +156,6 @@ Operations on raster data grdhisteq.equalize_grid grdhisteq.compute_bins grdlandmask - grdmask grdpaste grdproject grdsample From 68123cfc1c54a2343239f175d87fab869b8184a8 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 25 Feb 2026 22:21:16 +0800 Subject: [PATCH 06/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 7631efd61af..94b2e5a0a75 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -40,7 +40,11 @@ def grdmask( Full GMT docs at :gmt-docs:`grdmask.html`. - $aliases + **Aliases** + + .. hlist:: + :columns: 3 + - G = outgrid - I = spacing - N = mask_values From e70ca8c97bb56f1eeeb0be949597abf6842d90d8 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 25 Feb 2026 22:24:38 +0800 Subject: [PATCH 07/62] show the value of mask --- pygmt/src/grdmask.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 94b2e5a0a75..83d9d4ae83d 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -41,10 +41,10 @@ def grdmask( Full GMT docs at :gmt-docs:`grdmask.html`. **Aliases** - + .. hlist:: :columns: 3 - + - G = outgrid - I = spacing - N = mask_values @@ -88,6 +88,13 @@ def grdmask( >>> polygon = np.array([[125, 30], [130, 30], [130, 35], [125, 30]]) >>> # Create a mask grid with 1 arc-degree spacing >>> mask = pygmt.grdmask(data=polygon, spacing=1, region=[125, 130, 30, 35]) + >>> mask.values + array([[0., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 1., 0.], + [0., 0., 1., 1., 1., 0.], + [0., 0., 1., 1., 1., 0.], + [0., 0., 1., 1., 1., 0.], + [0., 0., 0., 0., 0., 0.]]) """ if spacing is None or region is None: raise GMTParameterError(required=["region", "spacing"]) From 1b0e05714574cc7c4f9191098c11e54c557d27f9 Mon Sep 17 00:00:00 2001 From: chuan Date: Thu, 26 Feb 2026 13:34:59 +0800 Subject: [PATCH 08/62] change parameter name from mask_values to outside, edge, inside. --- pygmt/src/grdmask.py | 26 +++++++++++++++----------- pygmt/tests/test_grdmask.py | 6 ++++-- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 83d9d4ae83d..e0c341a84dc 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -21,7 +21,9 @@ def grdmask( outgrid: PathLike | None = None, spacing: Sequence[float | str] | None = None, region: Sequence[float | str] | str | None = None, - mask_values: Sequence[float] | None = None, + outside: float | str = 0, + edge: float | str = 0, + inside: float | str = 1, verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] | bool = False, **kwargs, @@ -32,7 +34,7 @@ def grdmask( Reads one or more files (or standard input) containing polygon or data point coordinates, and creates a binary grid file where nodes that fall inside, on the edge, or outside the polygons (or within the search radius from data points) are - assigned values based on ``mask_values``. + assigned values based on ``outside``, ``edge``, and ``inside`` parameters. The mask grid can be used to mask out specific regions in other grids using :func:`pygmt.grdmath` or similar tools. For masking based on coastline features, @@ -47,7 +49,7 @@ def grdmask( - G = outgrid - I = spacing - - N = mask_values + - N = outside/edge/inside - R = region - V = verbose @@ -61,13 +63,15 @@ def grdmask( - **Point coverage mode**: Data points (used with ``search_radius`` parameter) $outgrid $spacing - mask_values - Set the values that will be assigned to nodes. Provide three values in the form - [*outside*, *edge*, *inside*]. Default is ``[0, 0, 1]``, meaning nodes outside - and on the edge are set to 0, and nodes inside are set to 1. - - Values can be any number, or one of ``None``, ``"NaN"``, and ``np.nan`` for - setting nodes to NaN. + outside + Set the value assigned to nodes outside the polygons. Default is 0. + Can be any number, or one of ``None``, ``"NaN"``, and ``np.nan`` for NaN. + edge + Set the value assigned to nodes on the polygon edges. Default is 0. + Can be any number, or one of ``None``, ``"NaN"``, and ``np.nan`` for NaN. + inside + Set the value assigned to nodes inside the polygons. Default is 1. + Can be any number, or one of ``None``, ``"NaN"``, and ``np.nan`` for NaN. $region $verbose @@ -101,7 +105,7 @@ def grdmask( aliasdict = AliasSystem( I=Alias(spacing, name="spacing", sep="/", size=2), - N=Alias(mask_values, name="mask_values", sep="/", size=3), + N=Alias([outside, edge, inside], name="mask_values", sep="/", size=3), ).add_common( R=region, V=verbose, diff --git a/pygmt/tests/test_grdmask.py b/pygmt/tests/test_grdmask.py index 15452ddaa98..120ad68a543 100644 --- a/pygmt/tests/test_grdmask.py +++ b/pygmt/tests/test_grdmask.py @@ -83,13 +83,15 @@ def test_grdmask_no_outgrid(polygon_data, expected_grid): def test_grdmask_custom_mask_values(polygon_data): """ - Test grdmask with custom mask_values. + Test grdmask with custom outside, edge, inside values. """ result = grdmask( data=polygon_data, spacing=1, region=[125, 130, 30, 35], - mask_values=[10, 20, 30], # outside, edge, inside + outside=10, + edge=20, + inside=30, ) assert isinstance(result, xr.DataArray) # Check that the grid has the right dimensions From 7a166e97f4c64b61e5e18bc0332c1ebdd5d068ce Mon Sep 17 00:00:00 2001 From: chuan Date: Thu, 26 Feb 2026 21:02:31 +0800 Subject: [PATCH 09/62] reset the N parameter --- pygmt/src/grdmask.py | 61 ++++++++++++++++++++++++++++++++----- pygmt/tests/test_grdmask.py | 14 +++++++++ 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index e0c341a84dc..9b9815a0f4c 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -21,9 +21,9 @@ def grdmask( outgrid: PathLike | None = None, spacing: Sequence[float | str] | None = None, region: Sequence[float | str] | str | None = None, - outside: float | str = 0, - edge: float | str = 0, - inside: float | str = 1, + outside: float | Literal["z", "id"] = 0, + edge: float | Literal["z", "id"] = 0, + inside: float | Literal["z", "id"] = 1, verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] | bool = False, **kwargs, @@ -66,12 +66,26 @@ def grdmask( outside Set the value assigned to nodes outside the polygons. Default is 0. Can be any number, or one of ``None``, ``"NaN"``, and ``np.nan`` for NaN. + + When using ``inside="z"`` or ``inside="id"``, this sets the outside value + appended after the mode (e.g., ``outside=1, inside="z"`` gives ``-Nz/1``). edge Set the value assigned to nodes on the polygon edges. Default is 0. Can be any number, or one of ``None``, ``"NaN"``, and ``np.nan`` for NaN. + + When using ``inside="z"``, setting ``edge="z"`` treats edges as inside + (corresponds to ``-NZ``). Similarly, ``inside="id", edge="id"`` gives ``-NP``. + The combination ``inside="z", edge="id"`` or ``inside="id", edge="z"`` is + invalid and will raise an error. inside Set the value assigned to nodes inside the polygons. Default is 1. Can be any number, or one of ``None``, ``"NaN"``, and ``np.nan`` for NaN. + + Special values: + + - ``"z"``: Use the z-value from polygon data (segment header ``-Zzval``, + ``-Lheader``, or via ``-aZ=name``). Corresponds to GMT ``-Nz``. + - ``"id"``: Use a running polygon ID number. Corresponds to GMT ``-Np``. $region $verbose @@ -103,10 +117,43 @@ def grdmask( if spacing is None or region is None: raise GMTParameterError(required=["region", "spacing"]) - aliasdict = AliasSystem( - I=Alias(spacing, name="spacing", sep="/", size=2), - N=Alias([outside, edge, inside], name="mask_values", sep="/", size=3), - ).add_common( + # Build the -N parameter string + special_modes = {"z", "id"} + inside_is_special = inside in special_modes + edge_is_special = edge in special_modes + + # Validate combinations + if inside_is_special and edge_is_special and inside != edge: + msg = f"Invalid combination: inside={inside!r} and edge={edge!r}. " + raise GMTParameterError( + reason=msg + "When both are special modes, they must be the same." + ) + + # Build -N argument + if inside_is_special: + # Mode: -Nz, -NZ, -Np, or -NP + mode_char = ( + "Z" + if edge == inside + else "z" + if inside == "z" + else "P" + if edge == inside + else "p" + ) + n_value = f"{mode_char}/{outside}" if outside != 0 else mode_char + aliasdict = AliasSystem( + I=Alias(spacing, name="spacing", sep="/", size=2), + N=n_value, + ) + else: + # Standard mode: outside/edge/inside + aliasdict = AliasSystem( + I=Alias(spacing, name="spacing", sep="/", size=2), + N=Alias([outside, edge, inside], name="mask_values", sep="/", size=3), + ) + + aliasdict = aliasdict.add_common( R=region, V=verbose, ) diff --git a/pygmt/tests/test_grdmask.py b/pygmt/tests/test_grdmask.py index 120ad68a543..707869ba5e2 100644 --- a/pygmt/tests/test_grdmask.py +++ b/pygmt/tests/test_grdmask.py @@ -107,3 +107,17 @@ def test_grdmask_fails(): """ with pytest.raises(GMTParameterError): grdmask(data=np.array([[0, 0], [1, 1], [1, 0], [0, 0]])) + + +def test_grdmask_invalid_combination(polygon_data): + """ + Check that grdmask fails when inside and edge have different special modes. + """ + with pytest.raises(GMTParameterError): + grdmask( + data=polygon_data, + spacing=1, + region=[125, 130, 30, 35], + inside="z", + edge="id", + ) From da0cc55c2948561d1dc590026fbf679d548da1fc Mon Sep 17 00:00:00 2001 From: chuan Date: Mon, 2 Mar 2026 11:26:43 +0800 Subject: [PATCH 10/62] set a private function --- pygmt/src/grdmask.py | 111 +++++++++++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 35 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 9b9815a0f4c..ef7abe618ce 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -15,6 +15,78 @@ __doctest_skip__ = ["grdmask"] +def _alias_option_N( # noqa: N802 + outside: float | Literal["z", "id"] = 0, + edge: float | Literal["z", "id"] = 0, + inside: float | Literal["z", "id"] = 1, +) -> Alias: + """ + Return an Alias object for the -N option. + + Builds the -N parameter string for grdmask based on the inside, edge, and + outside values. Handles special modes "z" (use z-value from polygon data) + and "id" (use running polygon ID). + + Parameters + ---------- + outside + Set the value assigned to nodes outside the polygons. Default is 0. + edge + Set the value assigned to nodes on the polygon edges. Default is 0. + inside + Set the value assigned to nodes inside the polygons. Default is 1. + + Returns + ------- + Alias + An Alias object representing the -N option value. + + Raises + ------ + GMTParameterError + If inside and edge are both special modes but different values. + + Examples + -------- + >>> _alias_option_N()._value + '0/0/1' + >>> _alias_option_N(outside=1, edge=1, inside=1)._value + '1/1/1' + >>> _alias_option_N(inside="z")._value + 'z' + >>> _alias_option_N(inside="z", outside=1)._value + 'z/1' + >>> _alias_option_N(inside="z", edge="z")._value + 'Z' + >>> _alias_option_N(inside="id")._value + 'p' + >>> _alias_option_N(inside="id", edge="id")._value + 'P' + """ + special_modes = {"z", "id"} + inside_is_special = inside in special_modes + edge_is_special = edge in special_modes + + # Validate combinations + if inside_is_special and edge_is_special and inside != edge: + msg = f"Invalid combination: inside={inside!r} and edge={edge!r}. " + raise GMTParameterError( + reason=msg + "When both are special modes, they must be the same." + ) + + # Build -N argument + if inside_is_special: + # Mode: -Nz, -NZ, -Np, or -NP + if edge == inside: + mode_char = "Z" if inside == "z" else "P" + else: + mode_char = "z" if inside == "z" else "p" + n_value = f"{mode_char}/{outside}" if outside != 0 else mode_char + return Alias(n_value, name="mask_values") + # Standard mode: outside/edge/inside + return Alias([outside, edge, inside], name="mask_values", sep="/", size=3) + + @fmt_docstring def grdmask( data, @@ -117,41 +189,10 @@ def grdmask( if spacing is None or region is None: raise GMTParameterError(required=["region", "spacing"]) - # Build the -N parameter string - special_modes = {"z", "id"} - inside_is_special = inside in special_modes - edge_is_special = edge in special_modes - - # Validate combinations - if inside_is_special and edge_is_special and inside != edge: - msg = f"Invalid combination: inside={inside!r} and edge={edge!r}. " - raise GMTParameterError( - reason=msg + "When both are special modes, they must be the same." - ) - - # Build -N argument - if inside_is_special: - # Mode: -Nz, -NZ, -Np, or -NP - mode_char = ( - "Z" - if edge == inside - else "z" - if inside == "z" - else "P" - if edge == inside - else "p" - ) - n_value = f"{mode_char}/{outside}" if outside != 0 else mode_char - aliasdict = AliasSystem( - I=Alias(spacing, name="spacing", sep="/", size=2), - N=n_value, - ) - else: - # Standard mode: outside/edge/inside - aliasdict = AliasSystem( - I=Alias(spacing, name="spacing", sep="/", size=2), - N=Alias([outside, edge, inside], name="mask_values", sep="/", size=3), - ) + aliasdict = AliasSystem( + I=Alias(spacing, name="spacing", sep="/", size=2), + N=_alias_option_N(outside=outside, edge=edge, inside=inside), + ) aliasdict = aliasdict.add_common( R=region, From 6302abe6aa43b880ce0d6d66ebd6c1541b185bf8 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Mon, 2 Mar 2026 17:53:37 +0800 Subject: [PATCH 11/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index ef7abe618ce..13ce05150aa 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -63,7 +63,7 @@ def _alias_option_N( # noqa: N802 >>> _alias_option_N(inside="id", edge="id")._value 'P' """ - special_modes = {"z", "id"} + special_modes = {"z": "z", "id": "p"} inside_is_special = inside in special_modes edge_is_special = edge in special_modes From 1d6d753f0a998f9a4fd970d720568d9e5d688caf Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Mon, 2 Mar 2026 17:53:46 +0800 Subject: [PATCH 12/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 13ce05150aa..2fdff58713e 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -77,10 +77,9 @@ def _alias_option_N( # noqa: N802 # Build -N argument if inside_is_special: # Mode: -Nz, -NZ, -Np, or -NP + mode_char = special_modes[inside] if edge == inside: - mode_char = "Z" if inside == "z" else "P" - else: - mode_char = "z" if inside == "z" else "p" + mode_char = mode_char.upper() n_value = f"{mode_char}/{outside}" if outside != 0 else mode_char return Alias(n_value, name="mask_values") # Standard mode: outside/edge/inside From d4293de0514834eedf59535c2b130695d5de59e8 Mon Sep 17 00:00:00 2001 From: chuan Date: Mon, 2 Mar 2026 17:56:58 +0800 Subject: [PATCH 13/62] delete docs --- pygmt/src/grdmask.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 2fdff58713e..59f8e3d56eb 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -27,25 +27,6 @@ def _alias_option_N( # noqa: N802 outside values. Handles special modes "z" (use z-value from polygon data) and "id" (use running polygon ID). - Parameters - ---------- - outside - Set the value assigned to nodes outside the polygons. Default is 0. - edge - Set the value assigned to nodes on the polygon edges. Default is 0. - inside - Set the value assigned to nodes inside the polygons. Default is 1. - - Returns - ------- - Alias - An Alias object representing the -N option value. - - Raises - ------ - GMTParameterError - If inside and edge are both special modes but different values. - Examples -------- >>> _alias_option_N()._value From a0ded6445f4a9bf97feb9a281844907bfffa7811 Mon Sep 17 00:00:00 2001 From: chuan Date: Mon, 2 Mar 2026 18:08:04 +0800 Subject: [PATCH 14/62] fix type error --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 59f8e3d56eb..7fc204a41e6 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -58,7 +58,7 @@ def _alias_option_N( # noqa: N802 # Build -N argument if inside_is_special: # Mode: -Nz, -NZ, -Np, or -NP - mode_char = special_modes[inside] + mode_char = special_modes[inside] # type: ignore[index] if edge == inside: mode_char = mode_char.upper() n_value = f"{mode_char}/{outside}" if outside != 0 else mode_char From 871bcff16b1c5471c103455c424a6502d998f141 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Tue, 3 Mar 2026 10:16:12 +0800 Subject: [PATCH 15/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 7fc204a41e6..aa3ea23a1a4 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -31,8 +31,8 @@ def _alias_option_N( # noqa: N802 -------- >>> _alias_option_N()._value '0/0/1' - >>> _alias_option_N(outside=1, edge=1, inside=1)._value - '1/1/1' + >>> _alias_option_N(outside=1, edge=2, inside=3)._value + '1/2/3' >>> _alias_option_N(inside="z")._value 'z' >>> _alias_option_N(inside="z", outside=1)._value From a0519e6d8a278db2d292af79fc8dcc2f4179808e Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Tue, 3 Mar 2026 10:16:19 +0800 Subject: [PATCH 16/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index aa3ea23a1a4..eb6b8b8ade7 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -16,9 +16,9 @@ def _alias_option_N( # noqa: N802 - outside: float | Literal["z", "id"] = 0, - edge: float | Literal["z", "id"] = 0, - inside: float | Literal["z", "id"] = 1, + outside: float | Literal["z", "id"] | None = None, + edge: float | Literal["z", "id"] | None = None, + inside: float | Literal["z", "id"] | None = None, ) -> Alias: """ Return an Alias object for the -N option. From 7d98d67bcbca08d23db47682cbd9e8eafa60bd8d Mon Sep 17 00:00:00 2001 From: chuan Date: Tue, 3 Mar 2026 10:25:38 +0800 Subject: [PATCH 17/62] simplified code logic --- pygmt/src/grdmask.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index eb6b8b8ade7..7b62a55938d 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -29,39 +29,35 @@ def _alias_option_N( # noqa: N802 Examples -------- - >>> _alias_option_N()._value + >>> _alias_option_N(outside=0, edge=0, inside=1)._value '0/0/1' >>> _alias_option_N(outside=1, edge=2, inside=3)._value '1/2/3' - >>> _alias_option_N(inside="z")._value + >>> _alias_option_N(outside=0, edge=0, inside="z")._value 'z' - >>> _alias_option_N(inside="z", outside=1)._value + >>> _alias_option_N(outside=1, edge=0, inside="z")._value 'z/1' - >>> _alias_option_N(inside="z", edge="z")._value + >>> _alias_option_N(outside=0, edge="z", inside="z")._value 'Z' - >>> _alias_option_N(inside="id")._value + >>> _alias_option_N(outside=0, edge=0, inside="id")._value 'p' - >>> _alias_option_N(inside="id", edge="id")._value + >>> _alias_option_N(outside=0, edge="id", inside="id")._value 'P' """ - special_modes = {"z": "z", "id": "p"} - inside_is_special = inside in special_modes - edge_is_special = edge in special_modes - # Validate combinations - if inside_is_special and edge_is_special and inside != edge: + if inside in {"z", "id"} and edge in {"z", "id"} and inside != edge: msg = f"Invalid combination: inside={inside!r} and edge={edge!r}. " raise GMTParameterError( reason=msg + "When both are special modes, they must be the same." ) # Build -N argument - if inside_is_special: + if inside in {"z", "id"}: # Mode: -Nz, -NZ, -Np, or -NP - mode_char = special_modes[inside] # type: ignore[index] + mode_char = "z" if inside == "z" else "p" if edge == inside: mode_char = mode_char.upper() - n_value = f"{mode_char}/{outside}" if outside != 0 else mode_char + n_value = mode_char if outside == 0 else f"{mode_char}/{outside}" return Alias(n_value, name="mask_values") # Standard mode: outside/edge/inside return Alias([outside, edge, inside], name="mask_values", sep="/", size=3) From ad2379439bb1f23e69b698340737bde71360236f Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Tue, 3 Mar 2026 14:43:50 +0800 Subject: [PATCH 18/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 7b62a55938d..22618122df8 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -57,7 +57,7 @@ def _alias_option_N( # noqa: N802 mode_char = "z" if inside == "z" else "p" if edge == inside: mode_char = mode_char.upper() - n_value = mode_char if outside == 0 else f"{mode_char}/{outside}" + mask_values = mode_char if outside is None else [mode_char, outside] return Alias(n_value, name="mask_values") # Standard mode: outside/edge/inside return Alias([outside, edge, inside], name="mask_values", sep="/", size=3) From 8e2a99864d0afef1f044baa1c717dd4189f5f27e Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Tue, 3 Mar 2026 14:44:12 +0800 Subject: [PATCH 19/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 22618122df8..fe8f74bec8e 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -58,7 +58,7 @@ def _alias_option_N( # noqa: N802 if edge == inside: mode_char = mode_char.upper() mask_values = mode_char if outside is None else [mode_char, outside] - return Alias(n_value, name="mask_values") + return Alias(mask_values, name="mask_values", sep="/", size=2) # Standard mode: outside/edge/inside return Alias([outside, edge, inside], name="mask_values", sep="/", size=3) From 21b543d760eff42885327ad2dee58e15bd2cea0a Mon Sep 17 00:00:00 2001 From: chuan Date: Tue, 3 Mar 2026 17:11:15 +0800 Subject: [PATCH 20/62] check output --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index fe8f74bec8e..576c792e3e9 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -57,7 +57,7 @@ def _alias_option_N( # noqa: N802 mode_char = "z" if inside == "z" else "p" if edge == inside: mode_char = mode_char.upper() - mask_values = mode_char if outside is None else [mode_char, outside] + mask_values = mode_char if outside in {None, 0} else [mode_char, outside] return Alias(mask_values, name="mask_values", sep="/", size=2) # Standard mode: outside/edge/inside return Alias([outside, edge, inside], name="mask_values", sep="/", size=3) From 53b334646130b550bff1ea6f4047ace2b77046d1 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Mar 2026 12:22:30 +0800 Subject: [PATCH 21/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 576c792e3e9..e1a92cd8510 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -168,9 +168,7 @@ def grdmask( aliasdict = AliasSystem( I=Alias(spacing, name="spacing", sep="/", size=2), N=_alias_option_N(outside=outside, edge=edge, inside=inside), - ) - - aliasdict = aliasdict.add_common( + ).add_common( R=region, V=verbose, ) From 8e9a3b69d271e6560bd31a70eea5a661946039bc Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Mar 2026 12:22:49 +0800 Subject: [PATCH 22/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index e1a92cd8510..e94a66d2e36 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -112,28 +112,18 @@ def grdmask( $outgrid $spacing outside - Set the value assigned to nodes outside the polygons. Default is 0. - Can be any number, or one of ``None``, ``"NaN"``, and ``np.nan`` for NaN. - - When using ``inside="z"`` or ``inside="id"``, this sets the outside value - appended after the mode (e.g., ``outside=1, inside="z"`` gives ``-Nz/1``). edge - Set the value assigned to nodes on the polygon edges. Default is 0. - Can be any number, or one of ``None``, ``"NaN"``, and ``np.nan`` for NaN. - - When using ``inside="z"``, setting ``edge="z"`` treats edges as inside - (corresponds to ``-NZ``). Similarly, ``inside="id", edge="id"`` gives ``-NP``. - The combination ``inside="z", edge="id"`` or ``inside="id", edge="z"`` is - invalid and will raise an error. inside - Set the value assigned to nodes inside the polygons. Default is 1. + Set the value assigned to nodes outside, on the edge, or inside the polygons. Can be any number, or one of ``None``, ``"NaN"``, and ``np.nan`` for NaN. - - Special values: + + ``inside`` can also be set to one of the following values: - ``"z"``: Use the z-value from polygon data (segment header ``-Zzval``, - ``-Lheader``, or via ``-aZ=name``). Corresponds to GMT ``-Nz``. - - ``"id"``: Use a running polygon ID number. Corresponds to GMT ``-Np``. + ``-Lheader``, or via ``-aZ=name``). + - ``"id"``: Use a running polygon ID number. + + To treats edges as inside, using the same value as ``inside``. $region $verbose From 2a19936ff929fbd0e359992476c0632b64eed5bd Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Mar 2026 12:23:17 +0800 Subject: [PATCH 23/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index e94a66d2e36..d3f50a1aeac 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -58,9 +58,9 @@ def _alias_option_N( # noqa: N802 if edge == inside: mode_char = mode_char.upper() mask_values = mode_char if outside in {None, 0} else [mode_char, outside] - return Alias(mask_values, name="mask_values", sep="/", size=2) - # Standard mode: outside/edge/inside - return Alias([outside, edge, inside], name="mask_values", sep="/", size=3) + else: + mask_values = [outside, edge, inside] + return Alias([outside, edge, inside], name="mask_values", sep="/", size={2, 3}) @fmt_docstring From 813b8e27286a77bf7ebd96da356cec6b3733d22a Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 4 Mar 2026 12:25:41 +0800 Subject: [PATCH 24/62] fix format --- pygmt/src/grdmask.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index d3f50a1aeac..4d7fa6a15d4 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -60,7 +60,7 @@ def _alias_option_N( # noqa: N802 mask_values = mode_char if outside in {None, 0} else [mode_char, outside] else: mask_values = [outside, edge, inside] - return Alias([outside, edge, inside], name="mask_values", sep="/", size={2, 3}) + return Alias(mask_values, name="mask_values", sep="/", size={2, 3}) @fmt_docstring @@ -116,14 +116,14 @@ def grdmask( inside Set the value assigned to nodes outside, on the edge, or inside the polygons. Can be any number, or one of ``None``, ``"NaN"``, and ``np.nan`` for NaN. - - ``inside`` can also be set to one of the following values: + + ``inside`` can also be set to one of the following values: - ``"z"``: Use the z-value from polygon data (segment header ``-Zzval``, ``-Lheader``, or via ``-aZ=name``). - ``"id"``: Use a running polygon ID number. - - To treats edges as inside, using the same value as ``inside``. + + To treats edges as inside, using the same value as ``inside``. $region $verbose From 1eb9667ceba205d9ff5aa915ba3249439a992cf3 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 4 Mar 2026 12:28:28 +0800 Subject: [PATCH 25/62] fix static type error --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 4d7fa6a15d4..21ce1bfb15f 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -60,7 +60,7 @@ def _alias_option_N( # noqa: N802 mask_values = mode_char if outside in {None, 0} else [mode_char, outside] else: mask_values = [outside, edge, inside] - return Alias(mask_values, name="mask_values", sep="/", size={2, 3}) + return Alias(mask_values, name="mask_values", sep="/", size=(2, 3)) @fmt_docstring From a1ba80073e96d4659720d11488c91d3294c80242 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Mar 2026 15:14:29 +0800 Subject: [PATCH 26/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 21ce1bfb15f..7e811870319 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -57,7 +57,7 @@ def _alias_option_N( # noqa: N802 mode_char = "z" if inside == "z" else "p" if edge == inside: mode_char = mode_char.upper() - mask_values = mode_char if outside in {None, 0} else [mode_char, outside] + mask_values = mode_char if outside is None else [mode_char, outside] else: mask_values = [outside, edge, inside] return Alias(mask_values, name="mask_values", sep="/", size=(2, 3)) From 05e963556bd840b32e4d8c32414ff00f303ff69f Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Mar 2026 15:14:38 +0800 Subject: [PATCH 27/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 7e811870319..ad67a3ae2ff 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -44,8 +44,9 @@ def _alias_option_N( # noqa: N802 >>> _alias_option_N(outside=0, edge="id", inside="id")._value 'P' """ + _inside_modes = {"z": "z", "id": "p"} # Validate combinations - if inside in {"z", "id"} and edge in {"z", "id"} and inside != edge: + if inside in _inside_modes and edge in _inside_modes and inside != edge: msg = f"Invalid combination: inside={inside!r} and edge={edge!r}. " raise GMTParameterError( reason=msg + "When both are special modes, they must be the same." From f9a87fe0a389db568bc29e695eeb87a97ceea9c1 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Mar 2026 15:14:46 +0800 Subject: [PATCH 28/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index ad67a3ae2ff..afefcf7837d 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -53,7 +53,7 @@ def _alias_option_N( # noqa: N802 ) # Build -N argument - if inside in {"z", "id"}: + if inside in _inside_modes: # Mode: -Nz, -NZ, -Np, or -NP mode_char = "z" if inside == "z" else "p" if edge == inside: From b63a8dece4bdf56a91efd515b7bf1e44a28bc864 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Wed, 4 Mar 2026 15:14:53 +0800 Subject: [PATCH 29/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index afefcf7837d..9ae8dcf217a 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -55,7 +55,7 @@ def _alias_option_N( # noqa: N802 # Build -N argument if inside in _inside_modes: # Mode: -Nz, -NZ, -Np, or -NP - mode_char = "z" if inside == "z" else "p" + mode_char = _inside_modes[inside] if edge == inside: mode_char = mode_char.upper() mask_values = mode_char if outside is None else [mode_char, outside] From 4bafeadd66a2ce4c24cd9f1ca00938326e3fda37 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Mar 2026 10:08:50 +0800 Subject: [PATCH 30/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 9ae8dcf217a..8a72372b4b8 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -29,8 +29,7 @@ def _alias_option_N( # noqa: N802 Examples -------- - >>> _alias_option_N(outside=0, edge=0, inside=1)._value - '0/0/1' + >>> _alias_option_N()._value >>> _alias_option_N(outside=1, edge=2, inside=3)._value '1/2/3' >>> _alias_option_N(outside=0, edge=0, inside="z")._value From e65774abda48f8bf3666e94951716fb1a5b003fe Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Mar 2026 10:09:07 +0800 Subject: [PATCH 31/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 8a72372b4b8..6d3b0200c94 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -32,15 +32,15 @@ def _alias_option_N( # noqa: N802 >>> _alias_option_N()._value >>> _alias_option_N(outside=1, edge=2, inside=3)._value '1/2/3' - >>> _alias_option_N(outside=0, edge=0, inside="z")._value + >>> _alias_option_N(inside="z")._value 'z' - >>> _alias_option_N(outside=1, edge=0, inside="z")._value + >>> _alias_option_N(outside=1, inside="z")._value 'z/1' - >>> _alias_option_N(outside=0, edge="z", inside="z")._value + >>> _alias_option_N(edge="z", inside="z")._value 'Z' - >>> _alias_option_N(outside=0, edge=0, inside="id")._value + >>> _alias_option_N(inside="id")._value 'p' - >>> _alias_option_N(outside=0, edge="id", inside="id")._value + >>> _alias_option_N(edge="id", inside="id")._value 'P' """ _inside_modes = {"z": "z", "id": "p"} From ee32aca7c29f44f87871c516658adf7a792351f9 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Mar 2026 10:09:21 +0800 Subject: [PATCH 32/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 6d3b0200c94..fde0586aec4 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -54,10 +54,10 @@ def _alias_option_N( # noqa: N802 # Build -N argument if inside in _inside_modes: # Mode: -Nz, -NZ, -Np, or -NP - mode_char = _inside_modes[inside] + mode = _inside_modes[inside] if edge == inside: - mode_char = mode_char.upper() - mask_values = mode_char if outside is None else [mode_char, outside] + mode = mode.upper() + mask_values = mode if outside is None else [mode, outside] else: mask_values = [outside, edge, inside] return Alias(mask_values, name="mask_values", sep="/", size=(2, 3)) From 8851b91bf2ad5bcf34a556cf15688e4931a5df0f Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Mar 2026 10:09:51 +0800 Subject: [PATCH 33/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index fde0586aec4..88468e388f3 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -79,7 +79,7 @@ def grdmask( """ Create mask grid from polygons or point coverage. - Reads one or more files (or standard input) containing polygon or data point + Reads one or more files containing polygon or data point coordinates, and creates a binary grid file where nodes that fall inside, on the edge, or outside the polygons (or within the search radius from data points) are assigned values based on ``outside``, ``edge``, and ``inside`` parameters. From fe5348e6b91d30955c365f89d9a222f9e8d1a842 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Mar 2026 10:10:04 +0800 Subject: [PATCH 34/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 88468e388f3..f962a22b567 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -80,7 +80,7 @@ def grdmask( Create mask grid from polygons or point coverage. Reads one or more files containing polygon or data point - coordinates, and creates a binary grid file where nodes that fall inside, on the + coordinates, and creates a grid where nodes that fall inside, on the edge, or outside the polygons (or within the search radius from data points) are assigned values based on ``outside``, ``edge``, and ``inside`` parameters. From 7919391942029d08540807be3a89d7f7e7f89e09 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Mar 2026 10:10:18 +0800 Subject: [PATCH 35/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index f962a22b567..37377779a27 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -104,8 +104,8 @@ def grdmask( Parameters ---------- data - Pass in either a file name, :class:`pandas.DataFrame`, :class:`numpy.ndarray`, - or a list of file names containing the polygon(s) or data points. Input can be: + Pass in either a file name to an ASCII data table, a 2-D $table_classes + containg the polygon(s) or data points. Input can be: - **Polygon mode**: One or more files containing closed polygon coordinates - **Point coverage mode**: Data points (used with ``search_radius`` parameter) From 38a814ea29a0cae458ee51ddf2d0a09beacfa505 Mon Sep 17 00:00:00 2001 From: chuan Date: Fri, 6 Mar 2026 10:24:27 +0800 Subject: [PATCH 36/62] fix alias_n --- pygmt/src/grdmask.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 37377779a27..32034ad7e52 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -54,12 +54,17 @@ def _alias_option_N( # noqa: N802 # Build -N argument if inside in _inside_modes: # Mode: -Nz, -NZ, -Np, or -NP - mode = _inside_modes[inside] + mode = _inside_modes[inside] # type: ignore[index] if edge == inside: mode = mode.upper() - mask_values = mode if outside is None else [mode, outside] + mask_values: str | list[str | float] | None = ( + mode if outside is None else [mode, outside] + ) + elif outside is None and edge is None: + # All values are None, return None + mask_values = None else: - mask_values = [outside, edge, inside] + mask_values = [outside, edge, inside] # type: ignore[list-item] return Alias(mask_values, name="mask_values", sep="/", size=(2, 3)) @@ -104,7 +109,7 @@ def grdmask( Parameters ---------- data - Pass in either a file name to an ASCII data table, a 2-D $table_classes + Pass in either a file name to an ASCII data table, a 2-D $table_classes containg the polygon(s) or data points. Input can be: - **Polygon mode**: One or more files containing closed polygon coordinates From 210e92dff497fc404eaf17982287155877985699 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Mar 2026 10:58:08 +0800 Subject: [PATCH 37/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 32034ad7e52..862bbf3232a 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -16,7 +16,7 @@ def _alias_option_N( # noqa: N802 - outside: float | Literal["z", "id"] | None = None, + outside: float None = None, edge: float | Literal["z", "id"] | None = None, inside: float | Literal["z", "id"] | None = None, ) -> Alias: From b548821a9f35c6c4713d6d405aa6072fdfe0ab00 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Fri, 6 Mar 2026 10:58:18 +0800 Subject: [PATCH 38/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 862bbf3232a..32f80931974 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -74,9 +74,9 @@ def grdmask( outgrid: PathLike | None = None, spacing: Sequence[float | str] | None = None, region: Sequence[float | str] | str | None = None, - outside: float | Literal["z", "id"] = 0, - edge: float | Literal["z", "id"] = 0, - inside: float | Literal["z", "id"] = 1, + outside: float | None = None, + edge: float | Literal["z", "id"] | None = None, + inside: float | Literal["z", "id"] | None = None, verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] | bool = False, **kwargs, From 5c818d6a6cd3e89a6ad8d32d48c901f0801c2b63 Mon Sep 17 00:00:00 2001 From: chuan Date: Fri, 6 Mar 2026 11:02:41 +0800 Subject: [PATCH 39/62] fix check --- pygmt/src/grdmask.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 32f80931974..e52ccb7625e 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -16,7 +16,7 @@ def _alias_option_N( # noqa: N802 - outside: float None = None, + outside: float | None = None, edge: float | Literal["z", "id"] | None = None, inside: float | Literal["z", "id"] | None = None, ) -> Alias: @@ -74,7 +74,7 @@ def grdmask( outgrid: PathLike | None = None, spacing: Sequence[float | str] | None = None, region: Sequence[float | str] | str | None = None, - outside: float | None = None, + outside: float | None = None, edge: float | Literal["z", "id"] | None = None, inside: float | Literal["z", "id"] | None = None, verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] From 5990c161eac06c7e446cb8423db995e6deb59238 Mon Sep 17 00:00:00 2001 From: chuan Date: Fri, 6 Mar 2026 11:05:15 +0800 Subject: [PATCH 40/62] fix static --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index e52ccb7625e..e33f6ddf0d8 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -58,7 +58,7 @@ def _alias_option_N( # noqa: N802 if edge == inside: mode = mode.upper() mask_values: str | list[str | float] | None = ( - mode if outside is None else [mode, outside] + mode if outside is None else [mode, outside] # type: ignore[assignment] ) elif outside is None and edge is None: # All values are None, return None From 7486ef9eb957d3b93558ccad8a60800f09ce0e4c Mon Sep 17 00:00:00 2001 From: chuan Date: Sat, 7 Mar 2026 17:46:37 +0800 Subject: [PATCH 41/62] fix outside --- pygmt/src/grdmask.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index e33f6ddf0d8..ef1f6b5b601 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -60,11 +60,12 @@ def _alias_option_N( # noqa: N802 mask_values: str | list[str | float] | None = ( mode if outside is None else [mode, outside] # type: ignore[assignment] ) - elif outside is None and edge is None: - # All values are None, return None + elif inside is None: + # inside is None, return None (GMT uses default 0/0/1) mask_values = None else: - mask_values = [outside, edge, inside] # type: ignore[list-item] + # inside is a number, build the full mask with defaults for outside/edge + mask_values = [outside or 0, edge or 0, inside] # type: ignore[list-item] return Alias(mask_values, name="mask_values", sep="/", size=(2, 3)) From 223b0bce244cd886e862177dcba619157be8541e Mon Sep 17 00:00:00 2001 From: chuan Date: Sun, 8 Mar 2026 20:50:15 +0800 Subject: [PATCH 42/62] add test and check for mask_values --- pygmt/src/grdmask.py | 28 ++++++++++++------ pygmt/tests/test_grdmask.py | 58 +++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 9 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index ef1f6b5b601..0a3e0867a48 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -32,6 +32,8 @@ def _alias_option_N( # noqa: N802 >>> _alias_option_N()._value >>> _alias_option_N(outside=1, edge=2, inside=3)._value '1/2/3' + >>> _alias_option_N(outside=3)._value + '3/0/1' >>> _alias_option_N(inside="z")._value 'z' >>> _alias_option_N(outside=1, inside="z")._value @@ -45,6 +47,10 @@ def _alias_option_N( # noqa: N802 """ _inside_modes = {"z": "z", "id": "p"} # Validate combinations + if edge in _inside_modes and inside != edge: + msg = f"Invalid combination: edge={edge!r} requires inside={edge!r}." + raise GMTParameterError(reason=msg) + if inside in _inside_modes and edge in _inside_modes and inside != edge: msg = f"Invalid combination: inside={inside!r} and edge={edge!r}. " raise GMTParameterError( @@ -60,12 +66,16 @@ def _alias_option_N( # noqa: N802 mask_values: str | list[str | float] | None = ( mode if outside is None else [mode, outside] # type: ignore[assignment] ) - elif inside is None: - # inside is None, return None (GMT uses default 0/0/1) + elif inside is None and outside is None and edge is None: + # All three are None, return None (GMT uses default 0/0/1) mask_values = None else: - # inside is a number, build the full mask with defaults for outside/edge - mask_values = [outside or 0, edge or 0, inside] # type: ignore[list-item] + # Build the full mask with defaults for any missing values + mask_values = [ + 0 if outside is None else outside, + 0 if edge is None else edge, + 1 if inside is None else inside, + ] # type: ignore[list-item] return Alias(mask_values, name="mask_values", sep="/", size=(2, 3)) @@ -129,7 +139,7 @@ def grdmask( ``-Lheader``, or via ``-aZ=name``). - ``"id"``: Use a running polygon ID number. - To treats edges as inside, using the same value as ``inside``. + To treat edges as inside, use the same value as ``inside``. $region $verbose @@ -152,10 +162,10 @@ def grdmask( >>> mask = pygmt.grdmask(data=polygon, spacing=1, region=[125, 130, 30, 35]) >>> mask.values array([[0., 0., 0., 0., 0., 0.], - [0., 0., 0., 0., 1., 0.], - [0., 0., 1., 1., 1., 0.], - [0., 0., 1., 1., 1., 0.], - [0., 0., 1., 1., 1., 0.], + [0., 0., 1., 1., 1., 0.], + [0., 0., 0., 1., 1., 0.], + [0., 0., 0., 0., 1., 0.], + [0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0.]]) """ if spacing is None or region is None: diff --git a/pygmt/tests/test_grdmask.py b/pygmt/tests/test_grdmask.py index 707869ba5e2..748d0704b90 100644 --- a/pygmt/tests/test_grdmask.py +++ b/pygmt/tests/test_grdmask.py @@ -44,6 +44,28 @@ def fixture_expected_grid(): ) +@pytest.fixture(scope="module", name="expected_grid_outside_only") +def fixture_expected_grid_outside_only(): + """ + Load the expected grdmask grid result when only outside is set. + """ + return xr.DataArray( + data=[ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [3.0, 0.0, 1.0, 1.0, 1.0, 0.0], + [3.0, 3.0, 0.0, 1.0, 1.0, 0.0], + [3.0, 3.0, 3.0, 0.0, 1.0, 0.0], + [3.0, 3.0, 3.0, 3.0, 0.0, 0.0], + [3.0, 3.0, 3.0, 3.0, 3.0, 0.0], + ], + coords={ + "x": [125.0, 126.0, 127.0, 128.0, 129.0, 130.0], + "y": [30.0, 31.0, 32.0, 33.0, 34.0, 35.0], + }, + dims=["y", "x"], + ) + + def test_grdmask_outgrid(polygon_data, expected_grid): """ Creates a mask grid with an outgrid argument. @@ -101,6 +123,24 @@ def test_grdmask_custom_mask_values(polygon_data): assert result.values.min() >= 0.0 +def test_grdmask_outside_only(polygon_data, expected_grid_outside_only): + """ + Test grdmask when only outside is set. + """ + result = grdmask( + data=polygon_data, + spacing=1, + region=[125, 130, 30, 35], + outside=3, + ) + + assert isinstance(result, xr.DataArray) + assert result.dims == ("y", "x") + assert result.gmt.gtype is GridType.CARTESIAN + assert result.gmt.registration is GridRegistration.GRIDLINE + xr.testing.assert_allclose(a=result, b=expected_grid_outside_only) + + def test_grdmask_fails(): """ Check that grdmask fails correctly when region and spacing are not given. @@ -121,3 +161,21 @@ def test_grdmask_invalid_combination(polygon_data): inside="z", edge="id", ) + + +@pytest.mark.parametrize( + ("edge", "inside"), + [("z", None), ("id", None), ("z", 5), ("id", 5)], +) +def test_grdmask_invalid_edge_special_mode(polygon_data, edge, inside): + """ + Check that special edge modes require the same special inside mode. + """ + with pytest.raises(GMTParameterError): + grdmask( + data=polygon_data, + spacing=1, + region=[125, 130, 30, 35], + edge=edge, + inside=inside, + ) From 223e71c86d89d83c92d3cc0787928ffbc3c4298a Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 18 Mar 2026 21:07:10 +0800 Subject: [PATCH 43/62] rewarp and fix layout --- pygmt/clib/session.py | 2 +- pygmt/src/grdmask.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index aa8de0fbcb3..6ce70fa4f67 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1918,7 +1918,7 @@ def virtualfile_in( # noqa: PLR0912 if hasattr(data, "items") and not hasattr(data, "to_frame"): # Dictionary, pandas.DataFrame or xarray.Dataset types. # pandas.Series will be handled below like a 1-D numpy.ndarray. - _data = [array for _, array in data.items()] # noqa: PERF102 + _data = [array for _, array in data.items()] else: # Python list, tuple, numpy.ndarray, and pandas.Series types _data = np.atleast_2d(np.asanyarray(data).T) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 0a3e0867a48..34d1553f573 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -45,6 +45,10 @@ def _alias_option_N( # noqa: N802 >>> _alias_option_N(edge="id", inside="id")._value 'P' """ + # All three are None, return None (GMT uses default 0/0/1) + if all(v is None for v in (outside, inside, edge)): + return Alias(None, name="mask_values") + _inside_modes = {"z": "z", "id": "p"} # Validate combinations if edge in _inside_modes and inside != edge: @@ -66,9 +70,6 @@ def _alias_option_N( # noqa: N802 mask_values: str | list[str | float] | None = ( mode if outside is None else [mode, outside] # type: ignore[assignment] ) - elif inside is None and outside is None and edge is None: - # All three are None, return None (GMT uses default 0/0/1) - mask_values = None else: # Build the full mask with defaults for any missing values mask_values = [ @@ -95,10 +96,10 @@ def grdmask( """ Create mask grid from polygons or point coverage. - Reads one or more files containing polygon or data point - coordinates, and creates a grid where nodes that fall inside, on the - edge, or outside the polygons (or within the search radius from data points) are - assigned values based on ``outside``, ``edge``, and ``inside`` parameters. + Reads one or more files containing polygon or data point coordinates, and creates a + grid where nodes that fall inside, on the edge, or outside the polygons (or within + the search radius from data points) are assigned values based on ``outside``, + ``edge``, and ``inside`` parameters. The mask grid can be used to mask out specific regions in other grids using :func:`pygmt.grdmath` or similar tools. For masking based on coastline features, From 596f7a6dafe4275f90065d96f09a6d3606b68e5b Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 18 Mar 2026 21:09:50 +0800 Subject: [PATCH 44/62] fix check --- pygmt/clib/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 6ce70fa4f67..ed45c9cf763 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1918,7 +1918,7 @@ def virtualfile_in( # noqa: PLR0912 if hasattr(data, "items") and not hasattr(data, "to_frame"): # Dictionary, pandas.DataFrame or xarray.Dataset types. # pandas.Series will be handled below like a 1-D numpy.ndarray. - _data = [array for _, array in data.items()] + _data = [array for _, array in data.items()] # noqa: PERF102 else: # Python list, tuple, numpy.ndarray, and pandas.Series types _data = np.atleast_2d(np.asanyarray(data).T) From 3b576f86dbd1c5610bc957d21dbb4580abfd78c8 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 18 Mar 2026 21:15:17 +0800 Subject: [PATCH 45/62] fix check --- pygmt/clib/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index ed45c9cf763..49736332146 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -1918,7 +1918,7 @@ def virtualfile_in( # noqa: PLR0912 if hasattr(data, "items") and not hasattr(data, "to_frame"): # Dictionary, pandas.DataFrame or xarray.Dataset types. # pandas.Series will be handled below like a 1-D numpy.ndarray. - _data = [array for _, array in data.items()] # noqa: PERF102 + _data = [array for _, array in data.items()] # noqa: PERF102 else: # Python list, tuple, numpy.ndarray, and pandas.Series types _data = np.atleast_2d(np.asanyarray(data).T) @@ -2368,4 +2368,4 @@ def extract_region(self) -> np.ndarray: if status != 0: msg = "Failed to extract region from current figure." raise GMTCLibError(msg) - return region + return region \ No newline at end of file From 64432b24a06b2608ea235221f740551941d8e5c5 Mon Sep 17 00:00:00 2001 From: chuan Date: Wed, 18 Mar 2026 22:03:20 +0800 Subject: [PATCH 46/62] fix typo --- pygmt/clib/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/clib/session.py b/pygmt/clib/session.py index 49736332146..aa8de0fbcb3 100644 --- a/pygmt/clib/session.py +++ b/pygmt/clib/session.py @@ -2368,4 +2368,4 @@ def extract_region(self) -> np.ndarray: if status != 0: msg = "Failed to extract region from current figure." raise GMTCLibError(msg) - return region \ No newline at end of file + return region From efb23e3be81858c9e4d03ddb780ffd0d012fb615 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Thu, 19 Mar 2026 19:09:20 +0800 Subject: [PATCH 47/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 34d1553f573..6c18c4cffa6 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -67,9 +67,7 @@ def _alias_option_N( # noqa: N802 mode = _inside_modes[inside] # type: ignore[index] if edge == inside: mode = mode.upper() - mask_values: str | list[str | float] | None = ( - mode if outside is None else [mode, outside] # type: ignore[assignment] - ) + mask_values = mode if outside is None else [mode, outside] else: # Build the full mask with defaults for any missing values mask_values = [ From 7a165daa31e423a3a05f68165b3014865b3ce565 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Thu, 19 Mar 2026 19:09:30 +0800 Subject: [PATCH 48/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 6c18c4cffa6..b1607e0aeb3 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -167,7 +167,7 @@ def grdmask( [0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0.]]) """ - if spacing is None or region is None: + if kwargs.get("I", spacing) is None or kwargs.get("R", region) is None: raise GMTParameterError(required=["region", "spacing"]) aliasdict = AliasSystem( From 225367984d50119f0c10a8af2e95264b717a25f8 Mon Sep 17 00:00:00 2001 From: chuan Date: Thu, 19 Mar 2026 19:31:51 +0800 Subject: [PATCH 49/62] use GMTValueError instead of ValueError for invalid input to grdmask. --- pygmt/src/grdmask.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index b1607e0aeb3..40a7de86ef2 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -9,7 +9,7 @@ from pygmt._typing import PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session -from pygmt.exceptions import GMTParameterError +from pygmt.exceptions import GMTParameterError, GMTValueError from pygmt.helpers import build_arg_list, fmt_docstring __doctest_skip__ = ["grdmask"] @@ -52,14 +52,12 @@ def _alias_option_N( # noqa: N802 _inside_modes = {"z": "z", "id": "p"} # Validate combinations if edge in _inside_modes and inside != edge: - msg = f"Invalid combination: edge={edge!r} requires inside={edge!r}." - raise GMTParameterError(reason=msg) + msg = f"edge={edge!r} requires inside={edge!r}." + raise GMTValueError(edge, description="edge", reason=msg) if inside in _inside_modes and edge in _inside_modes and inside != edge: - msg = f"Invalid combination: inside={inside!r} and edge={edge!r}. " - raise GMTParameterError( - reason=msg + "When both are special modes, they must be the same." - ) + msg = f"inside={inside!r} and edge={edge!r} must be the same." + raise GMTValueError(edge, description="edge", reason=msg) # Build -N argument if inside in _inside_modes: From b4eb6f999ef53093f4c6c12700cd48a5cbdb8cab Mon Sep 17 00:00:00 2001 From: chuan Date: Thu, 19 Mar 2026 19:37:04 +0800 Subject: [PATCH 50/62] use GMTValueError in test_grdmask --- pygmt/tests/test_grdmask.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/tests/test_grdmask.py b/pygmt/tests/test_grdmask.py index 748d0704b90..991da70cd3e 100644 --- a/pygmt/tests/test_grdmask.py +++ b/pygmt/tests/test_grdmask.py @@ -9,7 +9,7 @@ import xarray as xr from pygmt import grdmask from pygmt.enums import GridRegistration, GridType -from pygmt.exceptions import GMTParameterError +from pygmt.exceptions import GMTParameterError, GMTValueError from pygmt.helpers import GMTTempFile @@ -153,7 +153,7 @@ def test_grdmask_invalid_combination(polygon_data): """ Check that grdmask fails when inside and edge have different special modes. """ - with pytest.raises(GMTParameterError): + with pytest.raises(GMTValueError): grdmask( data=polygon_data, spacing=1, @@ -171,7 +171,7 @@ def test_grdmask_invalid_edge_special_mode(polygon_data, edge, inside): """ Check that special edge modes require the same special inside mode. """ - with pytest.raises(GMTParameterError): + with pytest.raises(GMTValueError): grdmask( data=polygon_data, spacing=1, From 6fb81cf9c2f5a4f71ce85cd49e20faeb56f34f58 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 22 Mar 2026 18:40:28 +0800 Subject: [PATCH 51/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 40a7de86ef2..fb5e5a36ad0 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -72,7 +72,7 @@ def _alias_option_N( # noqa: N802 0 if outside is None else outside, 0 if edge is None else edge, 1 if inside is None else inside, - ] # type: ignore[list-item] + ] return Alias(mask_values, name="mask_values", sep="/", size=(2, 3)) From fe2a54665c4d7adc218ebe352b4daea2d100a780 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 22 Mar 2026 18:40:36 +0800 Subject: [PATCH 52/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index fb5e5a36ad0..fc31b8db0fb 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -66,8 +66,7 @@ def _alias_option_N( # noqa: N802 if edge == inside: mode = mode.upper() mask_values = mode if outside is None else [mode, outside] - else: - # Build the full mask with defaults for any missing values + else: # Build the full mask with defaults for any missing values. mask_values = [ 0 if outside is None else outside, 0 if edge is None else edge, From df24648053ed9195096b2179806f37b05cda4056 Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 22 Mar 2026 18:40:45 +0800 Subject: [PATCH 53/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index fc31b8db0fb..d669bef82a2 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -60,8 +60,7 @@ def _alias_option_N( # noqa: N802 raise GMTValueError(edge, description="edge", reason=msg) # Build -N argument - if inside in _inside_modes: - # Mode: -Nz, -NZ, -Np, or -NP + if inside in _inside_modes: # Mode: -Nz, -NZ, -Np, or -NP mode = _inside_modes[inside] # type: ignore[index] if edge == inside: mode = mode.upper() From 5ace7d1c5f8540ed5ab89538af3cc05242be162d Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 22 Mar 2026 18:40:52 +0800 Subject: [PATCH 54/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index d669bef82a2..1ebad04b06b 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -61,7 +61,7 @@ def _alias_option_N( # noqa: N802 # Build -N argument if inside in _inside_modes: # Mode: -Nz, -NZ, -Np, or -NP - mode = _inside_modes[inside] # type: ignore[index] + mode = _inside_modes[inside] if edge == inside: mode = mode.upper() mask_values = mode if outside is None else [mode, outside] From 151b3a80072b3516c3cd48e79e3054004d7b04dd Mon Sep 17 00:00:00 2001 From: chuan Date: Sun, 22 Mar 2026 18:55:14 +0800 Subject: [PATCH 55/62] use parse --- pygmt/src/grdmask.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 1ebad04b06b..f18d3c405f2 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -29,20 +29,22 @@ def _alias_option_N( # noqa: N802 Examples -------- - >>> _alias_option_N()._value - >>> _alias_option_N(outside=1, edge=2, inside=3)._value + >>> def parse(**kwargs): + ... return AliasSystem(N=_alias_option_N(**kwargs)).get("N") + >>> parse() + >>> parse(outside=1, edge=2, inside=3) '1/2/3' - >>> _alias_option_N(outside=3)._value + >>> parse(outside=3) '3/0/1' - >>> _alias_option_N(inside="z")._value + >>> parse(inside="z") 'z' - >>> _alias_option_N(outside=1, inside="z")._value + >>> parse(outside=1, inside="z") 'z/1' - >>> _alias_option_N(edge="z", inside="z")._value + >>> parse(edge="z", inside="z") 'Z' - >>> _alias_option_N(inside="id")._value + >>> parse(inside="id") 'p' - >>> _alias_option_N(edge="id", inside="id")._value + >>> parse(edge="id", inside="id") 'P' """ # All three are None, return None (GMT uses default 0/0/1) From 682d72c37af0daee7f797d8fb796e9c7ea4e9785 Mon Sep 17 00:00:00 2001 From: chuan Date: Sun, 22 Mar 2026 18:59:58 +0800 Subject: [PATCH 56/62] typecheck --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index f18d3c405f2..885daae7c8a 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -62,7 +62,7 @@ def _alias_option_N( # noqa: N802 raise GMTValueError(edge, description="edge", reason=msg) # Build -N argument - if inside in _inside_modes: # Mode: -Nz, -NZ, -Np, or -NP + if isinstance(inside, str) and inside in _inside_modes: # Mode: -Nz, -NZ, -Np, or -NP mode = _inside_modes[inside] if edge == inside: mode = mode.upper() From cff6ad882a9c31c315fd58a79d165be59252ce32 Mon Sep 17 00:00:00 2001 From: chuan Date: Sun, 22 Mar 2026 19:01:05 +0800 Subject: [PATCH 57/62] make format --- pygmt/src/grdmask.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 885daae7c8a..54877d4302a 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -62,7 +62,9 @@ def _alias_option_N( # noqa: N802 raise GMTValueError(edge, description="edge", reason=msg) # Build -N argument - if isinstance(inside, str) and inside in _inside_modes: # Mode: -Nz, -NZ, -Np, or -NP + if ( + isinstance(inside, str) and inside in _inside_modes + ): # Mode: -Nz, -NZ, -Np, or -NP mode = _inside_modes[inside] if edge == inside: mode = mode.upper() From 1bd98acc2e2a6606d5228cc685c90255ef03571d Mon Sep 17 00:00:00 2001 From: Xingchen He Date: Sun, 22 Mar 2026 19:38:54 +0800 Subject: [PATCH 58/62] Update pygmt/src/grdmask.py Co-authored-by: Dongdong Tian --- pygmt/src/grdmask.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 54877d4302a..2945cb07766 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -54,8 +54,11 @@ def _alias_option_N( # noqa: N802 _inside_modes = {"z": "z", "id": "p"} # Validate combinations if edge in _inside_modes and inside != edge: - msg = f"edge={edge!r} requires inside={edge!r}." - raise GMTValueError(edge, description="edge", reason=msg) + raise GMTValueError( + edge, + description="edge", + reason=f"edge={edge!r} requires inside={edge!r}." + ) if inside in _inside_modes and edge in _inside_modes and inside != edge: msg = f"inside={inside!r} and edge={edge!r} must be the same." From 64c8918083b8f32ef5fd325babc1823b318f6cf9 Mon Sep 17 00:00:00 2001 From: chuan Date: Sun, 22 Mar 2026 19:42:42 +0800 Subject: [PATCH 59/62] remove intermediate variable --- pygmt/src/grdmask.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 2945cb07766..1cc5919ffc5 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -55,14 +55,15 @@ def _alias_option_N( # noqa: N802 # Validate combinations if edge in _inside_modes and inside != edge: raise GMTValueError( - edge, - description="edge", - reason=f"edge={edge!r} requires inside={edge!r}." + edge, description="edge", reason=f"edge={edge!r} requires inside={edge!r}." ) if inside in _inside_modes and edge in _inside_modes and inside != edge: - msg = f"inside={inside!r} and edge={edge!r} must be the same." - raise GMTValueError(edge, description="edge", reason=msg) + raise GMTValueError( + edge, + description="edge", + reason=f"inside={inside!r} and edge={edge!r} must be the same.", + ) # Build -N argument if ( From cef981eec80d1689be6ece7e83048cfe0db86c59 Mon Sep 17 00:00:00 2001 From: chuan Date: Sun, 22 Mar 2026 19:46:02 +0800 Subject: [PATCH 60/62] add type ignore --- pygmt/src/grdmask.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 1cc5919ffc5..1021a4c4d6a 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -66,10 +66,8 @@ def _alias_option_N( # noqa: N802 ) # Build -N argument - if ( - isinstance(inside, str) and inside in _inside_modes - ): # Mode: -Nz, -NZ, -Np, or -NP - mode = _inside_modes[inside] + if inside in _inside_modes: # Mode: -Nz, -NZ, -Np, or -NP + mode = _inside_modes[inside] # type: ignore[index] if edge == inside: mode = mode.upper() mask_values = mode if outside is None else [mode, outside] From 3dec2b580b9a27d596741c7c638bee3084fbb388 Mon Sep 17 00:00:00 2001 From: chuan Date: Sun, 22 Mar 2026 21:49:43 +0800 Subject: [PATCH 61/62] fix condition check --- pygmt/src/grdmask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 1021a4c4d6a..3e8a0d480b2 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -58,7 +58,7 @@ def _alias_option_N( # noqa: N802 edge, description="edge", reason=f"edge={edge!r} requires inside={edge!r}." ) - if inside in _inside_modes and edge in _inside_modes and inside != edge: + if inside in _inside_modes and edge is not None and edge != inside: raise GMTValueError( edge, description="edge", From 32dff85b0c09bab7d7c97c189b45e9a417dd846d Mon Sep 17 00:00:00 2001 From: chuan Date: Tue, 24 Mar 2026 12:25:44 +0800 Subject: [PATCH 62/62] add doctests --- pygmt/src/grdmask.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pygmt/src/grdmask.py b/pygmt/src/grdmask.py index 3e8a0d480b2..5589721ff66 100644 --- a/pygmt/src/grdmask.py +++ b/pygmt/src/grdmask.py @@ -46,6 +46,14 @@ def _alias_option_N( # noqa: N802 'p' >>> parse(edge="id", inside="id") 'P' + >>> parse(edge="z") + Traceback (most recent call last): + ... + pygmt.exceptions.GMTValueError: Invalid edge: 'z'. edge='z' requires inside='z'. + >>> parse(inside="z", edge="id") + Traceback (most recent call last): + ... + pygmt.exceptions.GMTValueError: Invalid edge: 'id'. edge='id' requires inside='id'. """ # All three are None, return None (GMT uses default 0/0/1) if all(v is None for v in (outside, inside, edge)):