The main idea is to get a png/jpg image file as a basemap for the complete bounds of a geodataframe.

The geodataframe here happens to be a map of the USA which I took from the topojson/us-atlas repository - this shapefile -> US States

I do some plotting on the map, but that is not important. We will use the simple raw map itself. Ideally I plot it in "albersUsa" projection, but I have been stuck at not getting the correct tiles itself to worry about the projection.

Loading the data and assigning it a proper CRS since the original file does not seem to have any information about its CRS and finnaly converting it to the CRS that contextily expects -

import pandas as pd
import contextily as ctx
import matplotlib.pyplot as plt # mostly for debugging

usa_gdf = gpd.read_file('https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json')
empty = usa_gdf.geometry.is_empty
usa_gdf = usa_gdf[~empty]
usa_gdf.crs = 'esri:102003'
usa_gdf = usa_gdf.to_crs(epsg=4326)  #EPSG:3857 also seems to be okay



Plotting the data to see what it looks like (ideally I want to keep everything in albersUsa projection) -

ax = usa_gdf.plot()

enter image description here

Just for checking, I use the add_basemap method of contextily which seems to work seamlessly with a matplotlib plot as the documentation shows -

ctx.add_basemap(ax, crs=usa_gdf.crs.to_string())

But it gives me the following error -

UserWarning: The inferred zoom level of 20 is not valid for the current tile provider (valid zooms: 0 - 18).


HTTPError                                 Traceback (most recent call last)

/usr/local/lib/python3.6/dist-packages/contextily/tile.py in _retryer(tile_url, wait, max_retries)
    446         request = requests.get(tile_url, headers={"user-agent": USER_AGENT})
--> 447         request.raise_for_status()
    448     except requests.HTTPError:

8 frames

HTTPError: 404 Client Error: Not Found for url: https://stamen-tiles-a.a.ssl.fastly.net/terrain/18/61167/101576.png

During handling of the above exception, another exception occurred:

HTTPError                                 Traceback (most recent call last)

/usr/local/lib/python3.6/dist-packages/contextily/tile.py in _retryer(tile_url, wait, max_retries)
    450             raise requests.HTTPError(
    451                 "Tile URL resulted in a 404 error. "
--> 452                 "Double-check your tile url:\n{}".format(tile_url)
    453             )
    454         elif request.status_code == 104:

HTTPError: Tile URL resulted in a 404 error. Double-check your tile url:

When I provide a source I get a text output with warning but no image -

ctx.add_basemap(ax, crs=usa_gdf.crs.to_string(), source=ctx.providers.Stamen.Toner)


UserWarning: The inferred zoom level of 20 is not valid for the current tile provider (valid zooms: 0 - 19).

<Figure size 432x288 with 0 Axes>

Then I try some lower zoom numbers zoom=3 with a few tile providers but everytime I just get a text output, no map or the previous error - the errors are themselves not consistent -

<Figure size 432x288 with 0 Axes>

Getting the tiles using inbuilt methods
To get the basemap image I looked at the documentation of contextily and found that bound2img method may help me get what I want so I tried it this way -

bounds = usa_gdf.total_bounds
us_img, us_ext = ctx.bounds2img(bounds[0],bounds[1],bounds[2],bounds[3],ll=True, zoom=4, source=ctx.providers.CartoDB.Positron)

This will output us_img as an RGB array. So we will convert and store it into a jpg image and then read it using PIL OR plot it directly using Matplotlib. Since both give the exact same result, I will show the PIL method here -

from PIL import Image
im = Image.fromarray(us_img)

Reading the file and plotting it -

f, ax = plt.subplots(1)
ax.imshow(plt.imread('us.jpeg'), extent=us_ext)

But all I get is this -
enter image description here

Playing with different zoom levels just gives me the world or zooms into Kansas...never the proper whole USA.

Any idea what is it that I am not doing right? And how to see the map of USA in albersUsa projection along with a basemap?

NOTE: It is important for me to get the tiles without using Matplotlib, as I said, here I used Matplotlib just for debugging. Ideally i'd just use PIL, contextily and geopandas. Because of this constraint I decided to use total_bounds and bounds2img , if there are other ways to get this done then I am open to those too. Also, if I work on a different geodataframe then I'd like to have the basemap for that full geodataframe, so the solution should take that into consideration too.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.