diff --git a/doc/_templates/autosummary/class.rst b/doc/_templates/autosummary/class.rst index 57a35f189a2..c3d36b90a43 100644 --- a/doc/_templates/autosummary/class.rst +++ b/doc/_templates/autosummary/class.rst @@ -8,8 +8,8 @@ .. rubric:: Attributes {% for item in attributes %} -.. autoproperty:: - {{ objname }}.{{ item }} +.. autoproperty:: {{ objname }}.{{ item }} + :no-index: {% endfor %} {% endif %} diff --git a/doc/api/index.rst b/doc/api/index.rst index 3d299cce71f..9916a9c9c80 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -25,6 +25,7 @@ Plotting map elements :toctree: generated Figure.basemap + Figure.clip Figure.coast Figure.colorbar Figure.hlines @@ -218,6 +219,7 @@ Miscellaneous which show_versions + src.ClipAccessor Datasets -------- diff --git a/pygmt/figure.py b/pygmt/figure.py index f9c8478747d..31eb6f1206c 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -9,6 +9,7 @@ from typing import Literal, overload from pygmt._typing import PathLike +from pygmt.src import ClipAccessor try: import IPython @@ -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 ` for the usage. + """ + return ClipAccessor(self) + def savefig( self, fname: PathLike, diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py index 8905124f917..49a569a94ac 100644 --- a/pygmt/src/__init__.py +++ b/pygmt/src/__init__.py @@ -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 diff --git a/pygmt/src/clip.py b/pygmt/src/clip.py new file mode 100644 index 00000000000..3a7fb900633 --- /dev/null +++ b/pygmt/src/clip.py @@ -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 + + def _deactivate(self): + """ + Deactivate clipping. + """ + raise NotImplementedError + + +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( + 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})) + + +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)