From 44f983fd0d520972b0f792c6ea6dfe4cd368d0dc Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 18 Jan 2024 16:47:07 +0800 Subject: [PATCH 01/11] Figure.coast: Use long names ("crude"/"low"/"intermediate"/"high"/"full") for the 'resolution' parameter --- examples/tutorials/basics/coastlines.py | 26 +++++++++++-------------- pygmt/src/coast.py | 23 +++++++++++++++++----- pygmt/tests/test_coast.py | 2 +- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/examples/tutorials/basics/coastlines.py b/examples/tutorials/basics/coastlines.py index 5e611b3e2d3..d1e4fa4399d 100644 --- a/examples/tutorials/basics/coastlines.py +++ b/examples/tutorials/basics/coastlines.py @@ -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) @@ -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() @@ -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) diff --git a/pygmt/src/coast.py b/pygmt/src/coast.py index 87a99692541..d7ad8ea7d29 100644 --- a/pygmt/src/coast.py +++ b/pygmt/src/coast.py @@ -2,6 +2,8 @@ coast - Plot land and water. """ +from typing import Literal + from pygmt.clib import Session from pygmt.exceptions import GMTInvalidInput from pygmt.helpers import ( @@ -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[ # noqa: ARG001 + "auto", "full", "high", "intermediate", "low", "crude" + ] = "auto", + **kwargs, +): r""" Plot continents, shorelines, rivers, and borders on maps. @@ -73,10 +81,11 @@ 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"``. 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 @@ -199,5 +208,9 @@ def coast(self, **kwargs): """At least one of the following parameters must be specified: lakes, land, water, rivers, borders, dcw, Q, or shorelines""" ) + # resolution + if kwargs.get("D") is not None: + kwargs["D"] = kwargs["D"][0] + with Session() as lib: lib.call_module(module="coast", args=build_arg_list(kwargs)) diff --git a/pygmt/tests/test_coast.py b/pygmt/tests/test_coast.py index 780ec63cace..64153e8e36e 100644 --- a/pygmt/tests/test_coast.py +++ b/pygmt/tests/test_coast.py @@ -29,7 +29,7 @@ def test_coast_world_mercator(): projection="M15c", frame="af", land="#aaaaaa", - resolution="c", + resolution="crude", water="white", ) return fig From 4fae7459d12ced88f919aee04658448146495b32 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 22 Jul 2024 23:07:40 +0800 Subject: [PATCH 02/11] Also improve select and grdlandmask --- pygmt/src/coast.py | 8 +++++--- pygmt/src/grdlandmask.py | 30 ++++++++++++++++++------------ pygmt/src/select.py | 22 ++++++++++++---------- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/pygmt/src/coast.py b/pygmt/src/coast.py index d7ad8ea7d29..e456fc74a55 100644 --- a/pygmt/src/coast.py +++ b/pygmt/src/coast.py @@ -84,8 +84,9 @@ def coast( resolution Select the resolution of the coastline dataset to use. The available resolutions from highest to lowest are: ``"full"``, ``"high"``, ``"intermediate"``, - ``"low"``, and ``"crude"``. Default is ``"auto"`` to automatically select the - most suitable resolution given the chosen map scale. + ``"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 @@ -208,7 +209,8 @@ def coast( """At least one of the following parameters must be specified: lakes, land, water, rivers, borders, dcw, Q, or shorelines""" ) - # resolution + + # Resolution if kwargs.get("D") is not None: kwargs["D"] = kwargs["D"][0] diff --git a/pygmt/src/grdlandmask.py b/pygmt/src/grdlandmask.py index 5244c89eca2..fd8a539a8fa 100644 --- a/pygmt/src/grdlandmask.py +++ b/pygmt/src/grdlandmask.py @@ -2,6 +2,8 @@ grdlandmask - Create a "wet-dry" mask grid from shoreline data base """ +from typing import Literal + import xarray as xr from pygmt.clib import Session from pygmt.exceptions import GMTInvalidInput @@ -23,7 +25,11 @@ x="cores", ) @kwargs_to_strings(I="sequence", R="sequence", N="sequence", E="sequence") -def grdlandmask(outgrid: str | None = None, **kwargs) -> xr.DataArray | None: +def grdlandmask( + outgrid: str | None = None, + resolution: Literal["full", "high", "intermediate", "low", "crude"] = "low", # noqa: ARG001 + **kwargs, +) -> xr.DataArray | None: r""" Create a grid file with set values for land and water. @@ -44,17 +50,13 @@ def grdlandmask(outgrid: str | None = None, **kwargs) -> xr.DataArray | None: {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 + 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. 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. bordervalues : bool, str, float, or list Nodes that fall exactly on a polygon boundary should be considered to be outside the polygon [Default considers them to be @@ -98,6 +100,10 @@ def grdlandmask(outgrid: str | None = None, **kwargs) -> xr.DataArray | None: if kwargs.get("I") is None or kwargs.get("R") is None: raise GMTInvalidInput("Both 'region' and 'spacing' must be specified.") + # Resolution + if kwargs.get("D") is not None: + kwargs["D"] = kwargs["D"][0] + with Session() as lib: with lib.virtualfile_out(kind="grid", fname=outgrid) as voutgrd: kwargs["G"] = voutgrd diff --git a/pygmt/src/select.py b/pygmt/src/select.py index ecd6d12bfad..c54c2fdf3a5 100644 --- a/pygmt/src/select.py +++ b/pygmt/src/select.py @@ -48,6 +48,7 @@ def select( data=None, output_type: Literal["pandas", "numpy", "file"] = "pandas", outfile: str | None = None, + resolution: Literal["full", "high", "intermediate", "low", "crude"] = "low", # noqa: ARG001 **kwargs, ) -> pd.DataFrame | np.ndarray | None: r""" @@ -116,16 +117,13 @@ def select( ` *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. + 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. 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. @@ -205,6 +203,10 @@ def select( >>> # longitudes 246 and 247 and latitudes 20 and 21 >>> out = pygmt.select(data=ship_data, region=[246, 247, 20, 21]) """ + # Resolution + if kwargs.get("D") is not None: + kwargs["D"] = kwargs["D"][0] + output_type = validate_output_table_type(output_type, outfile=outfile) column_names = None From 5fac3ae086fa09c84b52fd1d2eaabc63774d6998 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 29 Sep 2024 18:49:03 +0800 Subject: [PATCH 03/11] Remove the old aliases --- pygmt/src/coast.py | 8 +++----- pygmt/src/grdlandmask.py | 9 +++------ pygmt/src/select.py | 8 +++----- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/pygmt/src/coast.py b/pygmt/src/coast.py index e456fc74a55..fe0a034ec30 100644 --- a/pygmt/src/coast.py +++ b/pygmt/src/coast.py @@ -22,7 +22,6 @@ A="area_thresh", B="frame", C="lakes", - D="resolution", E="dcw", F="box", G="land", @@ -41,7 +40,7 @@ @kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") def coast( self, - resolution: Literal[ # noqa: ARG001 + resolution: Literal[ "auto", "full", "high", "intermediate", "low", "crude" ] = "auto", **kwargs, @@ -210,9 +209,8 @@ def coast( lakes, land, water, rivers, borders, dcw, Q, or shorelines""" ) - # Resolution - if kwargs.get("D") is not None: - kwargs["D"] = kwargs["D"][0] + # Alias 'resolution' to "D". + kwargs["D"] = resolution[0] with Session() as lib: lib.call_module(module="coast", args=build_arg_list(kwargs)) diff --git a/pygmt/src/grdlandmask.py b/pygmt/src/grdlandmask.py index fd8a539a8fa..b99b13497ad 100644 --- a/pygmt/src/grdlandmask.py +++ b/pygmt/src/grdlandmask.py @@ -15,7 +15,6 @@ @fmt_docstring @use_alias( A="area_thresh", - D="resolution", E="bordervalues", I="spacing", N="maskvalues", @@ -27,7 +26,7 @@ @kwargs_to_strings(I="sequence", R="sequence", N="sequence", E="sequence") def grdlandmask( outgrid: str | None = None, - resolution: Literal["full", "high", "intermediate", "low", "crude"] = "low", # noqa: ARG001 + resolution: Literal["full", "high", "intermediate", "low", "crude"] = "low", **kwargs, ) -> xr.DataArray | None: r""" @@ -100,10 +99,8 @@ def grdlandmask( if kwargs.get("I") is None or kwargs.get("R") is None: raise GMTInvalidInput("Both 'region' and 'spacing' must be specified.") - # Resolution - if kwargs.get("D") is not None: - kwargs["D"] = kwargs["D"][0] - + # Alias "resolution" to "D" + kwargs["D"] = resolution[0] with Session() as lib: with lib.virtualfile_out(kind="grid", fname=outgrid) as voutgrd: kwargs["G"] = voutgrd diff --git a/pygmt/src/select.py b/pygmt/src/select.py index c54c2fdf3a5..21d2c856217 100644 --- a/pygmt/src/select.py +++ b/pygmt/src/select.py @@ -22,7 +22,6 @@ @use_alias( A="area_thresh", C="dist2pt", - D="resolution", F="polygon", G="gridmask", I="reverse", @@ -48,7 +47,7 @@ def select( data=None, output_type: Literal["pandas", "numpy", "file"] = "pandas", outfile: str | None = None, - resolution: Literal["full", "high", "intermediate", "low", "crude"] = "low", # noqa: ARG001 + resolution: Literal["full", "high", "intermediate", "low", "crude"] = "low", **kwargs, ) -> pd.DataFrame | np.ndarray | None: r""" @@ -203,9 +202,8 @@ def select( >>> # longitudes 246 and 247 and latitudes 20 and 21 >>> out = pygmt.select(data=ship_data, region=[246, 247, 20, 21]) """ - # Resolution - if kwargs.get("D") is not None: - kwargs["D"] = kwargs["D"][0] + # Alias "resolution" to "D" + kwargs["D"] = resolution[0] output_type = validate_output_table_type(output_type, outfile=outfile) From 5e1ebe82aa7a3c3d1f0364898bf4208071d7b1a6 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 29 Oct 2024 13:01:11 +0800 Subject: [PATCH 04/11] Move the parser function into _common.py --- pygmt/src/_common.py | 59 +++++++++++++++++++++++++++++++++++++++- pygmt/src/coast.py | 12 ++++---- pygmt/src/grdlandmask.py | 7 +++-- pygmt/src/select.py | 6 ++-- 4 files changed, 71 insertions(+), 13 deletions(-) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index 96ae9791b71..b3842cc5cf9 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -3,8 +3,9 @@ """ from pathlib import Path -from typing import Any +from typing import Any, Literal +from pygmt.exceptions import GMTInvalidInput from pygmt.src.which import which @@ -39,3 +40,59 @@ def _data_geometry_is_point(data: Any, kind: str) -> bool: except FileNotFoundError: pass return False + + +def _parse_coastline_resolution( + resolution: Literal["auto", "full", "high", "intermediate", "low", "crude", None], + allow_auto: bool = False, +) -> str | None: + """ + 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. + allow_auto + Whether to allow the ``"auto"`` resolution. + + Returns + ------- + str or None + The parsed resolution value. + + Raises + ------ + GMTInvalidInput + If the resolution is invalid. + + Examples + -------- + >>> _parse_coastline_resolution("full") + "f" + >>> _parse_coastline_resolution("f") + "f" + >>> _parse_coastline_resolution("auto", allow_auto=True) + "a" + >>> _parse_coastline_resolution("invalid") + pygmt.exceptions.GMTInvalidInput: Invalid resolution: invalid. Valid values are ... + >>> _parse_coastline_resolution(None) + None + >>> _parse_coastline_resolution("auto") + pygmt.exceptions.GMTInvalidInput: Invalid resolution: auto. Valid values are ... + """ + if resolution is None: + return None + + valid_resolutions = {"full", "high", "intermediate", "low", "crude"} + if allow_auto: + valid_resolutions.add("auto") + if resolution not in {*valid_resolutions, *[res[0] for res in valid_resolutions]}: + msg = ( + f"Invalid resolution: {resolution}." + f"Valid values are {', '.join(valid_resolutions)}." + ) + raise GMTInvalidInput(msg) + return resolution[0] diff --git a/pygmt/src/coast.py b/pygmt/src/coast.py index fe0a034ec30..1c5cbfc147f 100644 --- a/pygmt/src/coast.py +++ b/pygmt/src/coast.py @@ -13,6 +13,7 @@ kwargs_to_strings, use_alias, ) +from pygmt.src._common import _parse_coastline_resolution __doctest_skip__ = ["coast"] @@ -41,8 +42,8 @@ def coast( self, resolution: Literal[ - "auto", "full", "high", "intermediate", "low", "crude" - ] = "auto", + "auto", "full", "high", "intermediate", "low", "crude", None + ] = None, **kwargs, ): r""" @@ -208,9 +209,8 @@ def coast( """At least one of the following parameters must be specified: lakes, land, water, rivers, borders, dcw, Q, or shorelines""" ) - - # Alias 'resolution' to "D". - kwargs["D"] = resolution[0] - + kwargs["D"] = kwargs.get( + "D", _parse_coastline_resolution(resolution, allow_auto=True) + ) with Session() as lib: lib.call_module(module="coast", args=build_arg_list(kwargs)) diff --git a/pygmt/src/grdlandmask.py b/pygmt/src/grdlandmask.py index b99b13497ad..a654a3b4e62 100644 --- a/pygmt/src/grdlandmask.py +++ b/pygmt/src/grdlandmask.py @@ -8,6 +8,7 @@ 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"] @@ -26,7 +27,7 @@ @kwargs_to_strings(I="sequence", R="sequence", N="sequence", E="sequence") def grdlandmask( outgrid: str | None = None, - resolution: Literal["full", "high", "intermediate", "low", "crude"] = "low", + resolution: Literal["full", "high", "intermediate", "low", "crude", None] = None, **kwargs, ) -> xr.DataArray | None: r""" @@ -99,8 +100,8 @@ def grdlandmask( if kwargs.get("I") is None or kwargs.get("R") is None: raise GMTInvalidInput("Both 'region' and 'spacing' must be specified.") - # Alias "resolution" to "D" - kwargs["D"] = resolution[0] + 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 diff --git a/pygmt/src/select.py b/pygmt/src/select.py index 21d2c856217..a0fe3f2de03 100644 --- a/pygmt/src/select.py +++ b/pygmt/src/select.py @@ -14,6 +14,7 @@ use_alias, validate_output_table_type, ) +from pygmt.src._common import _parse_coastline_resolution __doctest_skip__ = ["select"] @@ -47,7 +48,7 @@ def select( data=None, output_type: Literal["pandas", "numpy", "file"] = "pandas", outfile: str | None = None, - resolution: Literal["full", "high", "intermediate", "low", "crude"] = "low", + resolution: Literal["full", "high", "intermediate", "low", "crude", None] = None, **kwargs, ) -> pd.DataFrame | np.ndarray | None: r""" @@ -202,8 +203,7 @@ def select( >>> # longitudes 246 and 247 and latitudes 20 and 21 >>> out = pygmt.select(data=ship_data, region=[246, 247, 20, 21]) """ - # Alias "resolution" to "D" - kwargs["D"] = resolution[0] + kwargs["D"] = kwargs.get("D", _parse_coastline_resolution(resolution)) output_type = validate_output_table_type(output_type, outfile=outfile) From 2cbe6c9e7acbb4c87c392b972d7b744e514415d6 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 29 Oct 2024 13:10:53 +0800 Subject: [PATCH 05/11] Fix grdlandmask and improve docstrings --- pygmt/src/grdlandmask.py | 11 +++++++---- pygmt/src/select.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pygmt/src/grdlandmask.py b/pygmt/src/grdlandmask.py index a654a3b4e62..e00594e5054 100644 --- a/pygmt/src/grdlandmask.py +++ b/pygmt/src/grdlandmask.py @@ -54,9 +54,10 @@ def grdlandmask( 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. 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. + between levels. 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. bordervalues : bool, str, float, or list Nodes that fall exactly on a polygon boundary should be considered to be outside the polygon [Default considers them to be @@ -100,7 +101,9 @@ def grdlandmask( if kwargs.get("I") is None or kwargs.get("R") is None: raise GMTInvalidInput("Both 'region' and 'spacing' must be specified.") - kwargs["D"] = kwargs.get("D", _parse_coastline_resolution(resolution)) + kwargs["D"] = kwargs.get( + "D", _parse_coastline_resolution(resolution, allow_auto=True) + ) with Session() as lib: with lib.virtualfile_out(kind="grid", fname=outgrid) as voutgrd: diff --git a/pygmt/src/select.py b/pygmt/src/select.py index a0fe3f2de03..43029bfebc4 100644 --- a/pygmt/src/select.py +++ b/pygmt/src/select.py @@ -123,7 +123,7 @@ def select( ``"high"``, ``"intermediate"``, ``"low"``, and ``"crude"``, which drops by 80% between levels. 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. + resolution is selected. If ``None``, the low resolution is used by default. 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. From e24bbd8af00f0720907337bb3953f817b6040698 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 29 Oct 2024 13:20:14 +0800 Subject: [PATCH 06/11] Fix doctests --- pygmt/src/_common.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index b3842cc5cf9..044d4662c81 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -60,8 +60,7 @@ def _parse_coastline_resolution( Returns ------- - str or None - The parsed resolution value. + The parsed single-letter resolution or ``None``. Raises ------ @@ -71,17 +70,20 @@ def _parse_coastline_resolution( Examples -------- >>> _parse_coastline_resolution("full") - "f" + 'f' >>> _parse_coastline_resolution("f") - "f" + 'f' >>> _parse_coastline_resolution("auto", allow_auto=True) - "a" - >>> _parse_coastline_resolution("invalid") - pygmt.exceptions.GMTInvalidInput: Invalid resolution: invalid. Valid values are ... + 'a' >>> _parse_coastline_resolution(None) - None + >>> _parse_coastline_resolution("invalid") + Traceback (most recent call last): + ... + pygmt.exceptions.GMTInvalidInput: Invalid resolution: 'invalid'. Valid values ... >>> _parse_coastline_resolution("auto") - pygmt.exceptions.GMTInvalidInput: Invalid resolution: auto. Valid values are ... + Traceback (most recent call last): + ... + pygmt.exceptions.GMTInvalidInput: Invalid resolution: 'auto'. Valid values ... """ if resolution is None: return None @@ -91,7 +93,7 @@ def _parse_coastline_resolution( valid_resolutions.add("auto") if resolution not in {*valid_resolutions, *[res[0] for res in valid_resolutions]}: msg = ( - f"Invalid resolution: {resolution}." + f"Invalid resolution: '{resolution}'. " f"Valid values are {', '.join(valid_resolutions)}." ) raise GMTInvalidInput(msg) From af1fb75497287f3fe357fb204e539c62657874ce Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 24 May 2025 13:05:26 +0800 Subject: [PATCH 07/11] Reorder functions --- pygmt/src/_common.py | 116 +++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index e08a67ec913..a8df9510928 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -44,64 +44,6 @@ def _data_geometry_is_point(data: Any, kind: str) -> bool: return False -def _parse_coastline_resolution( - resolution: Literal["auto", "full", "high", "intermediate", "low", "crude", None], - allow_auto: bool = False, -) -> str | None: - """ - 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. - allow_auto - Whether to allow the ``"auto"`` resolution. - - Returns - ------- - The parsed single-letter resolution or ``None``. - - Raises - ------ - GMTInvalidInput - If the resolution is invalid. - - Examples - -------- - >>> _parse_coastline_resolution("full") - 'f' - >>> _parse_coastline_resolution("f") - 'f' - >>> _parse_coastline_resolution("auto", allow_auto=True) - 'a' - >>> _parse_coastline_resolution(None) - >>> _parse_coastline_resolution("invalid") - Traceback (most recent call last): - ... - pygmt.exceptions.GMTInvalidInput: Invalid resolution: 'invalid'. Valid values ... - >>> _parse_coastline_resolution("auto") - Traceback (most recent call last): - ... - pygmt.exceptions.GMTInvalidInput: Invalid resolution: 'auto'. Valid values ... - """ - if resolution is None: - return None - - valid_resolutions = {"full", "high", "intermediate", "low", "crude"} - if allow_auto: - valid_resolutions.add("auto") - if resolution not in {*valid_resolutions, *[res[0] for res in valid_resolutions]}: - msg = ( - f"Invalid resolution: '{resolution}'. " - f"Valid values are {', '.join(valid_resolutions)}." - ) - raise GMTInvalidInput(msg) - return resolution[0] - - class _FocalMechanismConventionCode(StrEnum): """ Enum to handle focal mechanism convention codes. @@ -309,3 +251,61 @@ def from_params( f"{', '.join(params)}." ) raise GMTInvalidInput(msg) + + +def _parse_coastline_resolution( + resolution: Literal["auto", "full", "high", "intermediate", "low", "crude", None], + allow_auto: bool = False, +) -> str | None: + """ + 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. + allow_auto + Whether to allow the ``"auto"`` resolution. + + Returns + ------- + The parsed single-letter resolution or ``None``. + + Raises + ------ + GMTInvalidInput + If the resolution is invalid. + + Examples + -------- + >>> _parse_coastline_resolution("full") + 'f' + >>> _parse_coastline_resolution("f") + 'f' + >>> _parse_coastline_resolution("auto", allow_auto=True) + 'a' + >>> _parse_coastline_resolution(None) + >>> _parse_coastline_resolution("invalid") + Traceback (most recent call last): + ... + pygmt.exceptions.GMTInvalidInput: Invalid resolution: 'invalid'. Valid values ... + >>> _parse_coastline_resolution("auto") + Traceback (most recent call last): + ... + pygmt.exceptions.GMTInvalidInput: Invalid resolution: 'auto'. Valid values ... + """ + if resolution is None: + return None + + valid_resolutions = {"full", "high", "intermediate", "low", "crude"} + if allow_auto: + valid_resolutions.add("auto") + if resolution not in {*valid_resolutions, *[res[0] for res in valid_resolutions]}: + msg = ( + f"Invalid resolution: '{resolution}'. " + f"Valid values are {', '.join(valid_resolutions)}." + ) + raise GMTInvalidInput(msg) + return resolution[0] From c4b9692f4214a1824c5a9719dbf3066e6095afba Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 24 May 2025 13:26:31 +0800 Subject: [PATCH 08/11] Refactor function --- pygmt/src/_common.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index a8df9510928..ecfd4404190 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -258,14 +258,15 @@ def _parse_coastline_resolution( allow_auto: bool = False, ) -> str | None: """ - Parse the resolution parameter for coastline-related functions. + 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. + and ``"crude"``, which drops by 80% between levels. ``None`` means using the + default resolution. allow_auto Whether to allow the ``"auto"`` resolution. @@ -296,16 +297,24 @@ def _parse_coastline_resolution( ... pygmt.exceptions.GMTInvalidInput: Invalid resolution: 'auto'. Valid values ... """ - if resolution is None: - return None - - valid_resolutions = {"full", "high", "intermediate", "low", "crude"} + _valid_res = { + "full": "f", + "high": "h", + "intermediate": "i", + "low": "l", + "crude": "c", + None: None, + } if allow_auto: - valid_resolutions.add("auto") - if resolution not in {*valid_resolutions, *[res[0] for res in valid_resolutions]}: - msg = ( - f"Invalid resolution: '{resolution}'. " - f"Valid values are {', '.join(valid_resolutions)}." - ) - raise GMTInvalidInput(msg) - return resolution[0] + _valid_res["auto"] = "a" + + if resolution in _valid_res: # Long form arguments. + return _valid_res[resolution] + if resolution in _valid_res.values(): # Short form arguments. + return resolution + + msg = ( + f"Invalid resolution: '{resolution}'. " + f"Valid values are {', '.join(_valid_res.keys())}." + ) + raise GMTInvalidInput(msg) From 4c648595026984d650068e4c357fb899af8cd8b2 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 24 May 2025 14:07:51 +0800 Subject: [PATCH 09/11] Actually, auto is always supported --- pygmt/src/_common.py | 21 ++++++--------------- pygmt/src/coast.py | 4 +--- pygmt/src/grdlandmask.py | 17 +++++++++-------- pygmt/src/select.py | 20 ++++++++++++-------- 4 files changed, 28 insertions(+), 34 deletions(-) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index ecfd4404190..c58cd433636 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -255,8 +255,7 @@ def from_params( def _parse_coastline_resolution( resolution: Literal["auto", "full", "high", "intermediate", "low", "crude", None], - allow_auto: bool = False, -) -> str | None: +) -> Literal["a", "f", "h", "i", "l", "c", None]: """ Parse the 'resolution' parameter for coastline-related functions. @@ -265,14 +264,13 @@ def _parse_coastline_resolution( 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. ``None`` means using the - default resolution. - allow_auto - Whether to allow the ``"auto"`` resolution. + 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 parsed single-letter resolution or ``None``. + The single-letter resolution code or ``None``. Raises ------ @@ -285,19 +283,14 @@ def _parse_coastline_resolution( 'f' >>> _parse_coastline_resolution("f") 'f' - >>> _parse_coastline_resolution("auto", allow_auto=True) - 'a' >>> _parse_coastline_resolution(None) >>> _parse_coastline_resolution("invalid") Traceback (most recent call last): ... pygmt.exceptions.GMTInvalidInput: Invalid resolution: 'invalid'. Valid values ... - >>> _parse_coastline_resolution("auto") - Traceback (most recent call last): - ... - pygmt.exceptions.GMTInvalidInput: Invalid resolution: 'auto'. Valid values ... """ _valid_res = { + "auto": "a", "full": "f", "high": "h", "intermediate": "i", @@ -305,8 +298,6 @@ def _parse_coastline_resolution( "crude": "c", None: None, } - if allow_auto: - _valid_res["auto"] = "a" if resolution in _valid_res: # Long form arguments. return _valid_res[resolution] diff --git a/pygmt/src/coast.py b/pygmt/src/coast.py index eb174e50595..52329f14a73 100644 --- a/pygmt/src/coast.py +++ b/pygmt/src/coast.py @@ -211,9 +211,7 @@ def coast( ) raise GMTInvalidInput(msg) - kwargs["D"] = kwargs.get( - "D", _parse_coastline_resolution(resolution, allow_auto=True) - ) + kwargs["D"] = kwargs.get("D", _parse_coastline_resolution(resolution)) with Session() as lib: lib.call_module(module="coast", args=build_arg_list(kwargs)) diff --git a/pygmt/src/grdlandmask.py b/pygmt/src/grdlandmask.py index 4513578ce8d..110455cb3ee 100644 --- a/pygmt/src/grdlandmask.py +++ b/pygmt/src/grdlandmask.py @@ -28,7 +28,9 @@ @kwargs_to_strings(I="sequence", R="sequence", N="sequence", E="sequence") def grdlandmask( outgrid: PathLike | None = None, - resolution: Literal["full", "high", "intermediate", "low", "crude", None] = None, + resolution: Literal[ + "auto", "full", "high", "intermediate", "low", "crude", None + ] = None, **kwargs, ) -> xr.DataArray | None: r""" @@ -51,10 +53,11 @@ def grdlandmask( {region} {area_thresh} 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. Note that because the coastlines differ in details a node in a + 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. @@ -105,9 +108,7 @@ def grdlandmask( msg = "Both 'region' and 'spacing' must be specified." raise GMTInvalidInput(msg) - kwargs["D"] = kwargs.get( - "D", _parse_coastline_resolution(resolution, allow_auto=True) - ) + kwargs["D"] = kwargs.get("D", _parse_coastline_resolution(resolution)) with Session() as lib: with lib.virtualfile_out(kind="grid", fname=outgrid) as voutgrd: diff --git a/pygmt/src/select.py b/pygmt/src/select.py index 7a42566f2f7..ff3307f4d9a 100644 --- a/pygmt/src/select.py +++ b/pygmt/src/select.py @@ -49,7 +49,9 @@ def select( data: PathLike | TableLike | None = None, output_type: Literal["pandas", "numpy", "file"] = "pandas", outfile: PathLike | None = None, - resolution: Literal["full", "high", "intermediate", "low", "crude", None] = None, + resolution: Literal[ + "auto", "full", "high", "intermediate", "low", "crude", None + ] = None, **kwargs, ) -> pd.DataFrame | np.ndarray | None: r""" @@ -118,13 +120,6 @@ def select( ` *polygonfile*. For spherical polygons (lon, lat), make sure no consecutive points are separated by 180 degrees or more in longitude. - 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. 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. If ``None``, the low resolution is used by default. 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. @@ -153,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 From 8a57f61ca7c257f2d745a6ef8cec884558e013bb Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 24 May 2025 14:17:15 +0800 Subject: [PATCH 10/11] Fix _parse_coastline_resolution --- pygmt/src/_common.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index c58cd433636..3f285c65b24 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -289,23 +289,18 @@ def _parse_coastline_resolution( ... pygmt.exceptions.GMTInvalidInput: Invalid resolution: 'invalid'. Valid values ... """ - _valid_res = { - "auto": "a", - "full": "f", - "high": "h", - "intermediate": "i", - "low": "l", - "crude": "c", - None: None, - } + 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] - if resolution in _valid_res: # Long form arguments. - return _valid_res[resolution] - if resolution in _valid_res.values(): # Short form arguments. + if resolution in {_res[0] for _res in _valid_res}: # Short-form arguments. return resolution msg = ( - f"Invalid resolution: '{resolution}'. " - f"Valid values are {', '.join(_valid_res.keys())}." + f"Invalid resolution: '{resolution}'. Valid values are {', '.join(_valid_res)}." ) raise GMTInvalidInput(msg) From 5f6d3a1ef7c54b5dbbc3cd21c8175179badbb6f0 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 24 May 2025 14:21:35 +0800 Subject: [PATCH 11/11] Fix typing issue --- pygmt/src/_common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/src/_common.py b/pygmt/src/_common.py index 3f285c65b24..53aab7fb66a 100644 --- a/pygmt/src/_common.py +++ b/pygmt/src/_common.py @@ -295,10 +295,10 @@ def _parse_coastline_resolution( _valid_res = {"auto", "full", "high", "intermediate", "low", "crude"} if resolution in _valid_res: # Long-form arguments. - return resolution[0] + return resolution[0] # type: ignore[return-value] if resolution in {_res[0] for _res in _valid_res}: # Short-form arguments. - return resolution + return resolution # type: ignore[return-value] msg = ( f"Invalid resolution: '{resolution}'. Valid values are {', '.join(_valid_res)}."