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 5bef9b37a96..d45823327df 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", # Set the plane elevation to 1,000 meters and make the fill "gray" plane="1000+ggray", @@ -88,7 +86,7 @@ frame=["xaf", "yaf", "WSnE"], projection="M15c", zsize="1.5c", - surftype="s", + surftype="surface", cmap="geo", plane="1000+ggrey", # Set the contour pen thickness to "0.1p" diff --git a/examples/tutorials/advanced/draping_on_3d_surface.py b/examples/tutorials/advanced/draping_on_3d_surface.py index eaa85cd367e..83932be6084 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 drapegrid=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 drapegrid=drapegrid, # Drap 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 90bc13f12b2..7aea3f1e7a8 100644 --- a/pygmt/src/grdview.py +++ b/pygmt/src/grdview.py @@ -9,6 +9,7 @@ from pygmt._typing import PathLike from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import build_arg_list, deprecate_parameter, fmt_docstring, use_alias __doctest_skip__ = ["grdview"] @@ -22,7 +23,6 @@ C="cmap", G="drapegrid", N="plane", - Q="surftype", I="shading", f="coltypes", n="interpolation", @@ -30,6 +30,14 @@ 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, + mesh_fill: float | None = None, + nan_transparent: bool = False, + monochrome: bool = False, contour_pen: str | None = None, facade_pen: str | None = None, mesh_pen: str | None = None, @@ -62,6 +70,7 @@ def grdview( # noqa: PLR0913 - Jz = zscale - JZ = zsize - R = region + - Q = surftype, dpi, mesh_fill, nan_transparent, monochrome - V = verbose - Wc = contour_pen - Wf = facade_pen @@ -95,18 +104,24 @@ def grdview( # noqa: PLR0913 Draw a plane at this z-level. If the optional color is provided via the **+g** modifier, and the projection is not oblique, the frontal facade between the plane and the data perimeter is colored. - 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 of 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`] + mesh_fill + Set the mesh fill in mesh plot or waterfall plots [Default is white]. + 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. @@ -114,8 +129,8 @@ def grdview( # noqa: PLR0913 Set the pen attributes used for the facade. You must also select ``plane`` for the facade outline to be drawn. 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. shading : str Provide the name of a grid file with intensities in the (-1,+1) range, or a constant intensity to apply everywhere (affects the ambient light). @@ -156,7 +171,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", ... ) @@ -165,9 +180,53 @@ 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) + 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"), + ], Wc=Alias(contour_pen, name="contour_pen"), Wf=Alias(facade_pen, name="facade_pen"), Wm=Alias(mesh_pen, name="mesh_pen"), diff --git a/pygmt/tests/test_grdview.py b/pygmt/tests/test_grdview.py index a1e8fd6735f..36da8599051 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 @@ -104,7 +104,7 @@ 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") + fig.grdview(grid=xrgrid, cmap="oleron", surftype="image") return fig @@ -115,7 +115,7 @@ def test_grdview_with_cmap_for_surface_monochrome_plot(xrgrid): monochrome plot. """ fig = Figure() - fig.grdview(grid=xrgrid, cmap="oleron", surftype="s+m") + fig.grdview(grid=xrgrid, cmap="oleron", surftype="surface", monochrome=True) return fig @@ -127,7 +127,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 @@ -179,7 +183,9 @@ def test_grdview_surface_plot_styled_with_contourpen(xrgrid): surface plot. """ fig = Figure() - fig.grdview(grid=xrgrid, cmap="relief", surftype="s", contour_pen="0.5p,black,dashed") + fig.grdview( + grid=xrgrid, cmap="relief", surftype="surface", contour_pen="0.5p,black,dash" + ) return fig @@ -190,7 +196,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 @@ -226,7 +237,12 @@ def test_grdview_drapegrid_dataarray(xrgrid): fig = Figure() fig.grdview( - grid=xrgrid, drapegrid=drapegrid, cmap="oleron", surftype="c", frame=True + grid=xrgrid, + drapegrid=drapegrid, + cmap="oleron", + surftype="image", + nan_transparent=True, + frame=True, ) return fig @@ -239,3 +255,32 @@ def test_grdview_wrong_kind_of_drapegrid(xrgrid): fig = Figure() with pytest.raises(GMTTypeError): fig.grdview(grid=xrgrid, drapegrid=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)