Using geoviews tile sources offline?

Here’s how you can use tiles offline.

  1. First cache/convert the tiles.
from pathlib import Path
import numpy as np
from shapely import box
import cartopy.io.img_tiles as cimgt
import cartopy.crs as ccrs

from PIL import Image


def cache_tiles(
    tile_source,
    max_target_z=1,
    x_bounds=(-180, 180),
    y_bounds=(-90, 90),
    cache_dir="tiles",
):
    """
    Caches map tiles within specified bounds from a given tile source.

    Args:
        tile_source (str or cartopy.io.img_tiles.Tiles): The tile source to use for caching.
            It can be a string specifying a built-in tile source, or an instance of a custom tile source class.
        max_target_z (int, optional): The maximum zoom level to cache. Defaults to 1.
        x_bounds (tuple, optional): The longitudinal bounds of the tiles to cache. Defaults to (-180, 180).
        y_bounds (tuple, optional): The latitudinal bounds of the tiles to cache. Defaults to (-90, 90).
        cache_dir (str, optional): The directory to store the cached tiles. Defaults to "tiles".

    Returns:
        pathlib.Path: The path to the cache directory.
    """
    if not isinstance(tile_source, cimgt.GoogleWTS):
        tile_source = getattr(cimgt, tile_source)
    tiles = tile_source(cache=cache_dir)

    bbox = ccrs.GOOGLE_MERCATOR.transform_points(
        ccrs.PlateCarree(), x=np.array(x_bounds), y=np.array(y_bounds)
    )[
        :, :-1
    ].flatten()  # drop Z, then convert to x0, y0, x1, y1
    target_domain = box(*bbox)

    for target_z in range(max_target_z):
        tiles.image_for_domain(target_domain, target_z)
    return Path(cache_dir) / tile_source.__name__


def convert_tiles_cache(cache_dir):
    """
    Converts cached tiles from numpy format to PNG format.

    Args:
        cache_dir (str): The directory containing the cached tiles in numpy format.

    Returns:
        str: The format string representing the converted PNG tiles.
    """
    for np_path in Path(cache_dir).rglob("*.npy"):
        img = Image.fromarray(np.load(np_path))
        img_path = Path(str(np_path.with_suffix(".png")).replace("_", "/"))
        img_path.parent.mkdir(parents=True, exist_ok=True)
        img.save(img_path)

    tiles_fmt = str(cache_dir / "{X}" / "{Y}" / "{Z}.png")
    return tiles_fmt


tiles_dir = convert_tiles_cache(cache_tiles("OSM", max_target_z=6))

Here’s what the directory looks like:
image

  1. Then using it in GeoViews is effortless!
import geoviews as gv

gv.extension("bokeh")

gv.WMTS(tiles_dir).opts(global_extent=True)

At various zoom levels:

At deep zoom levels, it’ll result in white because there’s no data at that zoom level.

To prevent it from trying to access missing data, set max_zoom equal to your max_target_z

import geoviews as gv

gv.extension("bokeh")

gv.WMTS(tiles_dir).opts(global_extent=True, max_zoom=6)
1 Like