"""
grdgradient - Compute directional gradients from a grid.
"""
import xarray as xr
from pygmt.clib import Session
from pygmt.exceptions import GMTInvalidInput
from pygmt.helpers import (
args_in_kwargs,
build_arg_list,
fmt_docstring,
kwargs_to_strings,
use_alias,
)
__doctest_skip__ = ["grdgradient"]
[docs]
@fmt_docstring
@use_alias(
A="azimuth",
D="direction",
E="radiance",
N="normalize",
Q="tiles",
R="region",
S="slope_file",
V="verbose",
f="coltypes",
n="interpolation",
)
@kwargs_to_strings(A="sequence", E="sequence", R="sequence")
def grdgradient(grid, outgrid: str | None = None, **kwargs) -> xr.DataArray | None:
r"""
Compute the directional derivative of the vector gradient of the data.
Can accept ``azimuth``, ``direction``, and ``radiance`` input to create
the resulting gradient.
Full option list at :gmt-docs:`grdgradient.html`
{aliases}
Parameters
----------
{grid}
{outgrid}
azimuth : float, str, or list
*azim*\ [/*azim2*].
Azimuthal direction for a directional derivative; *azim* is the
angle in the x,y plane measured in degrees positive clockwise from
north (the +y direction) toward east (the +x direction). The
negative of the directional derivative,
:math:`-(\frac{{dz}}{{dx}}\sin(\mbox{{azim}}) + \
\frac{{dz}}{{dy}}\cos(\mbox{{azim}}))`, is found; negation yields
positive values when the slope of :math:`z(x,y)` is downhill in the
*azim* direction, the correct sense for shading the illumination of an
image by a light source above the x,y plane shining from the *azim*
direction. Optionally, supply two azimuths, *azim*/*azim2*, in which
case the gradients in each of these directions are calculated and the
one larger in magnitude is retained; this is useful for illuminating
data with two directions of lineated structures, e.g., *0*/*270*
illuminates from the north (top) and west (left). Finally, if *azim*
is a file it must be a grid of the same domain, spacing and
registration as *grid* that will update the azimuth at each output
node when computing the directional derivatives.
direction : str
[**a**][**c**][**o**][**n**].
Find the direction of the positive (up-slope) gradient of the data.
The following options are supported:
- **a** - Find the aspect (i.e., the down-slope direction)
- **c** - Use the conventional Cartesian angles measured
counterclockwise from the positive x (east) direction.
- **o** - Report orientations (0-180) rather than directions (0-360).
- **n** - Add 90 degrees to all angles (e.g., to give local strikes of
the surface).
radiance : str or list
[**m**\|\ **s**\|\ **p**]\ *azim/elev*\ [**+a**\ *ambient*][**+d**\
*diffuse*][**+p**\ *specular*][**+s**\ *shine*].
Compute Lambertian radiance appropriate to use with
:meth:`pygmt.Figure.grdimage` and :meth:`pygmt.Figure.grdview`. The
Lambertian Reflection assumes an ideal surface that reflects all the
light that strikes it and the surface appears
equally bright from all viewing directions. Here, *azim* and *elev* are
the azimuth and elevation of the light vector. Optionally, supply
*ambient* [0.55], *diffuse* [0.6], *specular* [0.4], or *shine* [10],
which are parameters that control the reflectance properties of the
surface. Default values are given in the brackets. Use **s** for a
simpler Lambertian algorithm. Note that with this form you only have
to provide azimuth and elevation. Alternatively, use **p** for
the Peucker piecewise linear approximation (simpler but faster
algorithm; in this case *azim* and *elev* are hardwired to 315
and 45 degrees. This means that even if you provide other values
they will be ignored.).
normalize : str or bool
[**e**\|\ **t**][*amp*][**+a**\ *ambient*][**+s**\ *sigma*]\
[**+o**\ *offset*].
The actual gradients :math:`g` are offset and scaled to produce
normalized gradients :math:`g_n` with a maximum output magnitude of
*amp*. If *amp* is not given, default *amp* = 1. If *offset* is not
given, it is set to the average of :math:`g`. The following forms are
supported:
- **True** - Normalize using :math:`g_n = \mbox{{amp}}\
(\frac{{g - \mbox{{offset}}}}{{max(|g - \mbox{{offset}}|)}})`
- **e** - Normalize using a cumulative Laplace distribution yielding:
:math:`g_n = \mbox{{amp}}(1 - \
\exp{{(\sqrt{{2}}\frac{{g - \mbox{{offset}}}}{{\sigma}}))}}`, where
:math:`\sigma` is estimated using the L1 norm of
:math:`(g - \mbox{{offset}})` if it is not given.
- **t** - Normalize using a cumulative Cauchy distribution yielding:
:math:`g_n = \
\frac{{2(\mbox{{amp}})}}{{\pi}}(\tan^{{-1}}(\frac{{g - \
\mbox{{offset}}}}{{\sigma}}))` where :math:`\sigma` is estimated
using the L2 norm of :math:`(g - \mbox{{offset}})` if it is not
given.
As a final option, you may add **+a**\ *ambient* to add *ambient* to
all nodes after gradient calculations are completed.
tiles : str
**c**\|\ **r**\|\ **R**.
Control how normalization via ``normalize`` is carried out. When
multiple grids should be normalized the same way (i.e., with the same
*offset* and/or *sigma*),
we must pass these values via ``normalize``. However, this is
inconvenient if we compute these values from a grid. Use **c** to
save the results of *offset* and *sigma* to a statistics file; if
grid output is not needed for this run then do not specify
``outgrid``. For subsequent runs, just use **r** to read these
values. Using **R** will read then delete the statistics file.
{region}
slope_file : str
Name of output grid file with scalar magnitudes of gradient vectors.
Requires ``direction`` but makes ``outgrid`` optional.
{verbose}
{coltypes}
{interpolation}
Returns
-------
ret
Return type depends on whether the ``outgrid`` parameter is set:
- :class:`xarray.DataArray` if ``outgrid`` is not set
- ``None`` if ``outgrid`` is set (grid output will be stored in the file set by
``outgrid``)
Example
-------
>>> import pygmt
>>> # Load a grid of @earth_relief_30m data, with a longitude range of
>>> # 10° E to 30° E, and a latitude range of 15° N to 25° N
>>> grid = pygmt.datasets.load_earth_relief(
... resolution="30m", region=[10, 30, 15, 25]
... )
>>> # Create a new grid from an input grid, set the azimuth to 10 degrees,
>>> new_grid = pygmt.grdgradient(grid=grid, azimuth=10)
"""
if kwargs.get("Q") is not None and kwargs.get("N") is None:
msg = "Must specify normalize if tiles is specified."
raise GMTInvalidInput(msg)
if not args_in_kwargs(args=["A", "D", "E"], kwargs=kwargs):
msg = (
"At least one of the following parameters must be specified: "
"azimuth, direction, or radiance."
)
raise GMTInvalidInput(msg)
with Session() as lib:
with (
lib.virtualfile_in(check_kind="raster", data=grid) as vingrd,
lib.virtualfile_out(kind="grid", fname=outgrid) as voutgrd,
):
kwargs["G"] = voutgrd
lib.call_module(
module="grdgradient", args=build_arg_list(kwargs, infile=vingrd)
)
return lib.virtualfile_to_raster(vfname=voutgrd, outgrid=outgrid)