A full look into providers objects

One of the benefits of contextily is that it interfaces with the popular XYZ tiling standard. This means that any tiles provided through this format can be accessed. To make your life easier, contextily comes with a set of providers already included that makes including their maps much more straightforward and does not require you know any of the pesky details of the interface. These are all stored in the contextily.providers submodule, which also defines the specification contextily uses to store these so you can mimic it with an alternative provider that may not be built-in. This notebook will take a look at the submodule, illustrating how it works, what’s available and how you can expand it if needed.

[1]:
import contextily as ctx

import matplotlib.pyplot as plt

Choosing a web tile provider

By default, if the user does not specify anything in this respect, the Stamen Terrain background is used:

[2]:
# we are using an extent around Mexico for the examples
extent = (-12600000, -10300000, 1800000, 3800000)
[3]:
fig, ax = plt.subplots(figsize=(8, 8))
ax.axis(extent)
ctx.add_basemap(ax)
_images/providers_deepdive_4_0.png

The default can be modified by specifying a different provider with the source keyword.

For example, to use Stamen’s Toner Lite theme:

[4]:
fig, ax = plt.subplots(figsize=(8, 8))
ax.axis(extent)
ctx.add_basemap(ax, source=ctx.providers.Stamen.TonerLite)
_images/providers_deepdive_6_0.png

Or the main theme by OpenStreetMap:

[5]:
fig, ax = plt.subplots(figsize=(8, 8))
ax.axis(extent)
ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik)
_images/providers_deepdive_8_0.png

What is this “provider” object ?

The provider objects being passed to add_basemap are stored as dictionaries (with attribute access for easy access), so we can explore them in Python (or using tab completion in an interactive session!):

[6]:
ctx.providers.keys()
[6]:
dict_keys(['OpenStreetMap', 'OpenSeaMap', 'OpenPtMap', 'OpenTopoMap', 'OpenRailwayMap', 'OpenFireMap', 'SafeCast', 'Thunderforest', 'OpenMapSurfer', 'Hydda', 'MapBox', 'Stamen', 'Esri', 'OpenWeatherMap', 'HERE', 'FreeMapSK', 'MtbMap', 'CartoDB', 'HikeBike', 'BasemapAT', 'nlmaps', 'NASAGIBS', 'NLS', 'JusticeMap', 'Wikimedia', 'GeoportailFrance', 'OneMapSG'])

Most of those providers have themselves multiple options. For example, OpenStreetMap provides multiple background styles:

[7]:
ctx.providers.OpenStreetMap.keys()
[7]:
dict_keys(['Mapnik', 'DE', 'CH', 'France', 'HOT', 'BZH'])

Looking at a single one of those options, for example “Mapnik” (the default OpenStreetMap background), we can see this is a TileProvider object, which behaves as a dict:

[8]:
type(ctx.providers.OpenStreetMap.Mapnik)
[8]:
contextily._providers.TileProvider

We can explore its contents:

[9]:
ctx.providers.OpenStreetMap.Mapnik
[9]:
{'url': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
 'max_zoom': 19,
 'attribution': '(C) OpenStreetMap contributors',
 'name': 'OpenStreetMap.Mapnik'}

A “provider object” is then a dictionary with a few required entries, such as the url (required, with {z}/{x}/{y} to be replaced), with some additional metadata information (optional). These can be the attribution text to be used on the plot (attribution), or the maximum zoom level available for these tiles (max_zoom). When this information is available, contextily is smart enough to access and use it automatically.

Which providers are available by default?

The providers definitions that are shipped with contextily in the contextily.providers submodule, are coming from the `leaflet-providers <https://github.com/leaflet-extras/leaflet-providers>`__ package, an extension to javascript mapping library Leaflet that contains configurations for various free tile providers. Thus, all providers that are listed on their preview (http://leaflet-extras.github.io/leaflet-providers/preview/) should also be available in contextily.

Specifying options for a provider

Some providers require additional information, such as an API access key. This can be specified by calling the provider object. Any keyword specified will override the default value in the provider object.

For example, the OpenWeatherMap requires an API key:

[10]:
ctx.providers.OpenWeatherMap.Clouds
[10]:
{'url': 'http://{s}.tile.openweathermap.org/map/{variant}/{z}/{x}/{y}.png?appid={apiKey}',
 'max_zoom': 19,
 'attribution': 'Map data (C) OpenWeatherMap',
 'apiKey': '<insert your api key here>',
 'opacity': 0.5,
 'variant': 'clouds',
 'name': 'OpenWeatherMap.Clouds'}

We can specify this API key by calling the object:

[11]:
ctx.providers.OpenWeatherMap.Clouds(apiKey="my-private-api-key")
[11]:
{'url': 'http://{s}.tile.openweathermap.org/map/{variant}/{z}/{x}/{y}.png?appid={apiKey}',
 'max_zoom': 19,
 'attribution': 'Map data (C) OpenWeatherMap',
 'apiKey': 'my-private-api-key',
 'opacity': 0.5,
 'variant': 'clouds',
 'name': 'OpenWeatherMap.Clouds'}

This can then be specified where a source is expected. For example:

ctx.add_basemap(ax, source=ctx.providers.OpenWeatherMap.Clouds(apiKey="my-private-api-key"))

Manually specifying a provider

Since the provider definition is basically nothing more than an url with the {z}/{x}/{y} format, you can also specify such an url manually, in case the provider of your choice is not available in contextily (the provider is expected to follow the standard XYZ tile protocol):

[12]:
fig, ax = plt.subplots(figsize=(8, 8))
ax.axis(extent)
# using the url from `ctx.providers.OpenStreetMap.HOT` as example
ctx.add_basemap(ax, source='https://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png')
_images/providers_deepdive_29_0.png

In addition, you could also construct a dictionary with the "url" key and additional information yourself, for example to provide a default attribution text for all your plots.

Overview of built-in providers

Let us create a flat dictionary of the built-in providers:

[13]:
providers = {}

def get_providers(provider):
    if "url" in provider:
        providers[provider['name']] = provider
    else:
        for prov in provider.values():
            get_providers(prov)

get_providers(ctx.providers)

This results in quite a few of them already available:

[14]:
len(providers)
[14]:
141

For this illustration, let’s single out the following ones:

[15]:
selection = ['OpenStreetMap.Mapnik',
             'OpenTopoMap',
             'Stamen.Toner',
             'Stamen.TonerLite',
             'Stamen.Terrain',
             'Stamen.TerrainBackground',
             'Stamen.Watercolor',
             'NASAGIBS.ViirsEarthAtNight2012',
             'CartoDB.Positron',
             'CartoDB.Voyager'
            ]
[19]:
fig, axs = plt.subplots(2, 5, figsize=(25, 10))
axs = axs.flatten()
for name, ax in zip(selection, axs):
    ax.axis(extent)
    ctx.add_basemap(ax, source=providers[name])
    ax.set_title(name)
    ax.set_axis_off()
plt.show()
fig.savefig("tiles.png", bbox_inches="tight")
_images/providers_deepdive_38_0.png