Access common web basemap tile services via GDAL. Returns either WMTS connection strings (for ESRI services with WMTS support) or TMS minidriver XML for XYZ tile services.
basemap <- function(name = NULL, url = NULL, api_key = NULL, tile_level = 19L, bands = 3L) {
providers <- list(
# OpenStreetMap (no key)
OpenStreetMap = "https://tile.openstreetmap.org/${z}/${x}/${y}.png",
`OpenStreetMap.DE` = "https://tile.openstreetmap.de/${z}/${x}/${y}.png",
`OpenStreetMap.France` = "https://a.tile.openstreetmap.fr/osmfr/${z}/${x}/${y}.png",
`OpenStreetMap.HOT` = "https://a.tile.openstreetmap.fr/hot/${z}/${x}/${y}.png",
OpenTopoMap = "https://a.tile.opentopomap.org/${z}/${x}/${y}.png",
# CartoDB (no key)
`CartoDB.Positron` = "https://a.basemaps.cartocdn.com/light_all/${z}/${x}/${y}.png",
`CartoDB.PositronNoLabels` = "https://a.basemaps.cartocdn.com/light_nolabels/${z}/${x}/${y}.png",
`CartoDB.PositronOnlyLabels` = "https://a.basemaps.cartocdn.com/light_only_labels/${z}/${x}/${y}.png",
`CartoDB.DarkMatter` = "https://a.basemaps.cartocdn.com/dark_all/${z}/${x}/${y}.png",
`CartoDB.DarkMatterNoLabels` = "https://a.basemaps.cartocdn.com/dark_nolabels/${z}/${x}/${y}.png",
`CartoDB.DarkMatterOnlyLabels` = "https://a.basemaps.cartocdn.com/dark_only_labels/${z}/${x}/${y}.png",
`CartoDB.Voyager` = "https://a.basemaps.cartocdn.com/rastertiles/voyager/${z}/${x}/${y}.png",
`CartoDB.VoyagerNoLabels` = "https://a.basemaps.cartocdn.com/rastertiles/voyager_nolabels/${z}/${x}/${y}.png",
`CartoDB.VoyagerOnlyLabels` = "https://a.basemaps.cartocdn.com/rastertiles/voyager_only_labels/${z}/${x}/${y}.png",
# ESRI WMTS (no key)
`Esri.WorldImagery` = "WMTS:https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/WMTS/1.0.0/WMTSCapabilities.xml,layer=World_Imagery",
`Esri.WorldStreetMap` = "WMTS:https://services.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer/WMTS/1.0.0/WMTSCapabilities.xml,layer=World_Street_Map",
`Esri.WorldTopoMap` = "WMTS:https://services.arcgisonline.com/arcgis/rest/services/World_Topo_Map/MapServer/WMTS/1.0.0/WMTSCapabilities.xml,layer=World_Topo_Map",
`Esri.WorldTerrain` = "WMTS:https://services.arcgisonline.com/arcgis/rest/services/World_Terrain_Base/MapServer/WMTS/1.0.0/WMTSCapabilities.xml,layer=World_Terrain_Base",
`Esri.WorldShadedRelief` = "WMTS:https://services.arcgisonline.com/arcgis/rest/services/World_Shaded_Relief/MapServer/WMTS/1.0.0/WMTSCapabilities.xml,layer=World_Shaded_Relief",
`Esri.NatGeoWorldMap` = "WMTS:https://services.arcgisonline.com/arcgis/rest/services/NatGeo_World_Map/MapServer/WMTS/1.0.0/WMTSCapabilities.xml,layer=NatGeo_World_Map",
# ESRI TMS (no key)
`Esri.OceanBasemap` = "https://services.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer/tile/${z}/${y}/${x}",
`Esri.WorldGrayCanvas` = "https://services.arcgisonline.com/arcgis/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/${z}/${y}/${x}",
# Stadia (API key required)
`Stadia.AlidadeSmooth` = "https://tiles.stadiamaps.com/tiles/alidade_smooth/${z}/${x}/${y}.png",
`Stadia.AlidadeSmoothDark` = "https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/${z}/${x}/${y}.png",
`Stadia.OSMBright` = "https://tiles.stadiamaps.com/tiles/osm_bright/${z}/${x}/${y}.png",
`Stadia.Outdoors` = "https://tiles.stadiamaps.com/tiles/outdoors/${z}/${x}/${y}.png",
`Stadia.StamenToner` = "https://tiles.stadiamaps.com/tiles/stamen_toner/${z}/${x}/${y}.png",
`Stadia.StamenTonerBackground` = "https://tiles.stadiamaps.com/tiles/stamen_toner_background/${z}/${x}/${y}.png",
`Stadia.StamenTonerLines` = "https://tiles.stadiamaps.com/tiles/stamen_toner_lines/${z}/${x}/${y}.png",
`Stadia.StamenTonerLabels` = "https://tiles.stadiamaps.com/tiles/stamen_toner_labels/${z}/${x}/${y}.png",
`Stadia.StamenTonerLite` = "https://tiles.stadiamaps.com/tiles/stamen_toner_lite/${z}/${x}/${y}.png",
`Stadia.StamenWatercolor` = "https://tiles.stadiamaps.com/tiles/stamen_watercolor/${z}/${x}/${y}.png",
`Stadia.StamenTerrain` = "https://tiles.stadiamaps.com/tiles/stamen_terrain/${z}/${x}/${y}.png",
`Stadia.StamenTerrainBackground` = "https://tiles.stadiamaps.com/tiles/stamen_terrain_background/${z}/${x}/${y}.png",
`Stadia.StamenTerrainLabels` = "https://tiles.stadiamaps.com/tiles/stamen_terrain_labels/${z}/${x}/${y}.png",
# Thunderforest (API key required)
`Thunderforest.OpenCycleMap` = "https://tile.thunderforest.com/cycle/${z}/${x}/${y}.png",
`Thunderforest.Transport` = "https://tile.thunderforest.com/transport/${z}/${x}/${y}.png",
`Thunderforest.TransportDark` = "https://tile.thunderforest.com/transport-dark/${z}/${x}/${y}.png",
`Thunderforest.SpinalMap` = "https://tile.thunderforest.com/spinal-map/${z}/${x}/${y}.png",
`Thunderforest.Landscape` = "https://tile.thunderforest.com/landscape/${z}/${x}/${y}.png",
`Thunderforest.Outdoors` = "https://tile.thunderforest.com/outdoors/${z}/${x}/${y}.png",
`Thunderforest.Pioneer` = "https://tile.thunderforest.com/pioneer/${z}/${x}/${y}.png",
`Thunderforest.MobileAtlas` = "https://tile.thunderforest.com/mobile-atlas/${z}/${x}/${y}.png",
`Thunderforest.Neighbourhood` = "https://tile.thunderforest.com/neighbourhood/${z}/${x}/${y}.png"
)
needs_key <- c("Stadia", "Thunderforest")
if (!is.null(name)) {
name <- match.arg(name, names(providers))
tile_url <- providers[[name]]
if (startsWith(tile_url, "WMTS:")) return(tile_url)
provider <- strsplit(name, "\\.")[[1]][1]
if (provider %in% needs_key) {
if (is.null(api_key)) stop(sprintf("%s requires api_key", provider))
key_param <- if (provider == "Stadia") "api_key" else "apikey"
tile_url <- paste0(tile_url, "?", key_param, "=", api_key)
}
} else if (!is.null(url)) {
tile_url <- sub("/$", "", url)
if (!grepl("\\$\\{", tile_url)) tile_url <- paste0(tile_url, "/${z}/${x}/${y}.png")
} else stop("Provide 'name' or 'url'")
sprintf('<GDAL_WMS><Service name="TMS"><ServerUrl>%s</ServerUrl></Service><DataWindow><UpperLeftX>-20037508.34</UpperLeftX><UpperLeftY>20037508.34</UpperLeftY><LowerRightX>20037508.34</LowerRightX><LowerRightY>-20037508.34</LowerRightY><TileLevel>%d</TileLevel><TileCountX>1</TileCountX><TileCountY>1</TileCountY><YOrigin>top</YOrigin></DataWindow><Projection>EPSG:3857</Projection><BlockSizeX>256</BlockSizeX><BlockSizeY>256</BlockSizeY><BandsCount>%d</BandsCount><Cache/></GDAL_WMS>', tile_url, tile_level, bands)
}