diff --git a/examples/gallery/maps/tilemaps.py b/examples/gallery/maps/tilemaps.py index 92d0623b014..0314d4f85f6 100644 --- a/examples/gallery/maps/tilemaps.py +++ b/examples/gallery/maps/tilemaps.py @@ -2,8 +2,8 @@ Tile maps ========= -The :meth:`pygmt.Figure.tilemap` method allows to plot -tiles from a tile server or local file as a basemap or overlay. +The :meth:`pygmt.Figure.tilemap` method allows to plot tiles from a tile server or +local file as a basemap or overlay. """ # %% @@ -15,14 +15,13 @@ fig.tilemap( region=[-157.84, -157.8, 21.255, 21.285], projection="M12c", - # Set level of details (0-22) - # Higher levels mean a zoom level closer to the Earth's - # surface with more tiles covering a smaller - # geographic area and thus more details and vice versa - # Please note, not all zoom levels are always available + # Set level of details (0-22). + # Higher levels mean a zoom level closer to the Earth's surface with more tiles + # covering a smaller geographic area and thus more details and vice versa. + # Please note, not all zoom levels are always available. zoom=14, # Use tiles from OpenStreetMap tile server - source="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + source="https://tile.openstreetmap.org/{z}/{x}/{y}.png", frame=Axis(annot=True, tick=True, grid=True), ) @@ -30,9 +29,9 @@ # %% # It's also possible to use tiles provided via the -# `contextily `__ -# library. See :doc:`Contextily providers ` -# for a list of possible tilemap options. +# `contextily `__ library. See +# :doc:`Contextily providers ` for a list of possible +# tilemap options. fig = pygmt.Figure() fig.tilemap( diff --git a/pygmt/datasets/tile_map.py b/pygmt/datasets/tile_map.py index 697327a0870..419bb48de03 100644 --- a/pygmt/datasets/tile_map.py +++ b/pygmt/datasets/tile_map.py @@ -6,6 +6,10 @@ from collections.abc import Sequence from typing import Literal +from packaging.version import Version +from pygmt._show_versions import __version__ as _pygmt_version +from pygmt.exceptions import GMTParameterError + try: import contextily from rasterio.crs import CRS @@ -40,6 +44,7 @@ def load_tile_map( wait: int = 0, max_retries: int = 2, zoom_adjust: int | None = None, + headers: dict[str, str] | None = None, ) -> xr.DataArray: """ Load a georeferenced raster tile map from XYZ tile providers. @@ -75,7 +80,7 @@ def load_tile_map( 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. - ``https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png``. + ``https://tile.openstreetmap.org/{z}/{x}/{y}.png``. - A local file path. The file is read with :doc:`rasterio ` and all bands are loaded into the basemap. See :doc:`contextily:working_with_local_files`. @@ -98,6 +103,14 @@ def load_tile_map( 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. + headers + HTTP headers to include with requests to the tile server. This can be useful + for authentication or to set a custom User-Agent. When supported by + ``contextily`` (>=1.7.0), PyGMT sets a default ``User-Agent`` header like + ``PyGMT/vX.Y.Z (+https://www.pygmt.org)``. + + .. note:: + Requires ``contextily>=1.7.0``. Returns ------- @@ -164,6 +177,22 @@ def load_tile_map( "zoom_adjust": zoom_adjust, } + # TODO(contextily>=1.7.0): Remove once contextily>=1.7.0 is required. + # The 'headers' parameter was added in contextily v1.7.0 + if Version(contextily.__version__) < Version("1.7.0"): + if headers is not None: + raise GMTParameterError( + reason="The 'headers' parameter requires contextily>=1.7.0." + ) + else: + # Set default HTTP headers. + default_ua = f"PyGMT/{_pygmt_version} (+https://www.pygmt.org)" + if headers is None: + headers = {"User-Agent": default_ua} + elif not any(key.lower() == "user-agent" for key in headers): + headers = {**headers, "User-Agent": default_ua} + contextily_kwargs["headers"] = headers + west, east, south, north = region image, extent = contextily.bounds2img( w=west, s=south, e=east, n=north, **contextily_kwargs diff --git a/pygmt/src/tilemap.py b/pygmt/src/tilemap.py index 2468b315020..706dd607bb6 100644 --- a/pygmt/src/tilemap.py +++ b/pygmt/src/tilemap.py @@ -29,6 +29,7 @@ def tilemap( wait: int = 0, max_retries: int = 2, zoom_adjust: int | None = None, + headers: dict[str, str] | None = None, monochrome: bool = False, no_clip: bool = False, projection: str | None = None, @@ -89,7 +90,7 @@ def tilemap( 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. - ``https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png``. + ``https://tile.openstreetmap.org/{z}/{x}/{y}.png``. - A local file path. The file is read with :doc:`rasterio ` and all bands are loaded into the basemap. See :doc:`contextily:working_with_local_files`. @@ -108,6 +109,14 @@ def tilemap( 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. + headers + HTTP headers to include with requests to the tile server. This can be useful + for authentication or to set a custom User-Agent. When supported by + ``contextily`` (>=1.7.0), PyGMT sets a default ``User-Agent`` header like + ``PyGMT/vX.Y.Z (+https://www.pygmt.org)``. + + .. note:: + Requires ``contextily>=1.7.0``. kwargs : dict Extra keyword arguments to pass to :meth:`pygmt.Figure.grdimage`. """ @@ -120,6 +129,7 @@ def tilemap( wait=wait, max_retries=max_retries, zoom_adjust=zoom_adjust, + headers=headers, ) if lonlat: raster.gmt.gtype = GridType.GEOGRAPHIC