Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
80 changes: 80 additions & 0 deletions examples/gallery/images/grdpolygon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
Clipping grid to a complex polygon
==================================
Comment thread
kshitijaucharmal marked this conversation as resolved.
Outdated

The :func:`pygmt.grdcut` function allows you to extract a subregion from a
grid. In this example we use a complex polygon (GeoDataFrame or GMT ASCII file)
to crop the grid, invert it, or do both.
Comment thread
kshitijaucharmal marked this conversation as resolved.
Outdated
"""

# %%
import geopandas as gpd
import pygmt
from shapely.geometry import Polygon

fig = pygmt.Figure()

# Define region of interest around Iceland
region = [-28, -10, 62, 68]
Comment thread
kshitijaucharmal marked this conversation as resolved.
Outdated

# Load sample grid (3 arc-minutes global relief) in target area
grid = pygmt.datasets.load_earth_relief(resolution="03m", region=region)

# Create a more complex polygon (irregular shape) around a smaller ROI
complex_poly = Polygon(
[
(-26, 63),
(-23, 63.5),
(-20, 64),
(-18, 65),
(-19, 66),
(-22, 66.5),
(-25, 66),
(-27, 65),
(-26, 63),
]
)
gdf = gpd.GeoDataFrame({"geometry": [complex_poly]}, crs="EPSG:4326")
Comment thread
kshitijaucharmal marked this conversation as resolved.
Outdated

# Original grid
fig.basemap(
region=region,
projection="M12c",
frame=["WSne+toriginal grid", "xa5f1", "ya2f1"],
)
fig.grdimage(grid=grid, cmap="oleron")

# Cropped grid
fig.shift_origin(xshift="w+0.5c")
cropped_grid = pygmt.grdcut(grid=grid, polygon=gdf, crop=True)
fig.basemap(
region=region,
projection="M12c",
frame=["WSne+tcropped", "xa5f1", "ya2f1"],
)
fig.grdimage(grid=cropped_grid, cmap="oleron")

# Inverted grid
fig.shift_origin(xshift="w+0.5c")
inverted_grid = pygmt.grdcut(grid=grid, polygon=gdf, invert=True)
fig.basemap(
region=region,
projection="M12c",
frame=["WSne+tinverted", "xa5f1", "ya2f1"],
)
fig.grdimage(grid=inverted_grid, cmap="oleron")

# Cropped + inverted grid
fig.shift_origin(xshift="w+0.5c")
cropped_inverted_grid = pygmt.grdcut(grid=grid, polygon=gdf, crop=True, invert=True)
fig.basemap(
region=region,
projection="M12c",
frame=["WSne+tcropped and inverted", "xa5f1", "ya2f1"],
)
fig.grdimage(grid=cropped_inverted_grid, cmap="oleron")

# Shared colorbar
fig.colorbar(frame=["x+lElevation", "y+lm"], position="JMR+o0.5c/0c+w8c")

fig.show()
30 changes: 28 additions & 2 deletions pygmt/src/grdcut.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

from collections.abc import Sequence
from contextlib import ExitStack
from typing import Literal

import xarray as xr
Expand All @@ -14,14 +15,15 @@
build_arg_list,
data_kind,
fmt_docstring,
tempfile_from_geojson,
Comment thread
seisman marked this conversation as resolved.
Outdated
use_alias,
)

__doctest_skip__ = ["grdcut"]


@fmt_docstring
@use_alias(N="extend", S="circ_subregion", Z="z_subregion", f="coltypes")
@use_alias(N="extend", S="circ_subregion", Z="z_subregion", f="coltypes", F="polygon")
Comment thread
kshitijaucharmal marked this conversation as resolved.
Outdated
def grdcut(
grid: PathLike | xr.DataArray,
kind: Literal["grid", "image"] = "grid",
Expand All @@ -30,6 +32,8 @@ def grdcut(
region: Sequence[float | str] | str | None = None,
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
Comment thread
kshitijaucharmal marked this conversation as resolved.
crop: bool = False,
invert: bool = False,
**kwargs,
) -> xr.DataArray | None:
r"""
Expand Down Expand Up @@ -86,6 +90,17 @@ def grdcut(
NaNs, append **+N** to strip off such columns before (optionally)
considering the range of the core subset for further reduction of the
area.
polygon : str, geopandas.GeoDataFrame, or shapely.geometry
Extract a subregion using a polygon. Can be either:
- A GMT ASCII polygon file (`.gmt`)
- A geopandas.GeoDataFrame (must have CRS EPSG:4326)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure if that specific CRS is necessary, of if a CRS is needed actually. Other input types (ASCII file, shapely.geometry.Polygon) don't even have a CRS.

- A shapely.geometry.Polygon or MultiPolygon
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you add a unit test to confirm that MultiPolygon does work?

The polygon can have holes or multiple rings.
Optional modifiers:
- crop : bool
If True, crop the output grid region to the bounding box of the polygon.
- invert : bool
If True, invert the selection, setting all nodes inside the polygon to NaN.

{verbose}
{coltypes}
Expand Down Expand Up @@ -130,12 +145,23 @@ def grdcut(
)
aliasdict.merge(kwargs)

with Session() as lib:
with Session() as lib, ExitStack() as stack:
Comment thread
seisman marked this conversation as resolved.
Outdated
with (
lib.virtualfile_in(check_kind="raster", data=grid) as vingrd,
Comment thread
seisman marked this conversation as resolved.
lib.virtualfile_out(kind=outkind, fname=outgrid) as voutgrd,
):
aliasdict["G"] = voutgrd

if "F" in kwargs and kwargs["F"] is not None:
polygon_input = kwargs["F"]
modifiers = ("+c" * crop) + ("+i" * invert)
Comment thread
kshitijaucharmal marked this conversation as resolved.
Outdated

if not isinstance(polygon_input, (str, bytes)):
Comment thread
kshitijaucharmal marked this conversation as resolved.
Outdated
tmpfile = stack.enter_context(tempfile_from_geojson(polygon_input))
aliasdict["F"] = tmpfile + modifiers
else:
aliasdict["F"] = str(polygon_input) + modifiers

lib.call_module(
module="grdcut", args=build_arg_list(aliasdict, infile=vingrd)
)
Expand Down
Loading