Skip to content

Figure.coast/pygmt.select/pygmt.grdlandmask: Use long names ("crude"/"low"/"intermediate"/"high"/"full") for the 'resolution' parameter #3013

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jun 3, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 11 additions & 15 deletions examples/tutorials/basics/coastlines.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
# 3. island-in-lake shore
# 4. lake-in-island-in-lake shore
#
# You can specify which level you want to plot by passing the level number and
# a GMT pen configuration. For example, to plot just the coastlines with 0.5p
# thickness and black lines:
# You can specify which level you want to plot by passing the level number and a GMT
# pen configuration. For example, to plot just the coastlines with 0.5p thickness and
# black lines:

fig = pygmt.Figure()
fig.basemap(region="g", projection="W15c", frame=True)
Expand All @@ -50,18 +50,14 @@
# Resolutions
# -----------
#
# The coastline database comes with 5 resolutions. The resolution drops by 80%
# between levels:
#
# 1. ``"c"``: crude
# 2. ``"l"``: low (default)
# 3. ``"i"``: intermediate
# 4. ``"h"``: high
# 5. ``"f"``: full
# The coastline database comes with 5 resolutions: ``"full"``, ``"high"``,
# ``"intermediate"``, ``"low"``, and ``"crude"``. The resolution drops by 80% between
# levels. The ``resolution`` parameter defaults to ``"auto"`` to automatically select
# the best resolution given the chosen map scale.

oahu = [-158.3, -157.6, 21.2, 21.8]
fig = pygmt.Figure()
for res in ["c", "l", "i", "h", "f"]:
for res in ["crude", "low", "intermediate", "high", "full"]:
fig.coast(resolution=res, shorelines="1p", region=oahu, projection="M5c")
fig.shift_origin(xshift="5c")
fig.show()
Expand All @@ -71,9 +67,9 @@
# Land and water
# --------------
#
# Use the ``land`` and ``water`` parameters to specify a fill color for land
# and water bodies. The colors can be given by name or hex codes (like the ones
# used in HTML and CSS):
# Use the ``land`` and ``water`` parameters to specify a fill color for land and water
# bodies. The colors can be given by name or hex codes (like the ones used in HTML and
# CSS):

fig = pygmt.Figure()
fig.basemap(region="g", projection="W15c", frame=True)
Expand Down
53 changes: 53 additions & 0 deletions pygmt/src/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,56 @@ def from_params(
f"{', '.join(params)}."
)
raise GMTInvalidInput(msg)


def _parse_coastline_resolution(
resolution: Literal["auto", "full", "high", "intermediate", "low", "crude", None],
) -> Literal["a", "f", "h", "i", "l", "c", None]:
Comment on lines +256 to +258
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on making a StrEnum out of this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the benefits? This private function will likely be removed after #3239 is implemented.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GMT uses short-form arguments, but we prefer long-form arguments in PyGMT. Another example is in #3012. It will be a lot of work if we define StrEnum for all these cases. #3239 proposed a more general solution for it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, StrEnum will be a lot more work, and we'll need to bring in something like pydantic (mentioned at #3239 (comment)) to fully enable runtime checks. Ok to keep this as is for now, and wait for #3239.

"""
Parse the 'resolution' parameter for coastline-related functions.

Parameters
----------
resolution
The resolution of the coastline dataset to use. The available resolutions from
highest to lowest are: ``"full"``, ``"high"``, ``"intermediate"``, ``"low"``,
and ``"crude"``, which drops by 80% between levels. Alternatively, choose
``"auto"`` to automatically select the most suitable resolution given the chosen
map scale or region. ``None`` means using the default resolution.

Returns
-------
The single-letter resolution code or ``None``.

Raises
------
GMTInvalidInput
If the resolution is invalid.

Examples
--------
>>> _parse_coastline_resolution("full")
'f'
>>> _parse_coastline_resolution("f")
'f'
>>> _parse_coastline_resolution(None)
>>> _parse_coastline_resolution("invalid")
Traceback (most recent call last):
...
pygmt.exceptions.GMTInvalidInput: Invalid resolution: 'invalid'. Valid values ...
"""
if resolution is None:
return None

_valid_res = {"auto", "full", "high", "intermediate", "low", "crude"}

if resolution in _valid_res: # Long-form arguments.
return resolution[0] # type: ignore[return-value]

if resolution in {_res[0] for _res in _valid_res}: # Short-form arguments.
return resolution # type: ignore[return-value]

msg = (
f"Invalid resolution: '{resolution}'. Valid values are {', '.join(_valid_res)}."
)
raise GMTInvalidInput(msg)
25 changes: 19 additions & 6 deletions pygmt/src/coast.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
coast - Plot continents, countries, shorelines, rivers, and borders.
"""

from typing import Literal

from pygmt.clib import Session
from pygmt.exceptions import GMTInvalidInput
from pygmt.helpers import (
Expand All @@ -11,6 +13,7 @@
kwargs_to_strings,
use_alias,
)
from pygmt.src._common import _parse_coastline_resolution

__doctest_skip__ = ["coast"]

Expand All @@ -20,7 +23,6 @@
A="area_thresh",
B="frame",
C="lakes",
D="resolution",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After removing D="resolution", this alias won't be shown in the alias list. Need to wait for #3945.

E="dcw",
F="box",
G="land",
Expand All @@ -37,7 +39,13 @@
t="transparency",
)
@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence")
def coast(self, **kwargs):
def coast(
self,
resolution: Literal[
"auto", "full", "high", "intermediate", "low", "crude", None
] = None,
**kwargs,
):
r"""
Plot continents, countries, shorelines, rivers, and borders.

Expand Down Expand Up @@ -73,10 +81,12 @@ def coast(self, **kwargs):
parameter. Optionally, specify separate fills by appending
**+l** for lakes or **+r** for river-lakes, and passing multiple
strings in a list.
resolution : str
**f**\|\ **h**\|\ **i**\|\ **l**\|\ **c**.
Select the resolution of the data set to: (**f**\ )ull, (**h**\ )igh,
(**i**\ )ntermediate, (**l**\ )ow, and (**c**\ )rude.
resolution
Select the resolution of the coastline dataset to use. The available resolutions
from highest to lowest are: ``"full"``, ``"high"``, ``"intermediate"``,
``"low"``, and ``"crude"``, which drops by 80% between levels. Default is
``"auto"`` to automatically select the most suitable resolution given the chosen
map scale.
land : str
Select filling of "dry" areas.
rivers : int, str, or list
Expand Down Expand Up @@ -200,5 +210,8 @@ def coast(self, **kwargs):
"lakes, land, water, rivers, borders, dcw, Q, or shorelines."
)
raise GMTInvalidInput(msg)

kwargs["D"] = kwargs.get("D", _parse_coastline_resolution(resolution))

Comment on lines +214 to +215
Copy link
Member Author

@seisman seisman May 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simplest form of this line is:

kwargs["D"] = _parse_coastline_resolution(resolution)

but to support short-form parameters like D="f", we have to write the code like:

kwargs["D"] = kwargs.get("D", _parse_coastline_resolution(resolution))

The D="resolution" line is also removed from the use_alias decorator. Otherwise, we have to write codes like below:

kwargs["D"] = _parse_coastline_resolution(kwgars.get("D"))

It's fine, but then ruff will complain that the resolution parameter is unused.

with Session() as lib:
lib.call_module(module="coast", args=build_arg_list(kwargs))
34 changes: 21 additions & 13 deletions pygmt/src/grdlandmask.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@
grdlandmask - Create a "wet-dry" mask grid from shoreline database.
"""

from typing import Literal

import xarray as xr
from pygmt._typing import PathLike
from pygmt.clib import Session
from pygmt.exceptions import GMTInvalidInput
from pygmt.helpers import build_arg_list, fmt_docstring, kwargs_to_strings, use_alias
from pygmt.src._common import _parse_coastline_resolution

__doctest_skip__ = ["grdlandmask"]


@fmt_docstring
@use_alias(
A="area_thresh",
D="resolution",
E="bordervalues",
I="spacing",
N="maskvalues",
Expand All @@ -24,7 +26,13 @@
x="cores",
)
@kwargs_to_strings(I="sequence", R="sequence", N="sequence", E="sequence")
def grdlandmask(outgrid: PathLike | None = None, **kwargs) -> xr.DataArray | None:
def grdlandmask(
outgrid: PathLike | None = None,
resolution: Literal[
"auto", "full", "high", "intermediate", "low", "crude", None
] = None,
**kwargs,
) -> xr.DataArray | None:
r"""
Create a "wet-dry" mask grid from shoreline database.

Expand All @@ -44,17 +52,15 @@ def grdlandmask(outgrid: PathLike | None = None, **kwargs) -> xr.DataArray | Non
{spacing}
{region}
{area_thresh}
resolution : str
*res*\[\ **+f**\]. Select the resolution of the data set to use
((**f**)ull, (**h**)igh, (**i**)ntermediate, (**l**)ow, or
(**c**)rude). The resolution drops off by ~80% between data sets.
[Default is **l**]. Append **+f** to automatically select a lower
resolution should the one requested not be available
[abort if not found]. Alternatively, choose (**a**)uto to automatically
select the best resolution given the chosen region. Note that because
the coastlines differ in details a node in a mask file using one
resolution is not guaranteed to remain inside [or outside] when a
different resolution is selected.
resolution
Select the resolution of the coastline dataset to use. The available resolutions
from highest to lowest are: ``"full"``, ``"high"``, ``"intermediate"``,
``"low"``, and ``"crude"``, which drops by 80% between levels. Alternatively,
choose ``"auto"`` to automatically select the most suitable resolution given the
chosen region. Note that because the coastlines differ in details, a node in a
mask file using one resolution is not guaranteed to remain inside [or outside]
when a different resolution is selected. If ``None``, the low resolution is used
by default.
maskvalues : list
Set the values that will be assigned to nodes, in the form of [*wet*, *dry*], or
[*ocean*, *land*, *lake*, *island*, *pond*]. Default is ``[0, 1, 0, 1, 0]``
Expand Down Expand Up @@ -102,6 +108,8 @@ def grdlandmask(outgrid: PathLike | None = None, **kwargs) -> xr.DataArray | Non
msg = "Both 'region' and 'spacing' must be specified."
raise GMTInvalidInput(msg)

kwargs["D"] = kwargs.get("D", _parse_coastline_resolution(resolution))

with Session() as lib:
with lib.virtualfile_out(kind="grid", fname=outgrid) as voutgrd:
kwargs["G"] = voutgrd
Expand Down
26 changes: 15 additions & 11 deletions pygmt/src/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use_alias,
validate_output_table_type,
)
from pygmt.src._common import _parse_coastline_resolution

__doctest_skip__ = ["select"]

Expand All @@ -23,7 +24,6 @@
@use_alias(
A="area_thresh",
C="dist2pt",
D="resolution",
F="polygon",
G="gridmask",
I="reverse",
Expand All @@ -49,6 +49,9 @@ def select(
data: PathLike | TableLike | None = None,
output_type: Literal["pandas", "numpy", "file"] = "pandas",
outfile: PathLike | None = None,
resolution: Literal[
"auto", "full", "high", "intermediate", "low", "crude", None
] = None,
**kwargs,
) -> pd.DataFrame | np.ndarray | None:
r"""
Expand Down Expand Up @@ -117,16 +120,6 @@ def select(
<reference/file-formats.html#optional-segment-header-records>`
*polygonfile*. For spherical polygons (lon, lat), make sure no
consecutive points are separated by 180 degrees or more in longitude.
resolution : str
*resolution*\ [**+f**].
Ignored unless ``mask`` is set. Selects the resolution of the coastline
data set to use ((**f**)ull, (**h**)igh, (**i**)ntermediate, (**l**)ow,
or (**c**)rude). The resolution drops off by ~80% between data sets.
[Default is **l**]. Append (**+f**) to automatically select a lower
resolution should the one requested not be available [Default is abort
if not found]. Note that because the coastlines differ in details
it is not guaranteed that a point will remain inside [or outside] when
a different resolution is selected.
gridmask : str
Pass all locations that are inside the valid data area of the grid
*gridmask*. Nodes that are outside are either NaN or zero.
Expand Down Expand Up @@ -155,6 +148,15 @@ def select(

[Default is s/k/s/k/s (i.e., s/k), which passes all points on dry
land].
resolution
Ignored unless ``mask`` is set. Select the resolution of the coastline dataset
to use. The available resolutions from highest to lowest are: ``"full"``,
``"high"``, ``"intermediate"``, ``"low"``, and ``"crude"``, which drops by 80%
between levels. Alternatively, choose ``"auto"`` to automatically select the
most suitable resolution given the chosen region. Note that because the
coastlines differ in details, a node in a mask file using one resolution is not
guaranteed to remain inside [or outside] when a different resolution is
selected. If ``None``, the low resolution is used by default.
{region}
{verbose}
z_subregion : str or list
Expand Down Expand Up @@ -206,6 +208,8 @@ def select(
>>> # longitudes 246 and 247 and latitudes 20 and 21
>>> out = pygmt.select(data=ship_data, region=[246, 247, 20, 21])
"""
kwargs["D"] = kwargs.get("D", _parse_coastline_resolution(resolution))

output_type = validate_output_table_type(output_type, outfile=outfile)

column_names = None
Expand Down
2 changes: 1 addition & 1 deletion pygmt/tests/test_coast.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_coast_world_mercator():
projection="M15c",
frame="af",
land="#aaaaaa",
resolution="c",
resolution="crude",
water="white",
)
return fig
Expand Down
Loading