Note

This page was generated from gallery/plotting_basemap_background.ipynb.
Interactive online version: Binder badge

Adding a background map to plots#

This example shows how you can add a background basemap to plots created with the geopandas .plot() method. This makes use of the contextily package to retrieve web map tiles from several sources (OpenStreetMap, CartoDB). Also have a look at contextily’s introduction guide for possible new features not covered here.

[1]:
import geopandas
import geodatasets
import contextily as cx

Let’s use the NYC borough boundary data that is available in geopandas datasets. Plotting this gives the following result:

[2]:
df = geopandas.read_file(geodatasets.get_path("nybb"))
ax = df.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
../_images/gallery_plotting_basemap_background_3_0.png

Matching coordinate systems#

Before adding web map tiles to this plot, we first need to ensure the coordinate reference systems (CRS) of the tiles and the data match. Web map tiles are typically provided in Web Mercator (EPSG 3857), so let us first check what CRS our NYC boroughs are in:

[3]:
df.crs
[3]:
<Projected CRS: EPSG:2263>
Name: NAD83 / New York Long Island (ftUS)
Axis Info [cartesian]:
- X[east]: Easting (US survey foot)
- Y[north]: Northing (US survey foot)
Area of Use:
- name: United States (USA) - New York - counties of Bronx; Kings; Nassau; New York; Queens; Richmond; Suffolk.
- bounds: (-74.26, 40.47, -71.8, 41.3)
Coordinate Operation:
- name: SPCS83 New York Long Island zone (US Survey feet)
- method: Lambert Conic Conformal (2SP)
Datum: North American Datum 1983
- Ellipsoid: GRS 1980
- Prime Meridian: Greenwich

Now we know the CRS do not match, so we need to choose in which CRS we wish to visualize the data: either the CRS of the tiles, the one of the data, or even a different one.

The first option to match CRS is to leverage the to_crs method of GeoDataFrames to convert the CRS of our data, here to Web Mercator:

[4]:
df_wm = df.to_crs(epsg=3857)

We can then use add_basemap function of contextily to easily add a background map to our plot:

[5]:
ax = df_wm.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
cx.add_basemap(ax)
---------------------------------------------------------------------------
SSLEOFError                               Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connectionpool.py:467, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
    466 try:
--> 467     self._validate_conn(conn)
    468 except (SocketTimeout, BaseSSLError) as e:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connectionpool.py:1099, in HTTPSConnectionPool._validate_conn(self, conn)
   1098 if conn.is_closed:
-> 1099     conn.connect()
   1101 # TODO revise this, see https://github.com/urllib3/urllib3/issues/2791

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connection.py:653, in HTTPSConnection.connect(self)
    651 server_hostname_rm_dot = server_hostname.rstrip(".")
--> 653 sock_and_verified = _ssl_wrap_socket_and_match_hostname(
    654     sock=sock,
    655     cert_reqs=self.cert_reqs,
    656     ssl_version=self.ssl_version,
    657     ssl_minimum_version=self.ssl_minimum_version,
    658     ssl_maximum_version=self.ssl_maximum_version,
    659     ca_certs=self.ca_certs,
    660     ca_cert_dir=self.ca_cert_dir,
    661     ca_cert_data=self.ca_cert_data,
    662     cert_file=self.cert_file,
    663     key_file=self.key_file,
    664     key_password=self.key_password,
    665     server_hostname=server_hostname_rm_dot,
    666     ssl_context=self.ssl_context,
    667     tls_in_tls=tls_in_tls,
    668     assert_hostname=self.assert_hostname,
    669     assert_fingerprint=self.assert_fingerprint,
    670 )
    671 self.sock = sock_and_verified.socket

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connection.py:806, in _ssl_wrap_socket_and_match_hostname(sock, cert_reqs, ssl_version, ssl_minimum_version, ssl_maximum_version, cert_file, key_file, key_password, ca_certs, ca_cert_dir, ca_cert_data, assert_hostname, assert_fingerprint, server_hostname, ssl_context, tls_in_tls)
    804         server_hostname = normalized
--> 806 ssl_sock = ssl_wrap_socket(
    807     sock=sock,
    808     keyfile=key_file,
    809     certfile=cert_file,
    810     key_password=key_password,
    811     ca_certs=ca_certs,
    812     ca_cert_dir=ca_cert_dir,
    813     ca_cert_data=ca_cert_data,
    814     server_hostname=server_hostname,
    815     ssl_context=context,
    816     tls_in_tls=tls_in_tls,
    817 )
    819 try:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/util/ssl_.py:465, in ssl_wrap_socket(sock, keyfile, certfile, cert_reqs, ca_certs, server_hostname, ssl_version, ciphers, ssl_context, ca_cert_dir, key_password, ca_cert_data, tls_in_tls)
    463     pass
--> 465 ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname)
    466 return ssl_sock

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/util/ssl_.py:509, in _ssl_wrap_socket_impl(sock, ssl_context, tls_in_tls, server_hostname)
    507     return SSLTransport(sock, ssl_context, server_hostname)
--> 509 return ssl_context.wrap_socket(sock, server_hostname=server_hostname)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/ssl.py:517, in SSLContext.wrap_socket(self, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, session)
    511 def wrap_socket(self, sock, server_side=False,
    512                 do_handshake_on_connect=True,
    513                 suppress_ragged_eofs=True,
    514                 server_hostname=None, session=None):
    515     # SSLSocket class handles server_hostname encoding before it calls
    516     # ctx._wrap_socket()
--> 517     return self.sslsocket_class._create(
    518         sock=sock,
    519         server_side=server_side,
    520         do_handshake_on_connect=do_handshake_on_connect,
    521         suppress_ragged_eofs=suppress_ragged_eofs,
    522         server_hostname=server_hostname,
    523         context=self,
    524         session=session
    525     )

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/ssl.py:1108, in SSLSocket._create(cls, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, context, session)
   1107             raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets")
-> 1108         self.do_handshake()
   1109 except (OSError, ValueError):

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/ssl.py:1379, in SSLSocket.do_handshake(self, block)
   1378         self.settimeout(None)
-> 1379     self._sslobj.do_handshake()
   1380 finally:

SSLEOFError: [SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1006)

During handling of the above exception, another exception occurred:

SSLError                                  Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connectionpool.py:793, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    792 # Make the request on the HTTPConnection object
--> 793 response = self._make_request(
    794     conn,
    795     method,
    796     url,
    797     timeout=timeout_obj,
    798     body=body,
    799     headers=headers,
    800     chunked=chunked,
    801     retries=retries,
    802     response_conn=response_conn,
    803     preload_content=preload_content,
    804     decode_content=decode_content,
    805     **response_kw,
    806 )
    808 # Everything went great!

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connectionpool.py:491, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
    490         new_e = _wrap_proxy_error(new_e, conn.proxy.scheme)
--> 491     raise new_e
    493 # conn.request() calls http.client.*.request, not the method in
    494 # urllib3.request. It also calls makefile (recv) on the socket.

SSLError: [SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1006)

The above exception was the direct cause of the following exception:

MaxRetryError                             Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/requests/adapters.py:486, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    485 try:
--> 486     resp = conn.urlopen(
    487         method=request.method,
    488         url=url,
    489         body=request.body,
    490         headers=request.headers,
    491         redirect=False,
    492         assert_same_host=False,
    493         preload_content=False,
    494         decode_content=False,
    495         retries=self.max_retries,
    496         timeout=timeout,
    497         chunked=chunked,
    498     )
    500 except (ProtocolError, OSError) as err:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connectionpool.py:847, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    845     new_e = ProtocolError("Connection aborted.", new_e)
--> 847 retries = retries.increment(
    848     method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
    849 )
    850 retries.sleep()

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/util/retry.py:515, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
    514     reason = error or ResponseError(cause)
--> 515     raise MaxRetryError(_pool, url, reason) from reason  # type: ignore[arg-type]
    517 log.debug("Incremented Retry for (url='%s'): %r", url, new_retry)

MaxRetryError: HTTPSConnectionPool(host='a.tile.openstreetmap.fr', port=443): Max retries exceeded with url: /hot/11/601/768.png (Caused by SSLError(SSLEOFError(8, '[SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1006)')))

During handling of the above exception, another exception occurred:

SSLError                                  Traceback (most recent call last)
Cell In[5], line 2
      1 ax = df_wm.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
----> 2 cx.add_basemap(ax)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/contextily/plotting.py:134, in add_basemap(ax, zoom, source, interpolation, attribution, attribution_size, reset_extent, crs, resampling, zoom_adjust, **extra_imshow_args)
    130     left, right, bottom, top = _reproj_bb(
    131         left, right, bottom, top, crs, "epsg:3857"
    132     )
    133 # Download image
--> 134 image, extent = bounds2img(
    135     left, bottom, right, top, zoom=zoom, source=source, ll=False, zoom_adjust=zoom_adjust
    136 )
    137 # Warping
    138 if crs is not None:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/contextily/tile.py:262, in bounds2img(w, s, e, n, zoom, source, ll, wait, max_retries, n_connections, use_cache, zoom_adjust)
    260 preferred_backend = "threads" if (n_connections == 1 or not use_cache) else "processes"
    261 fetch_tile_fn = memory.cache(_fetch_tile) if use_cache else _fetch_tile
--> 262 arrays = Parallel(n_jobs=n_connections, prefer=preferred_backend)(
    263     delayed(fetch_tile_fn)(tile_url, wait, max_retries) for tile_url in tile_urls)
    264 # merge downloaded tiles
    265 merged, extent = _merge_tiles(tiles, arrays)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/parallel.py:1863, in Parallel.__call__(self, iterable)
   1861     output = self._get_sequential_output(iterable)
   1862     next(output)
-> 1863     return output if self.return_generator else list(output)
   1865 # Let's create an ID that uniquely identifies the current call. If the
   1866 # call is interrupted early and that the same instance is immediately
   1867 # re-used, this id will be used to prevent workers that were
   1868 # concurrently finalizing a task from the previous call to run the
   1869 # callback.
   1870 with self._lock:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/parallel.py:1792, in Parallel._get_sequential_output(self, iterable)
   1790 self.n_dispatched_batches += 1
   1791 self.n_dispatched_tasks += 1
-> 1792 res = func(*args, **kwargs)
   1793 self.n_completed_tasks += 1
   1794 self.print_progress()

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/memory.py:655, in MemorizedFunc.__call__(self, *args, **kwargs)
    654 def __call__(self, *args, **kwargs):
--> 655     return self._cached_call(args, kwargs)[0]

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/memory.py:598, in MemorizedFunc._cached_call(self, args, kwargs, shelving)
    595     must_call = True
    597 if must_call:
--> 598     out, metadata = self.call(*args, **kwargs)
    599     if self.mmap_mode is not None:
    600         # Memmap the output at the first call to be consistent with
    601         # later calls
    602         if self._verbose:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/memory.py:856, in MemorizedFunc.call(self, *args, **kwargs)
    854 if self._verbose > 0:
    855     print(format_call(self.func, args, kwargs))
--> 856 output = self.func(*args, **kwargs)
    857 self.store_backend.dump_item(
    858     [func_id, args_id], output, verbose=self._verbose)
    860 duration = time.time() - start_time

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/contextily/tile.py:291, in _fetch_tile(tile_url, wait, max_retries)
    290 def _fetch_tile(tile_url, wait, max_retries):
--> 291     request = _retryer(tile_url, wait, max_retries)
    292     with io.BytesIO(request.content) as image_stream:
    293         image = Image.open(image_stream).convert("RGBA")

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/contextily/tile.py:434, in _retryer(tile_url, wait, max_retries)
    414 """
    415 Retry a url many times in attempt to get a tile
    416
   (...)
    431 request object containing the web response.
    432 """
    433 try:
--> 434     request = requests.get(tile_url, headers={"user-agent": USER_AGENT})
    435     request.raise_for_status()
    436 except requests.HTTPError:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/requests/api.py:73, in get(url, params, **kwargs)
     62 def get(url, params=None, **kwargs):
     63     r"""Sends a GET request.
     64
     65     :param url: URL for the new :class:`Request` object.
   (...)
     70     :rtype: requests.Response
     71     """
---> 73     return request("get", url, params=params, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/requests/api.py:59, in request(method, url, **kwargs)
     55 # By using the 'with' statement we are sure the session is closed, thus we
     56 # avoid leaving sockets open which can trigger a ResourceWarning in some
     57 # cases, and look like a memory leak in others.
     58 with sessions.Session() as session:
---> 59     return session.request(method=method, url=url, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/requests/sessions.py:589, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
    584 send_kwargs = {
    585     "timeout": timeout,
    586     "allow_redirects": allow_redirects,
    587 }
    588 send_kwargs.update(settings)
--> 589 resp = self.send(prep, **send_kwargs)
    591 return resp

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/requests/sessions.py:703, in Session.send(self, request, **kwargs)
    700 start = preferred_clock()
    702 # Send the request
--> 703 r = adapter.send(request, **kwargs)
    705 # Total elapsed time of the request (approximately)
    706 elapsed = preferred_clock() - start

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/requests/adapters.py:517, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    513         raise ProxyError(e, request=request)
    515     if isinstance(e.reason, _SSLError):
    516         # This branch is for urllib3 v1.22 and later.
--> 517         raise SSLError(e, request=request)
    519     raise ConnectionError(e, request=request)
    521 except ClosedPoolError as e:

SSLError: HTTPSConnectionPool(host='a.tile.openstreetmap.fr', port=443): Max retries exceeded with url: /hot/11/601/768.png (Caused by SSLError(SSLEOFError(8, '[SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1006)')))
../_images/gallery_plotting_basemap_background_9_1.png

If we want to convert the CRS of the tiles instead, which might be advisable for large datasets, we can use the crs keyword argument of add_basemap as follows:

[6]:
ax = df.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
cx.add_basemap(ax, crs=df.crs)
---------------------------------------------------------------------------
UnidentifiedImageError                    Traceback (most recent call last)
Cell In[6], line 2
      1 ax = df.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
----> 2 cx.add_basemap(ax, crs=df.crs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/contextily/plotting.py:134, in add_basemap(ax, zoom, source, interpolation, attribution, attribution_size, reset_extent, crs, resampling, zoom_adjust, **extra_imshow_args)
    130     left, right, bottom, top = _reproj_bb(
    131         left, right, bottom, top, crs, "epsg:3857"
    132     )
    133 # Download image
--> 134 image, extent = bounds2img(
    135     left, bottom, right, top, zoom=zoom, source=source, ll=False, zoom_adjust=zoom_adjust
    136 )
    137 # Warping
    138 if crs is not None:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/contextily/tile.py:262, in bounds2img(w, s, e, n, zoom, source, ll, wait, max_retries, n_connections, use_cache, zoom_adjust)
    260 preferred_backend = "threads" if (n_connections == 1 or not use_cache) else "processes"
    261 fetch_tile_fn = memory.cache(_fetch_tile) if use_cache else _fetch_tile
--> 262 arrays = Parallel(n_jobs=n_connections, prefer=preferred_backend)(
    263     delayed(fetch_tile_fn)(tile_url, wait, max_retries) for tile_url in tile_urls)
    264 # merge downloaded tiles
    265 merged, extent = _merge_tiles(tiles, arrays)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/parallel.py:1863, in Parallel.__call__(self, iterable)
   1861     output = self._get_sequential_output(iterable)
   1862     next(output)
-> 1863     return output if self.return_generator else list(output)
   1865 # Let's create an ID that uniquely identifies the current call. If the
   1866 # call is interrupted early and that the same instance is immediately
   1867 # re-used, this id will be used to prevent workers that were
   1868 # concurrently finalizing a task from the previous call to run the
   1869 # callback.
   1870 with self._lock:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/parallel.py:1792, in Parallel._get_sequential_output(self, iterable)
   1790 self.n_dispatched_batches += 1
   1791 self.n_dispatched_tasks += 1
-> 1792 res = func(*args, **kwargs)
   1793 self.n_completed_tasks += 1
   1794 self.print_progress()

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/memory.py:655, in MemorizedFunc.__call__(self, *args, **kwargs)
    654 def __call__(self, *args, **kwargs):
--> 655     return self._cached_call(args, kwargs)[0]

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/memory.py:598, in MemorizedFunc._cached_call(self, args, kwargs, shelving)
    595     must_call = True
    597 if must_call:
--> 598     out, metadata = self.call(*args, **kwargs)
    599     if self.mmap_mode is not None:
    600         # Memmap the output at the first call to be consistent with
    601         # later calls
    602         if self._verbose:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/memory.py:856, in MemorizedFunc.call(self, *args, **kwargs)
    854 if self._verbose > 0:
    855     print(format_call(self.func, args, kwargs))
--> 856 output = self.func(*args, **kwargs)
    857 self.store_backend.dump_item(
    858     [func_id, args_id], output, verbose=self._verbose)
    860 duration = time.time() - start_time

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/contextily/tile.py:293, in _fetch_tile(tile_url, wait, max_retries)
    291 request = _retryer(tile_url, wait, max_retries)
    292 with io.BytesIO(request.content) as image_stream:
--> 293     image = Image.open(image_stream).convert("RGBA")
    294     array = np.asarray(image)
    295     image.close()

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/PIL/Image.py:3280, in open(fp, mode, formats)
   3278     warnings.warn(message)
   3279 msg = "cannot identify image file %r" % (filename if filename else fp)
-> 3280 raise UnidentifiedImageError(msg)

UnidentifiedImageError: cannot identify image file <_io.BytesIO object at 0x7fdc4287c1d0>
../_images/gallery_plotting_basemap_background_11_1.png

This reprojects map tiles to a target CRS which may in some cases cause a loss of sharpness. See contextily’s guide on warping tiles for more information on the subject.

Controlling the level of detail#

We can control the detail of the map tiles using the optional zoom keyword (be careful to not specify a too high zoom level, as this can result in a large download).:

[7]:
ax = df_wm.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
cx.add_basemap(ax, zoom=12)
---------------------------------------------------------------------------
SSLEOFError                               Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connectionpool.py:467, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
    466 try:
--> 467     self._validate_conn(conn)
    468 except (SocketTimeout, BaseSSLError) as e:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connectionpool.py:1099, in HTTPSConnectionPool._validate_conn(self, conn)
   1098 if conn.is_closed:
-> 1099     conn.connect()
   1101 # TODO revise this, see https://github.com/urllib3/urllib3/issues/2791

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connection.py:653, in HTTPSConnection.connect(self)
    651 server_hostname_rm_dot = server_hostname.rstrip(".")
--> 653 sock_and_verified = _ssl_wrap_socket_and_match_hostname(
    654     sock=sock,
    655     cert_reqs=self.cert_reqs,
    656     ssl_version=self.ssl_version,
    657     ssl_minimum_version=self.ssl_minimum_version,
    658     ssl_maximum_version=self.ssl_maximum_version,
    659     ca_certs=self.ca_certs,
    660     ca_cert_dir=self.ca_cert_dir,
    661     ca_cert_data=self.ca_cert_data,
    662     cert_file=self.cert_file,
    663     key_file=self.key_file,
    664     key_password=self.key_password,
    665     server_hostname=server_hostname_rm_dot,
    666     ssl_context=self.ssl_context,
    667     tls_in_tls=tls_in_tls,
    668     assert_hostname=self.assert_hostname,
    669     assert_fingerprint=self.assert_fingerprint,
    670 )
    671 self.sock = sock_and_verified.socket

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connection.py:806, in _ssl_wrap_socket_and_match_hostname(sock, cert_reqs, ssl_version, ssl_minimum_version, ssl_maximum_version, cert_file, key_file, key_password, ca_certs, ca_cert_dir, ca_cert_data, assert_hostname, assert_fingerprint, server_hostname, ssl_context, tls_in_tls)
    804         server_hostname = normalized
--> 806 ssl_sock = ssl_wrap_socket(
    807     sock=sock,
    808     keyfile=key_file,
    809     certfile=cert_file,
    810     key_password=key_password,
    811     ca_certs=ca_certs,
    812     ca_cert_dir=ca_cert_dir,
    813     ca_cert_data=ca_cert_data,
    814     server_hostname=server_hostname,
    815     ssl_context=context,
    816     tls_in_tls=tls_in_tls,
    817 )
    819 try:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/util/ssl_.py:465, in ssl_wrap_socket(sock, keyfile, certfile, cert_reqs, ca_certs, server_hostname, ssl_version, ciphers, ssl_context, ca_cert_dir, key_password, ca_cert_data, tls_in_tls)
    463     pass
--> 465 ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname)
    466 return ssl_sock

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/util/ssl_.py:509, in _ssl_wrap_socket_impl(sock, ssl_context, tls_in_tls, server_hostname)
    507     return SSLTransport(sock, ssl_context, server_hostname)
--> 509 return ssl_context.wrap_socket(sock, server_hostname=server_hostname)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/ssl.py:517, in SSLContext.wrap_socket(self, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, session)
    511 def wrap_socket(self, sock, server_side=False,
    512                 do_handshake_on_connect=True,
    513                 suppress_ragged_eofs=True,
    514                 server_hostname=None, session=None):
    515     # SSLSocket class handles server_hostname encoding before it calls
    516     # ctx._wrap_socket()
--> 517     return self.sslsocket_class._create(
    518         sock=sock,
    519         server_side=server_side,
    520         do_handshake_on_connect=do_handshake_on_connect,
    521         suppress_ragged_eofs=suppress_ragged_eofs,
    522         server_hostname=server_hostname,
    523         context=self,
    524         session=session
    525     )

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/ssl.py:1108, in SSLSocket._create(cls, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, context, session)
   1107             raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets")
-> 1108         self.do_handshake()
   1109 except (OSError, ValueError):

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/ssl.py:1379, in SSLSocket.do_handshake(self, block)
   1378         self.settimeout(None)
-> 1379     self._sslobj.do_handshake()
   1380 finally:

SSLEOFError: [SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1006)

During handling of the above exception, another exception occurred:

SSLError                                  Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connectionpool.py:793, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    792 # Make the request on the HTTPConnection object
--> 793 response = self._make_request(
    794     conn,
    795     method,
    796     url,
    797     timeout=timeout_obj,
    798     body=body,
    799     headers=headers,
    800     chunked=chunked,
    801     retries=retries,
    802     response_conn=response_conn,
    803     preload_content=preload_content,
    804     decode_content=decode_content,
    805     **response_kw,
    806 )
    808 # Everything went great!

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connectionpool.py:491, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
    490         new_e = _wrap_proxy_error(new_e, conn.proxy.scheme)
--> 491     raise new_e
    493 # conn.request() calls http.client.*.request, not the method in
    494 # urllib3.request. It also calls makefile (recv) on the socket.

SSLError: [SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1006)

The above exception was the direct cause of the following exception:

MaxRetryError                             Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/requests/adapters.py:486, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    485 try:
--> 486     resp = conn.urlopen(
    487         method=request.method,
    488         url=url,
    489         body=request.body,
    490         headers=request.headers,
    491         redirect=False,
    492         assert_same_host=False,
    493         preload_content=False,
    494         decode_content=False,
    495         retries=self.max_retries,
    496         timeout=timeout,
    497         chunked=chunked,
    498     )
    500 except (ProtocolError, OSError) as err:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/connectionpool.py:847, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    845     new_e = ProtocolError("Connection aborted.", new_e)
--> 847 retries = retries.increment(
    848     method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
    849 )
    850 retries.sleep()

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/urllib3/util/retry.py:515, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
    514     reason = error or ResponseError(cause)
--> 515     raise MaxRetryError(_pool, url, reason) from reason  # type: ignore[arg-type]
    517 log.debug("Incremented Retry for (url='%s'): %r", url, new_retry)

MaxRetryError: HTTPSConnectionPool(host='a.tile.openstreetmap.fr', port=443): Max retries exceeded with url: /hot/12/1202/1540.png (Caused by SSLError(SSLEOFError(8, '[SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1006)')))

During handling of the above exception, another exception occurred:

SSLError                                  Traceback (most recent call last)
Cell In[7], line 2
      1 ax = df_wm.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
----> 2 cx.add_basemap(ax, zoom=12)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/contextily/plotting.py:134, in add_basemap(ax, zoom, source, interpolation, attribution, attribution_size, reset_extent, crs, resampling, zoom_adjust, **extra_imshow_args)
    130     left, right, bottom, top = _reproj_bb(
    131         left, right, bottom, top, crs, "epsg:3857"
    132     )
    133 # Download image
--> 134 image, extent = bounds2img(
    135     left, bottom, right, top, zoom=zoom, source=source, ll=False, zoom_adjust=zoom_adjust
    136 )
    137 # Warping
    138 if crs is not None:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/contextily/tile.py:262, in bounds2img(w, s, e, n, zoom, source, ll, wait, max_retries, n_connections, use_cache, zoom_adjust)
    260 preferred_backend = "threads" if (n_connections == 1 or not use_cache) else "processes"
    261 fetch_tile_fn = memory.cache(_fetch_tile) if use_cache else _fetch_tile
--> 262 arrays = Parallel(n_jobs=n_connections, prefer=preferred_backend)(
    263     delayed(fetch_tile_fn)(tile_url, wait, max_retries) for tile_url in tile_urls)
    264 # merge downloaded tiles
    265 merged, extent = _merge_tiles(tiles, arrays)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/parallel.py:1863, in Parallel.__call__(self, iterable)
   1861     output = self._get_sequential_output(iterable)
   1862     next(output)
-> 1863     return output if self.return_generator else list(output)
   1865 # Let's create an ID that uniquely identifies the current call. If the
   1866 # call is interrupted early and that the same instance is immediately
   1867 # re-used, this id will be used to prevent workers that were
   1868 # concurrently finalizing a task from the previous call to run the
   1869 # callback.
   1870 with self._lock:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/parallel.py:1792, in Parallel._get_sequential_output(self, iterable)
   1790 self.n_dispatched_batches += 1
   1791 self.n_dispatched_tasks += 1
-> 1792 res = func(*args, **kwargs)
   1793 self.n_completed_tasks += 1
   1794 self.print_progress()

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/memory.py:655, in MemorizedFunc.__call__(self, *args, **kwargs)
    654 def __call__(self, *args, **kwargs):
--> 655     return self._cached_call(args, kwargs)[0]

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/memory.py:598, in MemorizedFunc._cached_call(self, args, kwargs, shelving)
    595     must_call = True
    597 if must_call:
--> 598     out, metadata = self.call(*args, **kwargs)
    599     if self.mmap_mode is not None:
    600         # Memmap the output at the first call to be consistent with
    601         # later calls
    602         if self._verbose:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/joblib/memory.py:856, in MemorizedFunc.call(self, *args, **kwargs)
    854 if self._verbose > 0:
    855     print(format_call(self.func, args, kwargs))
--> 856 output = self.func(*args, **kwargs)
    857 self.store_backend.dump_item(
    858     [func_id, args_id], output, verbose=self._verbose)
    860 duration = time.time() - start_time

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/contextily/tile.py:291, in _fetch_tile(tile_url, wait, max_retries)
    290 def _fetch_tile(tile_url, wait, max_retries):
--> 291     request = _retryer(tile_url, wait, max_retries)
    292     with io.BytesIO(request.content) as image_stream:
    293         image = Image.open(image_stream).convert("RGBA")

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/contextily/tile.py:434, in _retryer(tile_url, wait, max_retries)
    414 """
    415 Retry a url many times in attempt to get a tile
    416
   (...)
    431 request object containing the web response.
    432 """
    433 try:
--> 434     request = requests.get(tile_url, headers={"user-agent": USER_AGENT})
    435     request.raise_for_status()
    436 except requests.HTTPError:

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/requests/api.py:73, in get(url, params, **kwargs)
     62 def get(url, params=None, **kwargs):
     63     r"""Sends a GET request.
     64
     65     :param url: URL for the new :class:`Request` object.
   (...)
     70     :rtype: requests.Response
     71     """
---> 73     return request("get", url, params=params, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/requests/api.py:59, in request(method, url, **kwargs)
     55 # By using the 'with' statement we are sure the session is closed, thus we
     56 # avoid leaving sockets open which can trigger a ResourceWarning in some
     57 # cases, and look like a memory leak in others.
     58 with sessions.Session() as session:
---> 59     return session.request(method=method, url=url, **kwargs)

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/requests/sessions.py:589, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
    584 send_kwargs = {
    585     "timeout": timeout,
    586     "allow_redirects": allow_redirects,
    587 }
    588 send_kwargs.update(settings)
--> 589 resp = self.send(prep, **send_kwargs)
    591 return resp

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/requests/sessions.py:703, in Session.send(self, request, **kwargs)
    700 start = preferred_clock()
    702 # Send the request
--> 703 r = adapter.send(request, **kwargs)
    705 # Total elapsed time of the request (approximately)
    706 elapsed = preferred_clock() - start

File ~/checkouts/readthedocs.org/user_builds/geopandas/conda/v0.14.3/lib/python3.11/site-packages/requests/adapters.py:517, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    513         raise ProxyError(e, request=request)
    515     if isinstance(e.reason, _SSLError):
    516         # This branch is for urllib3 v1.22 and later.
--> 517         raise SSLError(e, request=request)
    519     raise ConnectionError(e, request=request)
    521 except ClosedPoolError as e:

SSLError: HTTPSConnectionPool(host='a.tile.openstreetmap.fr', port=443): Max retries exceeded with url: /hot/12/1202/1540.png (Caused by SSLError(SSLEOFError(8, '[SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1006)')))
../_images/gallery_plotting_basemap_background_15_1.png

Choosing a different style#

By default, contextily uses the OpenStreetMap HOT style. We can specify a different style using cx.providers:

[8]:
ax = df_wm.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
cx.add_basemap(ax, source=cx.providers.CartoDB.Positron)
ax.set_axis_off()
../_images/gallery_plotting_basemap_background_18_0.png

Adding labels as an overlay#

Sometimes, when you plot data on a basemap, the data will obscure some important map elements, such as labels, that you would otherwise want to see unobscured. Some map tile providers offer multiple sets of partially transparent tiles to solve this, and contextily will do its best to auto-detect these transparent layers and put them on top.

[9]:
ax = df_wm.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
cx.add_basemap(ax, source=cx.providers.CartoDB.PositronNoLabels)
cx.add_basemap(ax, source=cx.providers.CartoDB.PositronOnlyLabels)
../_images/gallery_plotting_basemap_background_21_0.png

By splitting the layers like this, you can also independently manipulate the level of zoom on each layer, for example to make labels larger while still showing a lot of detail.

[10]:
ax = df_wm.plot(figsize=(10, 10), alpha=0.5, edgecolor="k")
cx.add_basemap(ax, source=cx.providers.CartoDB.PositronNoLabels, zoom=12)
cx.add_basemap(ax, source=cx.providers.CartoDB.PositronOnlyLabels, zoom=10)
../_images/gallery_plotting_basemap_background_23_0.png