"""
grdview - Create 3-D perspective image or surface mesh from a grid.
"""
from collections.abc import Sequence
from typing import Literal
import xarray as xr
from packaging.version import Version
from pygmt._typing import PathLike
from pygmt.alias import Alias, AliasSystem
from pygmt.clib import Session, __gmt_version__
from pygmt.exceptions import GMTParameterError
from pygmt.helpers import build_arg_list, deprecate_parameter, fmt_docstring, use_alias
from pygmt.src.grdinfo import grdinfo
__doctest_skip__ = ["grdview"]
def _alias_option_Q( # noqa: N802
surftype=None, dpi=None, mesh_fill=None, monochrome=False, nan_transparent=False
):
"""
Helper function to build the Alias list for the -Q option.
Examples
--------
>>> def parse(**kwargs):
... return AliasSystem(Q=_alias_option_Q(**kwargs)).get("Q")
>>> parse(surftype="surface")
's'
>>> parse(surftype="mesh")
'm'
>>> parse(surftype="surface+mesh")
'sm'
>>> parse(surftype="waterfall_x")
'mx'
>>> parse(surftype="waterfall_y")
'my'
>>> parse(surftype="image")
'i'
>>> parse(surftype="image", nan_transparent=True)
'c'
>>> parse(surftype="image", dpi=150)
'i150'
>>> parse(surftype="image", dpi=150, nan_transparent=True)
'c150'
>>> parse(surftype="mesh", mesh_fill="blue")
'mblue'
>>> parse(surftype="surface", monochrome=True)
's+m'
>>> parse(surftype="surface+mesh", monochrome=True)
'sm+m'
>>> # Check for backward compatibility with old raw GMT syntax.
>>> for surftype in ["s", "m", "sm", "i", "c", "mx", "my", "mblue", "i150"]:
... assert parse(surftype=surftype) == surftype
"""
_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 is not None and v is not False
for v in (dpi, mesh_fill, monochrome, nan_transparent)
):
raise GMTParameterError(
conflicts_with=(
"surftype",
["dpi", "mesh_fill", "monochrome", "nan_transparent"],
),
reason="'surftype' is specified using the unrecommended GMT command string syntax.",
)
if dpi is not None and surftype != "image":
raise GMTParameterError(
conflicts_with=("dpi", [f"surftype={surftype!r}"]),
reason="'dpi' is allowed only when 'surftype' is 'image'.",
)
if nan_transparent and surftype != "image":
raise GMTParameterError(
conflicts_with=("nan_transparent", [f"surftype={surftype!r}"]),
reason="'nan_transparent' is allowed only when 'surftype' is 'image'.",
)
if mesh_fill is not None and surftype not in {"mesh", "waterfall_x", "waterfall_y"}:
raise GMTParameterError(
conflicts_with=("mesh_fill", [f"surftype={surftype!r}"]),
reason="'mesh_fill' is allowed only when 'surftype' is 'mesh', 'waterfall_x', or 'waterfall_y'.",
)
return [
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"),
]
@fmt_docstring
# TODO(PyGMT>=0.20.0): Remove the deprecated '*pen' parameters.
# TODO(PyGMT>=0.20.0): Remove the deprecated 'drapegrid' parameter.
@deprecate_parameter("contourpen", "contour_pen", "v0.18.0", remove_version="v0.20.0")
@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", 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: str | None = None,
mesh_pen: str | None = None,
plane: float | bool = False,
facade_fill: str | None = None,
facade_pen: str | None = None,
projection: str | None = None,
zscale: float | str | None = None,
zsize: float | str | None = None,
region: Sequence[float | str] | str | None = None,
frame: str | Sequence[str] | Literal["none"] | bool = False,
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
panel: int | Sequence[int] | bool = False,
perspective: float | Sequence[float] | str | bool = False,
transparency: float | None = None,
**kwargs,
):
r"""
Create 3-D perspective image or surface mesh from a grid.
Reads a 2-D grid file and produces a 3-D perspective plot by drawing a mesh,
painting a colored/gray-shaded surface made up of polygons, or by scanline
conversion of these polygons to a raster image. Options include draping a data
set on top of a surface, plotting of contours on top of the surface, and apply
artificial illumination based on intensities provided in a separate grid file.
Full GMT docs at :gmt-docs:`grdview.html`.
$aliases
- B = frame
- J = projection
- Jz = zscale
- JZ = zsize
- N = plane, facade_fill
- R = region
- Q = surftype, dpi, mesh_fill, nan_transparent, **+m**: monochrome
- V = verbose
- Wc = contour_pen
- Wf = facade_pen
- Wm = mesh_pen
- c = panel
- p = perspective
- t = transparency
Parameters
----------
$grid
cmap : str
The name of the color palette table to use.
drape_grid : str or :class:`xarray.DataArray`
The file name or a :class:`xarray.DataArray` of the image grid to be draped on
top of the relief provided by ``grid`` [Default determines colors from ``grid``]
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
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. 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
*zmin* is used if it is less than the grid minimum value. Use ``facade_pen`` and
``facade_fill`` to control the appearance of the plane.
**Note**: For GMT<=6.6.0, *zmin* set in ``region`` has no effect due to a GMT
bug.
facade_fill
Fill for the frontal facade between the plane specified by ``plane`` and the
data perimeter.
facade_pen
Set the pen attributes used for the facade.
shading : str or float
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).
Alternatively, derive an intensity grid from the main input data grid by using
:func:`pygmt.grdgradient` first; append **+a**\ *azimuth*, **+n**\ *args*, and
**+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 </tutorials/basics/regions>` 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
$interpolation
$perspective
$transparency
Example
-------
>>> import pygmt
>>> # Load the 30 arc-minutes grid with "gridline" registration in a given region
>>> grid = pygmt.datasets.load_earth_relief(
... resolution="30m",
... region=[-92.5, -82.5, -3, 7],
... registration="gridline",
... )
>>> # Create a new figure instance with pygmt.Figure()
>>> fig = pygmt.Figure()
>>> # Create the contour plot
>>> fig.grdview(
... # Pass in the grid downloaded above
... grid=grid,
... # Set the perspective to an azimuth of 130° and an elevation of 30°
... perspective=[130, 30],
... # Add a frame to the x- and y-axes
... # Specify annotations on the south and east borders of the plot
... frame=["xa", "ya", "wSnE"],
... # Set the projection of the 2-D map to Mercator with a 10 cm width
... projection="M10c",
... # Set the vertical scale (z-axis) to 2 cm
... zsize="2c",
... # Set "surface plot" to color the surface via a CPT
... surftype="surface",
... # Specify CPT to "geo"
... cmap="gmt/geo",
... )
>>> # Show the plot
>>> fig.show()
"""
self._activate_figure()
# 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
# Workaround for GMT bug https://github.com/GenericMappingTools/gmt/pull/8838
# Fix the plane value to be the grid minimum if plane=True.
# Notes:
# 1. It's the minimum of the grid, not a subset of the grid defined by 'region'.
# 2. The GMT docs says "if -R was used to set zmin/zmax then we use that value if
# it is less than the grid minimum value.". We can't add a workaround for this
# case since we can't parse zmin/zmax from 'region' if 'region' was set in
# previous plotting commands.
# TODO(GMT>6.6.0): Remove this workaround.
if Version(__gmt_version__) <= Version("6.6.0") and plane is True:
plane = grdinfo(grid, per_column=True).split()[4]
aliasdict = AliasSystem(
Jz=Alias(zscale, name="zscale"),
JZ=Alias(zsize, name="zsize"),
Q=_alias_option_Q(
surftype=surftype,
dpi=dpi,
mesh_fill=mesh_fill,
monochrome=monochrome,
nan_transparent=nan_transparent,
),
N=[
Alias(plane, name="plane"),
Alias(facade_fill, name="facade_fill", prefix="+g"),
],
Wc=Alias(contour_pen, name="contour_pen"),
Wf=Alias(facade_pen, name="facade_pen"),
Wm=Alias(mesh_pen, name="mesh_pen"),
).add_common(
B=frame,
J=projection,
R=region,
V=verbose,
c=panel,
p=perspective,
t=transparency,
)
aliasdict.merge(kwargs)
with Session() as lib:
with (
lib.virtualfile_in(check_kind="raster", data=grid) as vingrd,
lib.virtualfile_in(
check_kind="raster", data=kwargs.get("G"), required=False
) as vdrapegrid,
):
aliasdict["G"] = vdrapegrid
lib.call_module(
module="grdview", args=build_arg_list(aliasdict, infile=vingrd)
)