diff --git a/contextily/place.py b/contextily/place.py index 89ec5ed3..070adf1f 100644 --- a/contextily/place.py +++ b/contextily/place.py @@ -71,6 +71,10 @@ class Place(object): bbox_map : tuple The bounding box of the returned image, expressed in Web Mercator, with the following order: [minX, minY, maxX, maxY] + timeout : float or tuple + [Optional. Default: None] How many seconds to wait for the + server to send data before giving up, as a float, or a + (connect timeout, read timeout) tuple. """ def __init__( @@ -82,6 +86,7 @@ def __init__( source=None, headers: dict[str, str] | None = None, geocoder=gp.geocoders.Nominatim(user_agent=_default_user_agent), + timeout=None ): self.path = path if source is None: @@ -107,6 +112,7 @@ def __init__( self.latitude = resp.latitude self.longitude = resp.longitude self.geocode = resp + self.timeout = timeout # Get map params self.zoom = ( @@ -130,16 +136,19 @@ def _get_map(self): try: if isinstance(self.path, str): im, bbox = bounds2raster( - self.w, self.s, self.e, self.n, self.path, zoom=self.zoom, **kwargs + self.w, self.s, self.e, self.n, + self.path, + zoom=self.zoom, timeout=self.timeout, **kwargs ) else: im, bbox = bounds2img( - self.w, self.s, self.e, self.n, self.zoom, **kwargs + self.w, self.s, self.e, self.n, + zoom=self.zoom, timeout=self.timeout, **kwargs ) except Exception as err: raise ValueError( - "Could not retrieve map with parameters: {}, {}, {}, {}, zoom={}\n{}\nError: {}".format( - self.w, self.s, self.e, self.n, self.zoom, kwargs, err + "Could not retrieve map with parameters: {}, {}, {}, {}, zoom={}, timeout={}\n{}\nError: {}".format( + self.w, self.s, self.e, self.n, self.zoom, self.timeout, kwargs, err ) ) diff --git a/contextily/plotting.py b/contextily/plotting.py index f313b19a..af2663df 100644 --- a/contextily/plotting.py +++ b/contextily/plotting.py @@ -27,6 +27,7 @@ def add_basemap( crs=None, resampling=Resampling.bilinear, zoom_adjust=None, + timeout=None, **extra_imshow_args, ): """ @@ -84,6 +85,10 @@ def add_basemap( [Optional. Default: None] The amount to adjust a chosen zoom level if it is chosen automatically. Values outside of -1 to 1 are not recommended as they can lead to slow execution. + timeout : float or tuple + [Optional. Default=None] How many seconds to wait for the + server to send data before giving up, as a float, or a + (connect timeout, read timeout) tuple. **extra_imshow_args : Other parameters to be passed to `imshow`. @@ -145,6 +150,7 @@ def add_basemap( headers=headers, ll=False, zoom_adjust=zoom_adjust, + timeout=timeout ) # Warping if crs is not None: diff --git a/contextily/tile.py b/contextily/tile.py index 58ff6948..38779847 100644 --- a/contextily/tile.py +++ b/contextily/tile.py @@ -79,6 +79,7 @@ def bounds2raster( max_retries=2, n_connections=1, use_cache=True, + timeout=None ): """ Take bounding box and zoom, and write tiles into a raster file in @@ -132,6 +133,10 @@ def bounds2raster( If False, caching of the downloaded tiles will be disabled. This can be useful in resource constrained environments, especially when using n_connections > 1, or when a tile provider's terms of use don't allow caching. + timeout : float or tuple + [Optional. Default: None] How many seconds to wait for the + server to send data before giving up, as a float, or a + (connect timeout, read timeout) tuple. Returns ------- @@ -159,6 +164,7 @@ def bounds2raster( ll=True, n_connections=n_connections, use_cache=use_cache, + timeout=timeout ) # Write @@ -202,6 +208,7 @@ def bounds2img( n_connections=1, use_cache=True, zoom_adjust=None, + timeout=None, ): """ Take bounding box and zoom and return an image with all the tiles @@ -257,6 +264,10 @@ def bounds2img( [Optional. Default: None] The amount to adjust a chosen zoom level if it is chosen automatically. Values outside of -1 to 1 are not recommended as they can lead to slow execution. + timeout : float or tuple + [Optional. Default: None] How many seconds to wait for the + server to send data before giving up, as a float, or a + (connect timeout, read timeout) tuple. Returns ------- @@ -296,7 +307,7 @@ def bounds2img( ) fetch_tile_fn = memory.cache(_fetch_tile) if use_cache else _fetch_tile arrays = Parallel(n_jobs=n_connections, prefer=preferred_backend)( - delayed(fetch_tile_fn)(tile_url, wait, max_retries, headers) for tile_url in tile_urls + delayed(fetch_tile_fn)(tile_url, wait, max_retries, headers, timeout=timeout) for tile_url in tile_urls ) # merge downloaded tiles merged, extent = _merge_tiles(tiles, arrays) @@ -324,8 +335,8 @@ def _process_source(source): return provider -def _fetch_tile(tile_url, wait, max_retries, headers: dict[str, str]): - array = _retryer(tile_url, wait, max_retries, headers) +def _fetch_tile(tile_url, wait, max_retries, headers: dict[str, str], timeout=None): + array = _retryer(tile_url, wait, max_retries, headers, timeout=timeout) return array @@ -443,7 +454,7 @@ def _warper(img, transform, s_crs, t_crs, resampling): return img, bounds, transform -def _retryer(tile_url, wait, max_retries, headers: dict[str, str]): +def _retryer(tile_url, wait, max_retries, headers: dict[str, str], timeout=None): """ Retry a url many times in attempt to get a tile and read the image @@ -460,13 +471,20 @@ def _retryer(tile_url, wait, max_retries, headers: dict[str, str]): will stop trying to fetch more tiles from a rate-limited API. headers: dict[str, str] headers to include with request. + timeout : float or tuple + [Optional. Default=None] How many seconds to wait for the + server to send data before giving up, as a float, or a + (connect timeout, read timeout) tuple. Returns ------- array of the tile """ try: - request = requests.get(tile_url, headers={"user-agent": USER_AGENT, **headers}) + request = requests.get( + tile_url, + headers={"user-agent": USER_AGENT, **headers}, + timeout=timeout) request.raise_for_status() with io.BytesIO(request.content) as image_stream: image = Image.open(image_stream).convert("RGBA") @@ -485,7 +503,7 @@ def _retryer(tile_url, wait, max_retries, headers: dict[str, str]): if max_retries > 0: time.sleep(wait) max_retries -= 1 - request = _retryer(tile_url, wait, max_retries, headers) + request = _retryer(tile_url, wait, max_retries, headers, timeout=timeout) else: raise requests.HTTPError("Connection reset by peer too many times. " f"Last message was: {request.status_code} "