Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
dd3abe1
add grdmask for required arguments
Chuan1937 Feb 25, 2026
67da3c5
Merge branch 'main' into grdmask1
Chuan1937 Feb 25, 2026
caedc1a
add grdmask doc api
Chuan1937 Feb 25, 2026
44d969c
Update pygmt/src/grdmask.py
Chuan1937 Feb 25, 2026
fdee109
Update pygmt/src/grdmask.py
Chuan1937 Feb 25, 2026
7f0ddff
change grdmask category
Chuan1937 Feb 25, 2026
68123cf
Update pygmt/src/grdmask.py
Chuan1937 Feb 25, 2026
e70ca8c
show the value of mask
Chuan1937 Feb 25, 2026
1b0e057
change parameter name from mask_values to outside, edge, inside.
Chuan1937 Feb 26, 2026
7a166e9
reset the N parameter
Chuan1937 Feb 26, 2026
da0cc55
set a private function
Chuan1937 Mar 2, 2026
d9b3478
Merge branch 'main' into grdmask1
Chuan1937 Mar 2, 2026
6302abe
Update pygmt/src/grdmask.py
Chuan1937 Mar 2, 2026
1d6d753
Update pygmt/src/grdmask.py
Chuan1937 Mar 2, 2026
d4293de
delete docs
Chuan1937 Mar 2, 2026
a0ded64
fix type error
Chuan1937 Mar 2, 2026
871bcff
Update pygmt/src/grdmask.py
Chuan1937 Mar 3, 2026
a0519e6
Update pygmt/src/grdmask.py
Chuan1937 Mar 3, 2026
7d98d67
simplified code logic
Chuan1937 Mar 3, 2026
ad23794
Update pygmt/src/grdmask.py
Chuan1937 Mar 3, 2026
8e2a998
Update pygmt/src/grdmask.py
Chuan1937 Mar 3, 2026
21b543d
check output
Chuan1937 Mar 3, 2026
115733d
Merge branch 'main' into grdmask1
Chuan1937 Mar 3, 2026
68e654f
Merge branch 'main' into grdmask1
Chuan1937 Mar 4, 2026
53b3346
Update pygmt/src/grdmask.py
Chuan1937 Mar 4, 2026
8e9a3b6
Update pygmt/src/grdmask.py
Chuan1937 Mar 4, 2026
2a19936
Update pygmt/src/grdmask.py
Chuan1937 Mar 4, 2026
09bf961
Merge branch 'main' into grdmask1
Chuan1937 Mar 4, 2026
813b8e2
fix format
Chuan1937 Mar 4, 2026
1eb9667
fix static type error
Chuan1937 Mar 4, 2026
a1ba800
Update pygmt/src/grdmask.py
Chuan1937 Mar 4, 2026
05e9635
Update pygmt/src/grdmask.py
Chuan1937 Mar 4, 2026
f9a87fe
Update pygmt/src/grdmask.py
Chuan1937 Mar 4, 2026
b63a8de
Update pygmt/src/grdmask.py
Chuan1937 Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ Operations on tabular data
blockmedian
blockmode
filter1d
grdmask
nearneighbor
project
select
Expand Down
1 change: 1 addition & 0 deletions pygmt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
grdhisteq,
grdinfo,
grdlandmask,
grdmask,
grdpaste,
grdproject,
grdsample,
Expand Down
1 change: 1 addition & 0 deletions pygmt/src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
172 changes: 172 additions & 0 deletions pygmt/src/grdmask.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
"""
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,
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,
) -> xr.DataArray | None:
"""
Create mask grid from polygons or point coverage.

Reads one or more files (or standard input) containing polygon or data point
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no standard in in Python.

Suggested change
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

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**

.. hlist::
:columns: 3

- G = outgrid
- I = spacing
- N = outside/edge/inside
- 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:
Comment on lines +108 to +109
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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)
$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.
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

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])
>>> 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"])

# 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,
)
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)
123 changes: 123 additions & 0 deletions pygmt/tests/test_grdmask.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""
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 outside, edge, inside values.
"""
result = grdmask(
data=polygon_data,
spacing=1,
region=[125, 130, 30, 35],
outside=10,
edge=20,
inside=30,
)
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]]))


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",
)
Loading