diff --git a/examples/gallery/3d_plots/grdview_surface.py b/examples/gallery/3d_plots/grdview_surface.py index 9b5bbdede1d..6253087f064 100644 --- a/examples/gallery/3d_plots/grdview_surface.py +++ b/examples/gallery/3d_plots/grdview_surface.py @@ -46,12 +46,11 @@ def ackley(x, y): SCALE = 0.5 # in centimeters fig.grdview( data, - # Set annotations and gridlines in steps of five, and - # tick marks in steps of one + # Set annotations and gridlines in steps of five, and tick marks in steps of one frame=["a5f1g5", "za5f1g5"], projection=f"x{SCALE}c", zscale=f"{SCALE}c", - surftype="s", + surftype="surface", cmap="roma", perspective=[135, 30], # Azimuth southeast (135°), at elevation 30° shading="+a45", diff --git a/examples/tutorials/advanced/3d_perspective_image.py b/examples/tutorials/advanced/3d_perspective_image.py index c380a8bf5b6..e8223e26218 100644 --- a/examples/tutorials/advanced/3d_perspective_image.py +++ b/examples/tutorials/advanced/3d_perspective_image.py @@ -47,10 +47,8 @@ frame=["xa", "yaf", "WSnE"], projection="M15c", zsize="1.5c", - # Set the surftype to "surface" - surftype="s", - # Set the CPT to "geo" - cmap="geo", + surftype="surface", + cmap="geo", # Set the CPT to "geo" ) fig.show() @@ -65,7 +63,7 @@ frame=["xa", "yaf", "WSnE"], projection="M15c", zsize="1.5c", - surftype="s", + surftype="surface", cmap="geo", plane=1000, # Set the plane elevation to 1,000 meters facade_fill="gray", # Color the facade in "gray" @@ -88,7 +86,7 @@ frame=["xaf", "yaf", "WSnE"], projection="M15c", zsize="1.5c", - surftype="s", + surftype="surface", cmap="geo", plane=1000, facade_fill="gray", diff --git a/examples/tutorials/advanced/draping_on_3d_surface.py b/examples/tutorials/advanced/draping_on_3d_surface.py index 21131e79ec7..215e374dc21 100644 --- a/examples/tutorials/advanced/draping_on_3d_surface.py +++ b/examples/tutorials/advanced/draping_on_3d_surface.py @@ -58,7 +58,7 @@ grid=grd_relief, # Use elevation grid for z values drape_grid=grd_age, # Use crustal age grid for color-coding cmap=True, # Use colormap created for the crustal age - surftype="i", # Create an image plot + surftype="image", # Create an image plot # Use an illumination from the azimuthal directions 0° (north) and 270° # (west) with a normalization via a cumulative Laplace distribution for # the shading @@ -122,7 +122,7 @@ grid=grd_relief, # Use elevation grid for z values drape_grid=drape_grid, # Drape image grid for the EU flag on top cmap=True, # Use colormap defined for the EU flag - surftype="i", # Create an image plot + surftype="image", # Create an image plot # Use an illumination from the azimuthal directions 0° (north) and 270° (west) with # a normalization via a cumulative Laplace distribution for the shading shading="+a0/270+ne0.6", diff --git a/pygmt/src/grdview.py b/pygmt/src/grdview.py index ebf820508fb..df42c5f4a65 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -10,6 +10,7 @@ from pygmt._typing import PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session, __gmt_version__ +from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import build_arg_list, deprecate_parameter, fmt_docstring, use_alias from pygmt.src.grdinfo import grdinfo @@ -21,18 +22,19 @@ @deprecate_parameter("facadepen", "facade_pen", "v0.18.0", remove_version="v0.20.0") @deprecate_parameter("meshpen", "mesh_pen", "v0.18.0", remove_version="v0.20.0") @deprecate_parameter("drapegrid", "drape_grid", "v0.18.0", remove_version="v0.20.0") -@use_alias( - C="cmap", - G="drape_grid", - Q="surftype", - I="shading", - f="coltypes", - n="interpolation", -) +@use_alias(C="cmap", G="drape_grid", I="shading", f="coltypes", n="interpolation") def grdview( # noqa: PLR0913 self, grid: PathLike | xr.DataArray, + surftype: Literal[ + "mesh", "surface", "surface+mesh", "image", "waterfall_x", "waterfall_y" + ] + | None = None, + dpi: int | None = None, + nan_transparent: bool = False, + monochrome: bool = False, contour_pen: str | None = None, + mesh_fill: float | None = None, mesh_pen: str | None = None, plane: float | bool = False, facade_fill: str | None = None, @@ -40,13 +42,13 @@ def grdview( # noqa: PLR0913 projection: str | None = None, zscale: float | str | None = None, zsize: float | str | None = None, - frame: str | Sequence[str] | bool = False, region: Sequence[float | str] | str | None = None, + frame: str | Sequence[str] | bool = False, verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] | bool = False, panel: int | Sequence[int] | bool = False, - transparency: float | None = None, perspective: float | Sequence[float] | str | bool = False, + transparency: float | None = None, **kwargs, ): r""" @@ -67,6 +69,7 @@ def grdview( # noqa: PLR0913 - JZ = zsize - N = plane, facade_fill - R = region + - Q = surftype, dpi, mesh_fill, nan_transparent, monochrome - V = verbose - Wc = contour_pen - Wf = facade_pen @@ -78,15 +81,6 @@ def grdview( # noqa: PLR0913 Parameters ---------- $grid - region : str or list - *xmin/xmax/ymin/ymax*\ [**+r**][**+u**\ *unit*]. - Specify the :doc:`region ` of interest. When used - with ``perspective``, optionally append */zmin/zmax* to indicate the range to - use for the 3-D axes [Default is the region given by the input grid]. - $projection - zscale/zsize - Set z-axis scaling or z-axis size. - $frame cmap : str The name of the color palette table to use. drape_grid : str or :class:`xarray.DataArray` @@ -95,24 +89,30 @@ def grdview( # noqa: PLR0913 Note that ``zscale`` and ``plane`` always refer to ``grid``. ``drape_grid`` only provides the information pertaining to colors, which (if ``drape_grid`` is a grid) will be looked-up via the CPT (see ``cmap``). - surftype : str - Specify cover type of the grid. Select one of following settings: - - - **m**: mesh plot [Default]. - - **mx** or **my**: waterfall plots (row or column profiles). - - **s**: surface plot, and optionally append **m** to have mesh lines drawn on - top of the surface. - - **i**: image plot. - - **c**: Same as **i** but will make nodes with z = NaN transparent. - - For any of these choices, you may force a monochrome image by appending the - modifier **+m**. + surftype + Specify surface type for the grid. Valid values are: + + - ``"mesh"``: mesh plot [Default]. + - ``"surface``: surface plot. + - ``"surface+mesh"``: surface plot with mesh lines drawn on top of the surface. + - ``"image"``: image plot. + - ``"waterfall_x"``/``"waterfall_y"``: waterfall plots (row or column profiles). + dpi + Effective dots-per-unit resolution for the rasterization for image plots (i.e., + ``surftype="image"``) [Default is :gmt-term:`GMT_GRAPHICS_DPU`] + nan_transparent + Make grid nodes with z = NaN transparent, using the color-masking feature in + PostScript Level 3. Only applies when ``surftype="image"``. + monochrome + Force conversion to monochrome image using the (television) YIQ transformation. contour_pen Draw contour lines on top of surface or mesh (not image). Append pen attributes used for the contours. mesh_pen - Set the pen attributes used for the mesh. You must also select ``surftype`` of - **m** or **sm** for meshlines to be drawn. + Set the pen attributes used for the mesh. Need to set ``surftype`` to + ``"mesh"``, or ``"surface+mesh"`` to draw meshlines. + mesh_fill + Set the mesh fill in mesh plot or waterfall plots [Default is white]. plane Draw a plane at the specified z-level. If ``True``, defaults to the minimum value in the grid. However, if ``region`` was used to set *zmin/zmax* then @@ -133,6 +133,15 @@ def grdview( # noqa: PLR0913 **+m**\ *ambient* to specify azimuth, intensity, and ambient arguments for that function, or just give **+d** to select the default arguments [Default is ``"+a-45+nt1+m0"``]. + $projection + zscale/zsize + Set z-axis scaling or z-axis size. + region : str or list + *xmin/xmax/ymin/ymax*\ [**+r**][**+u**\ *unit*]. + Specify the :doc:`region ` of interest. When used + with ``perspective``, optionally append */zmin/zmax* to indicate the range to + use for the 3-D axes [Default is the region given by the input grid]. + $frame $verbose $panel $coltypes @@ -165,7 +174,7 @@ def grdview( # noqa: PLR0913 ... # Set the vertical scale (z-axis) to 2 cm ... zsize="2c", ... # Set "surface plot" to color the surface via a CPT - ... surftype="s", + ... surftype="surface", ... # Specify CPT to "geo" ... cmap="geo", ... ) @@ -174,6 +183,40 @@ def grdview( # noqa: PLR0913 """ self._activate_figure() + if dpi is not None and surftype != "image": + msg = "Parameter 'dpi' can only be used when 'surftype' is 'image'." + raise GMTInvalidInput(msg) + if nan_transparent and surftype != "image": + msg = "Parameter 'nan_transparent' can only be used when 'surftype' is 'image'." + raise GMTInvalidInput(msg) + if mesh_fill is not None and surftype not in {"mesh", "waterfall_x", "waterfall_y"}: + msg = ( + "Parameter 'mesh_fill' can only be used when 'surftype' is 'mesh', " + "'waterfall_x', or 'waterfall_y'." + ) + raise GMTInvalidInput(msg) + + _surftype_mapping = { + "surface": "s", + "mesh": "m", + "surface+mesh": "sm", + "image": "c" if nan_transparent is True else "i", + "waterfall_x": "mx", + "waterfall_y": "my", + } + + # Previously, 'surftype' was aliased to Q. + _old_surftype_syntax = surftype is not None and surftype not in _surftype_mapping + + if _old_surftype_syntax and any( + v not in {None, False} for v in (dpi, mesh_fill, monochrome, nan_transparent) + ): + msg = ( + "Parameter 'surftype' is given with a raw GMT command string, and conflicts " + "with parameters 'dpi', 'mesh_fill', 'monochrome', or 'nan_transparent'." + ) + raise GMTInvalidInput(msg) + # Enable 'plane' if 'facade_fill' or 'facade_pen' are set if plane is False and (facade_fill is not None or facade_pen is not None): plane = True @@ -193,6 +236,16 @@ def grdview( # noqa: PLR0913 aliasdict = AliasSystem( Jz=Alias(zscale, name="zscale"), JZ=Alias(zsize, name="zsize"), + Q=[ + Alias( + surftype, + name="surftype", + mapping=_surftype_mapping if not _old_surftype_syntax else None, + ), + Alias(dpi, name="dpi"), + Alias(mesh_fill, name="mesh_fill"), + Alias(monochrome, name="monochrome", prefix="+m"), + ], N=[ Alias(plane, name="plane"), Alias(facade_fill, name="facade_fill", prefix="+g"), diff --git a/pygmt/tests/baseline/test_grdview_image_dpi.png.dvc b/pygmt/tests/baseline/test_grdview_image_dpi.png.dvc new file mode 100644 index 00000000000..8864be6e72f --- /dev/null +++ b/pygmt/tests/baseline/test_grdview_image_dpi.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: d384910b17de5a2a842cd2625761c821 + size: 224724 + hash: md5 + path: test_grdview_image_dpi.png diff --git a/pygmt/tests/baseline/test_grdview_mesh_pen_and_mesh_fill.png.dvc b/pygmt/tests/baseline/test_grdview_mesh_pen_and_mesh_fill.png.dvc new file mode 100644 index 00000000000..e3c16423c6d --- /dev/null +++ b/pygmt/tests/baseline/test_grdview_mesh_pen_and_mesh_fill.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 9158bd6308a9bb59fdcaf56e406954b3 + size: 55008 + hash: md5 + path: test_grdview_mesh_pen_and_mesh_fill.png diff --git a/pygmt/tests/baseline/test_grdview_surftype.png.dvc b/pygmt/tests/baseline/test_grdview_surftype.png.dvc new file mode 100644 index 00000000000..e76f60713a3 --- /dev/null +++ b/pygmt/tests/baseline/test_grdview_surftype.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 3c4e5e2aba5d541909ad94e4ec203186 + size: 225575 + hash: md5 + path: test_grdview_surftype.png diff --git a/pygmt/tests/baseline/test_grdview_with_cmap_for_image_plot.png.dvc b/pygmt/tests/baseline/test_grdview_with_cmap_for_image_plot.png.dvc deleted file mode 100644 index b85a4ddb08c..00000000000 --- a/pygmt/tests/baseline/test_grdview_with_cmap_for_image_plot.png.dvc +++ /dev/null @@ -1,4 +0,0 @@ -outs: -- md5: 22264dc7db5565465d47092a139377cc - size: 267613 - path: test_grdview_with_cmap_for_image_plot.png diff --git a/pygmt/tests/baseline/test_grdview_with_cmap_for_surface_monochrome_plot.png.dvc b/pygmt/tests/baseline/test_grdview_with_cmap_for_surface_monochrome_plot.png.dvc deleted file mode 100644 index 3f3cbac6d9a..00000000000 --- a/pygmt/tests/baseline/test_grdview_with_cmap_for_surface_monochrome_plot.png.dvc +++ /dev/null @@ -1,4 +0,0 @@ -outs: -- md5: 152085fd81de3ad78b5c7ffb3cf0d080 - size: 95530 - path: test_grdview_with_cmap_for_surface_monochrome_plot.png diff --git a/pygmt/tests/test_grdview.py b/pygmt/tests/test_grdview.py index 021495b9de8..b3dbf5e6bb7 100644 --- a/pygmt/tests/test_grdview.py +++ b/pygmt/tests/test_grdview.py @@ -4,7 +4,7 @@ import pytest from pygmt import Figure, grdcut -from pygmt.exceptions import GMTTypeError +from pygmt.exceptions import GMTInvalidInput, GMTTypeError from pygmt.helpers import GMTTempFile from pygmt.helpers.testing import load_static_earth_relief @@ -53,6 +53,102 @@ def test_grdview_grid_dataarray(xrgrid): return fig +@pytest.mark.mpl_image_compare +def test_grdview_surftype(grid): + """ + Test grdview with different surftype values. + """ + args = { + "grid": grid, + "projection": "M?", + "frame": True, + "panel": True, + "perspective": (-150, 25), + "zsize": "1.0c", + } + + fig = Figure() + with fig.subplot(nrows=2, ncols=3, subsize=("5c", "5c"), margins=(0, -0.5)): + for surftype in [ + "mesh", + "surface", + "surface+mesh", + "image", + "waterfall_x", + "waterfall_y", + ]: + if surftype in {"surface", "surface+mesh"}: + fig.grdview(surftype=surftype, cmap="SCM/oleron", **args) + else: + fig.grdview(surftype=surftype, **args) + return fig + + +@pytest.mark.mpl_image_compare +def test_grdview_image_dpi(grid): + """ + Test grdview with surftype="image" and dpi parameter. + """ + fig = Figure() + for dpi in [None, 10, 100]: + fig.grdview( + grid=grid, + projection="M4c", + surftype="image", + dpi=dpi, + frame=["af", f"WSen+tdpi={dpi}"], + perspective=(225, 30), + ) + fig.shift_origin(xshift="7c") + return fig + + +@pytest.mark.mpl_image_compare +def test_grdview_monochrome(grid): + """ + Test grdview with different surftype values and monochrome=True. + """ + args = { + "grid": grid, + "projection": "M?", + "frame": True, + "panel": True, + "perspective": (-150, 25), + "zsize": "1.0c", + "monochrome": True, + } + fig = Figure() + with fig.subplot(nrows=2, ncols=3, subsize=("5c", "5c"), margins=(0, -0.5)): + for surftype in ["mesh", "surface", "surface+mesh"]: + if surftype in {"surface", "surface+mesh"}: + fig.grdview(surftype=surftype, cmap="SCM/oleron", **args) + else: + fig.grdview(surftype=surftype, **args) + return fig + + +@pytest.mark.mpl_image_compare +def test_grdview_mesh_pen_and_mesh_fill(grid): + """ + Test grdview with mesh_pena and mesh_fill parameters. + """ + args = { + "grid": grid, + "projection": "M?", + "frame": True, + "panel": True, + "perspective": (-150, 25), + "zsize": "1.0c", + "mesh_fill": "lightred", + "mesh_pen": "0.5p,blue", + } + fig = Figure() + with fig.subplot(nrows=1, ncols=3, subsize=("5c", "5c"), margins=0): + for surftype in ["mesh", "waterfall_x", "waterfall_y"]: + fig.grdview(surftype=surftype, **args) + return fig + + def test_grdview_wrong_kind_of_grid(xrgrid): """ Run grdview using grid input that is not an xarray.DataArray or file. @@ -98,27 +194,6 @@ def test_grdview_with_perspective_and_zsize(xrgrid): return fig -@pytest.mark.mpl_image_compare -def test_grdview_with_cmap_for_image_plot(xrgrid): - """ - Run grdview by passing in a grid and setting a colormap for producing an image plot. - """ - fig = Figure() - fig.grdview(grid=xrgrid, cmap="oleron", surftype="i") - return fig - - -@pytest.mark.mpl_image_compare -def test_grdview_with_cmap_for_surface_monochrome_plot(xrgrid): - """ - Run grdview by passing in a grid and setting a colormap for producing a surface - monochrome plot. - """ - fig = Figure() - fig.grdview(grid=xrgrid, cmap="oleron", surftype="s+m") - return fig - - @pytest.mark.mpl_image_compare def test_grdview_with_cmap_for_perspective_surface_plot(xrgrid): """ @@ -127,7 +202,11 @@ def test_grdview_with_cmap_for_perspective_surface_plot(xrgrid): """ fig = Figure() fig.grdview( - grid=xrgrid, cmap="oleron", surftype="s", perspective=[225, 30], zscale=0.005 + grid=xrgrid, + cmap="oleron", + surftype="surface", + perspective=[225, 30], + zscale=0.005, ) return fig @@ -182,7 +261,7 @@ def test_grdview_surface_plot_styled_with_contourpen(xrgrid): """ fig = Figure() fig.grdview( - grid=xrgrid, cmap="relief", surftype="s", contour_pen="0.5p,black,dashed" + grid=xrgrid, cmap="relief", surftype="surface", contour_pen="0.5p,black,dashed" ) return fig @@ -194,7 +273,12 @@ def test_grdview_surface_mesh_plot_styled_with_meshpen(xrgrid): mesh plot. """ fig = Figure() - fig.grdview(grid=xrgrid, cmap="relief", surftype="sm", mesh_pen="0.5p,black,dashed") + fig.grdview( + grid=xrgrid, + cmap="relief", + surftype="surface+mesh", + mesh_pen="0.5p,black,dashed", + ) return fig @@ -243,7 +327,12 @@ def test_grdview_drapegrid_dataarray(xrgrid): fig = Figure() fig.grdview( - grid=xrgrid, drape_grid=drape_grid, cmap="oleron", surftype="c", frame=True + grid=xrgrid, + drape_grid=drape_grid, + cmap="oleron", + surftype="image", + nan_transparent=True, + frame=True, ) return fig @@ -256,3 +345,32 @@ def test_grdview_wrong_kind_of_drapegrid(xrgrid): fig = Figure() with pytest.raises(GMTTypeError): fig.grdview(grid=xrgrid, drape_grid=dataset) + + +def test_grdview_invalid_surftype(gridfile): + """ + Test grdview with an invalid surftype or invalid combination of surftype and other + parameters. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.grdview(grid=gridfile, surftype="surface", dpi=300) + with pytest.raises(GMTInvalidInput): + fig.grdview(grid=gridfile, surftype="surface", nan_transparent=True) + with pytest.raises(GMTInvalidInput): + fig.grdview(grid=gridfile, surftype="surface", mesh_fill="red") + + +def test_grdview_mixed_syntax(gridfile): + """ + Run grdview using grid as a file and drapegrid as an xarray.DataArray. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.grdview(grid=gridfile, cmap="oleron", surftype="i", dpi=300) + with pytest.raises(GMTInvalidInput): + fig.grdview(grid=gridfile, cmap="oleron", surftype="m", mesh_fill="red") + with pytest.raises(GMTInvalidInput): + fig.grdview(grid=gridfile, cmap="oleron", surftype="s", monochrome=True) + with pytest.raises(GMTInvalidInput): + fig.grdview(grid=gridfile, cmap="oleron", surftype="i", nan_transparent=True)