Skip to content

POC: Figure.clip: Clip paths by polygons, land or water #3878

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions doc/_templates/autosummary/class.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
.. rubric:: Attributes

{% for item in attributes %}
.. autoproperty::
{{ objname }}.{{ item }}
.. autoproperty:: {{ objname }}.{{ item }}
:no-index:
{% endfor %}
{% endif %}

Expand Down
2 changes: 2 additions & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Plotting map elements
:toctree: generated

Figure.basemap
Figure.clip
Figure.coast
Figure.colorbar
Figure.hlines
Expand Down Expand Up @@ -226,6 +227,7 @@ Miscellaneous

which
show_versions
src.ClipAccessor

Datasets
--------
Expand Down
10 changes: 10 additions & 0 deletions pygmt/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Literal, overload

from pygmt._typing import PathLike
from pygmt.src import ClipAccessor

try:
import IPython
Expand Down Expand Up @@ -137,6 +138,15 @@ def region(self) -> np.ndarray:
wesn = lib.extract_region()
return wesn

@property
def clip(self) -> ClipAccessor:
"""
Set up a clipping path and only plot data inside/outside the clipped path.

See :class:`pygmt.src.clip.ClipAccessor <ClipAccessor>` for the usage.
"""
return ClipAccessor(self)

def savefig(
self,
fname: PathLike,
Expand Down
1 change: 1 addition & 0 deletions pygmt/src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pygmt.src.basemap import basemap
from pygmt.src.binstats import binstats
from pygmt.src.blockm import blockmean, blockmedian, blockmode
from pygmt.src.clip import ClipAccessor
from pygmt.src.coast import coast
from pygmt.src.colorbar import colorbar
from pygmt.src.config import config
Expand Down
267 changes: 267 additions & 0 deletions pygmt/src/clip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
"""
Clip.
"""

from collections.abc import Sequence

from pygmt.clib import Session
from pygmt.helpers import build_arg_list, is_nonstr_iter


class _ClipContext:
"""
Base class for the clip context manager.
"""

def __init__(self, figure, data=None, **kwargs):
self._figure = figure # The parent Figure object.
self._data = data
self._kwargs = kwargs

def __enter__(self):
self._figure._preprocess() # Activate the current figure.
self._activate()

def __exit__(self, exc_type, exc_val, exc_tb):
self._figure._preprocess() # Activate the current figure.
self._deactivate()

def _activate(self):
"""
Activate clipping.
"""
raise NotImplementedError

Check warning on line 33 in pygmt/src/clip.py

View check run for this annotation

Codecov / codecov/patch

pygmt/src/clip.py#L33

Added line #L33 was not covered by tests

def _deactivate(self):
"""
Deactivate clipping.
"""
raise NotImplementedError

Check warning on line 39 in pygmt/src/clip.py

View check run for this annotation

Codecov / codecov/patch

pygmt/src/clip.py#L39

Added line #L39 was not covered by tests


class _ClipLand(_ClipContext):
"""
Clip the land area (i.e., "dry" areas).
"""

def _activate(self):
self._figure.coast(land=True, **self._kwargs)

def _deactivate(self):
self._figure.coast(Q=True)


class _ClipWater(_ClipContext):
"""
Clip the water areas (i.e., "wet" areas such as oceans and lakes).
"""

def _activate(self):
self._figure.coast(water=True, **self._kwargs)

def _deactivate(self):
self._figure.coast(Q=True)


class _ClipDcw(_ClipContext):
"""
Clip based on the Digital Chart of the World.
"""

def _activate(self):
self._figure.coast(**self._kwargs)

def _deactivate(self):
self._figure.coast(Q=True)


class _ClipSolar(_ClipContext):
"""
Clip the data to the solar terminator.
"""

def _activate(self):
self._figure.solar(fill=True, **self._kwargs)

def _deactivate(self):
with Session() as lib:
lib.call_module(module="clip", args=build_arg_list({"C": True}))


class _ClipPolygon(_ClipContext):
"""
Clip polygonal paths.
"""

def _activate(self):
with Session() as lib:
with lib.virtualfile_in(data=self._data) as vintbl:
lib.call_module(
module="clip",
args=build_arg_list(self._kwargs, infile=vintbl),
)

def _deactivate(self):
with Session() as lib:
lib.call_module(module="clip", args=build_arg_list({"C": True}))


class _ClipMask(_ClipContext):
"""
Clip the data to a mask.
"""

def _activate(self):
with Session() as lib:
with lib.virtualfile_in(data=self._data) as vintbl:
lib.call_module(

Check warning on line 117 in pygmt/src/clip.py

View check run for this annotation

Codecov / codecov/patch

pygmt/src/clip.py#L115-L117

Added lines #L115 - L117 were not covered by tests
module="mask",
args=build_arg_list(self._kwargs, infile=vintbl),
)

def _deactivate(self):
with Session() as lib:
lib.call_module(module="mask", args=build_arg_list({"C": True}))

Check warning on line 124 in pygmt/src/clip.py

View check run for this annotation

Codecov / codecov/patch

pygmt/src/clip.py#L123-L124

Added lines #L123 - L124 were not covered by tests


class ClipAccessor:
"""
Accessor for the clip methods.
"""

def __init__(self, fig):
self._fig = fig # The parent Figure object.

def land(self, **kwargs):
"""
Clip the land area (i.e., "dry" areas).

Must be used as a context manager. Any plotting operations within the context
manager will be clipped to the land areas.

Parameters
----------
kwargs
Additional arguments passed to :meth:`pygmt.Figure.coast`.

Examples
--------
>>> from pygmt import Figure
>>> from pygmt.datasets import load_earth_relief
>>>
>>> grid = load_earth_relief()
>>> fig = Figure()
>>> fig.basemap(region="g", projection="W15c", frame=True)
>>> with fig.clip.land():
... fig.grdimage(grid, cmap="geo")
>>> fig.show()
"""
return _ClipLand(self._fig, **kwargs)

def water(self, **kwargs):
"""
Clip the water areas (i.e., "wet" areas such as oceans and lakes).

Must be used as a context manager. Any plotting operations within the context
manager will be clipped to the water areas.

Parameters
----------
kwargs
Additional arguments passed to :meth:`pygmt.Figure.coast`.

Examples
--------
>>> from pygmt import Figure
>>> from pygmt.datasets import load_earth_relief
>>>
>>> grid = load_earth_relief()
>>> fig = Figure()
>>> fig.basemap(region="g", projection="W15c", frame=True)
>>> with fig.clip.water():
... fig.grdimage(grid, cmap="geo")
>>> fig.show()
"""
return _ClipWater(self._fig, **kwargs)

def polygon(self, x, y, **kwargs):
"""
Clip polygonal paths.

Parameters
----------
x/y
Coordinates of polygon.
kwargs
Additional arguments passed to GMT's ``clip`` module.

Examples
--------
>>> from pygmt import Figure
>>> from pygmt.datasets import load_earth_relief
>>>
>>> grid = load_earth_relief()
>>> fig = Figure()
>>> fig.basemap(region="g", projection="W15c", frame=True)
>>> with fig.clip.polygon(x=[-10, 10, 10, -10], y=[-10, -10, 10, 10]):
... fig.grdimage(grid, cmap="geo")
>>> fig.show()
"""
return _ClipPolygon(self._fig, data={"x": x, "y": y}, **kwargs)

def dcw(self, code: str | Sequence[str], **kwargs):
"""
Clip based on the Digital Chart of the World.

Examples
--------
>>> from pygmt import Figure
>>> from pygmt.datasets import load_earth_relief
>>>
>>> grid = load_earth_relief()
>>> fig = Figure()
>>> fig.basemap(region="g", projection="W15c", frame=True)
>>> with fig.clip.dcw(code="JP"):
... fig.grdimage(grid, cmap="geo")
>>> fig.show()
"""
_code = ",".join(code) if is_nonstr_iter(code) else code
return _ClipDcw(self._fig, dcw=f"{_code}+c", **kwargs)

def solar(self, **kwargs):
"""
Clip the data to the solar terminator.

Examples
--------
>>> from pygmt import Figure
>>> from pygmt.datasets import load_earth_relief
>>>
>>> grid = load_earth_relief()
>>> fig = Figure()
>>> fig.basemap(region="g", projection="W15c", frame=True)
>>> with fig.clip.solar(terminator="civil"):
... fig.grdimage(grid, cmap="geo")
>>> fig.show()
"""
return _ClipSolar(self._fig, **kwargs)

def mask(self, x, y, spacing, radius=None):
"""
Clip the data to a mask.

Examples
--------
>>> from pygmt import Figure
>>> from pygmt.datasets import load_earth_relief
>>>
>>> grid = load_earth_relief()
>>> fig = Figure()
>>> fig.basemap(region="g", projection="Q15c", frame=True)
>>> with fig.clip.mask(
... x=[180] * 16, y=np.arange(-80, 80, 10), spacing="30m", radius="5d"
... ):
... fig.grdimage(grid, cmap="geo")
>>> fig.show()
"""
return _ClipMask(self._fig, data={"x": x, "y": y}, I=spacing, S=radius)

Check warning on line 267 in pygmt/src/clip.py

View check run for this annotation

Codecov / codecov/patch

pygmt/src/clip.py#L267

Added line #L267 was not covered by tests
Loading