Skip to content

TYP: Add type hints to the Figure.tilemap method #3095

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 8 commits into from
Mar 20, 2024
118 changes: 52 additions & 66 deletions pygmt/src/tilemap.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
tilemap - Plot XYZ tile maps.
"""

from __future__ import annotations
from typing import Literal

from pygmt.clib import Session
from pygmt.datasets.tile_map import load_tile_map
from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias

try:
import rioxarray # noqa: F401
from xyzservices import TileProvider

_HAS_RIOXARRAY = True
except ImportError:
TileProvider = None
_HAS_RIOXARRAY = False


Expand All @@ -34,86 +36,72 @@
@kwargs_to_strings(c="sequence_comma", p="sequence") # R="sequence",
def tilemap(
self,
region,
zoom="auto",
source=None,
lonlat=True,
wait=0,
max_retries=2,
region: list,
zoom: int | Literal["auto"] = "auto",
source: TileProvider | str | None = None,
lonlat: bool = True,
wait: int = 0,
max_retries: int = 2,
zoom_adjust: int | None = None,
**kwargs,
):
r"""
Plot an XYZ tile map.

This method loads XYZ tile maps from a tile server or local file using
:func:`pygmt.datasets.load_tile_map` into a georeferenced form, and plots
the tiles as a basemap or overlay using :meth:`pygmt.Figure.grdimage`.
:func:`pygmt.datasets.load_tile_map` into a georeferenced form, and plots the tiles
as a basemap or overlay using :meth:`pygmt.Figure.grdimage`.

**Note**: By default, standard web map tiles served in a Spherical Mercator
(EPSG:3857) Cartesian format will be reprojected to a geographic coordinate
reference system (OGC:WGS84) and plotted with longitude/latitude bounds
when ``lonlat=True``. If reprojection is not desired, please set
``lonlat=False`` and provide Spherical Mercator (EPSG:3857) coordinates to
the ``region`` parameter.
reference system (OGC:WGS84) and plotted with longitude/latitude bounds when
``lonlat=True``. If reprojection is not desired, please set ``lonlat=False`` and
provide Spherical Mercator (EPSG:3857) coordinates to the ``region`` parameter.

{aliases}

Parameters
----------
region : list
The bounding box of the map in the form of a list [*xmin*, *xmax*,
*ymin*, *ymax*]. These coordinates should be in longitude/latitude if
``lonlat=True`` or Spherical Mercator (EPSG:3857) if ``lonlat=False``.

zoom : int or str
Optional. Level of detail. Higher levels (e.g. ``22``) mean a zoom
level closer to the Earth's surface, with more tiles covering a smaller
geographical area and thus more detail. Lower levels (e.g. ``0``) mean
a zoom level further from the Earth's surface, with less tiles covering
a larger geographical area and thus less detail [Default is
``"auto"`` to automatically determine the zoom level based on the
bounding box region extent].
region
The bounding box of the map in the form of a list [*xmin*, *xmax*, *ymin*,
*ymax*]. These coordinates should be in longitude/latitude if ``lonlat=True`` or
Spherical Mercator (EPSG:3857) if ``lonlat=False``.
zoom
Level of detail. Higher levels (e.g. ``22``) mean a zoom level closer to the
Earth's surface, with more tiles covering a smaller geographical area and thus
more detail. Lower levels (e.g. ``0``) mean a zoom level further from the
Earth's surface, with less tiles covering a larger geographical area and thus
less detail. Default is ``"auto"`` to automatically determine the zoom level
based on the bounding box region extent.

.. note::
The maximum possible zoom level may be smaller than ``22``, and depends on
what is supported by the chosen web tile provider source.

source : xyzservices.TileProvider or str
Optional. The tile source: web tile provider or path to a local file.
Provide either:

- A web tile provider in the form of a
:class:`xyzservices.TileProvider` object. See
:doc:`Contextily providers <contextily:providers_deepdive>` for a
list of tile providers [Default is
``xyzservices.providers.OpenStreetMap.HOT``, i.e. OpenStreetMap
Humanitarian web tiles].
- A web tile provider in the form of a URL. The placeholders for the
XYZ in the URL need to be {{x}}, {{y}}, {{z}}, respectively. E.g.
source
The tile source: web tile provider or path to a local file. Provide either:

- A web tile provider in the form of a :class:`xyzservices.TileProvider` object.
See :doc:`Contextily providers <contextily:providers_deepdive>` for a list of
tile providers. Default is ``xyzservices.providers.OpenStreetMap.HOT``, i.e.
OpenStreetMap Humanitarian web tiles.
- A web tile provider in the form of a URL. The placeholders for the XYZ in the
Copy link
Member Author

Choose a reason for hiding this comment

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

I have to use double curly brackets here {{x}}, otherwise the fmt_docstrings decorator will report KeyError exception (see https://github.com/GenericMappingTools/pygmt/actions/runs/8225955536/job/22491725235?pr=3095).

The {{x}} is correctly shown as {x} on the website.

URL need to be ``{{x}}``, ``{{y}}``, ``{{z}}``, respectively. E.g.
``https://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png``.
- A local file path. The file is read with
:doc:`rasterio <rasterio:index>` and all bands are loaded into the
basemap. See
- A local file path. The file is read with :doc:`rasterio <rasterio:index>` and
all bands are loaded into the basemap. See
:doc:`contextily:working_with_local_files`.

.. important::
Tiles are assumed to be in the Spherical Mercator projection (EPSG:3857).

lonlat : bool
Optional. If ``False``, coordinates in ``region`` are assumed to be
Spherical Mercator as opposed to longitude/latitude [Default is
``True``].

wait : int
Optional. If the tile API is rate-limited, the number of seconds to
wait between a failed request and the next try [Default is ``0``].

max_retries : int
Optional. Total number of rejected requests allowed before contextily
will stop trying to fetch more tiles from a rate-limited API [Default
is ``2``].

lonlat
If ``False``, coordinates in ``region`` are assumed to be Spherical Mercator as
opposed to longitude/latitude.
wait
If the tile API is rate-limited, the number of seconds to wait between a failed
request and the next try.
max_retries
Total number of rejected requests allowed before contextily will stop trying to
fetch more tiles from a rate-limited API.
zoom_adjust
The amount to adjust a chosen zoom level if it is chosen automatically. Values
outside of -1 to 1 are not recommended as they can lead to slow execution.
Expand All @@ -128,18 +116,16 @@ def tilemap(
------
ImportError
If ``rioxarray`` is not installed. Follow
:doc:`install instructions for rioxarray <rioxarray:installation>`,
(e.g. via ``python -m pip install rioxarray``) before using this
function.
:doc:`install instructions for rioxarray <rioxarray:installation>`, (e.g. via
``python -m pip install rioxarray``) before using this function.
"""
kwargs = self._preprocess(**kwargs)

if not _HAS_RIOXARRAY:
raise ImportError(
"Package `rioxarray` is required to be installed to use this function. "
"Please use `python -m pip install rioxarray` or "
"`mamba install -c conda-forge rioxarray` "
"to install the package."
"`mamba install -c conda-forge rioxarray` to install the package."
)

raster = load_tile_map(
Expand All @@ -152,14 +138,14 @@ def tilemap(
zoom_adjust=zoom_adjust,
)

# Reproject raster from Spherical Mercator (EPSG:3857) to
# lonlat (OGC:CRS84) if bounding box region was provided in lonlat
# Reproject raster from Spherical Mercator (EPSG:3857) to lonlat (OGC:CRS84) if
# bounding box region was provided in lonlat
if lonlat and raster.rio.crs == "EPSG:3857":
raster = raster.rio.reproject(dst_crs="OGC:CRS84")
raster.gmt.gtype = 1 # set to geographic type

# Only set region if no_clip is None or False, so that plot is clipped to
# exact bounding box region
# Only set region if no_clip is None or False, so that plot is clipped to exact
# bounding box region
if kwargs.get("N") in [None, False]:
kwargs["R"] = "/".join(str(coordinate) for coordinate in region)

Expand Down