Note
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")
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)')))
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>
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)')))
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()
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)
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)