geopandas / contextily

Context geo-tiles in Python
https://contextily.readthedocs.io/en/latest/
BSD 3-Clause "New" or "Revised" License
493 stars 81 forks source link

BUG: HTTPError for points close together #199

Open raybellwaves opened 2 years ago

raybellwaves commented 2 years ago

Default source (cx.providers.Stamen.Terrain) gives HTTPError for points close together. Another source works (e.g. cx.providers.Esri.NatGeoWorldMap) but a small zoom has to be specified (e.g. zoom=1). Not sure if "auto" can handle this case.

import contextily as cx
import geopandas as gpd
import pandas as pd

df = pd.DataFrame(
    data={
        "lon": [24.061341666666667, 24.061378333333334],
        "lat": [37.69585333333333, 37.69589],
    }
)

gdf = gpd.GeoDataFrame(
    df,
    geometry=gpd.points_from_xy(df["lon"], df["lat"]),
    crs="epsg:4326",
)

cx.add_basemap(gdf.plot(color="red", figsize=(16, 16)), crs=gdf.crs)

gives

/Users/ray/miniconda3/envs/test_env/lib/python3.9/site-packages/contextily/tile.py:581: UserWarning: The inferred zoom level of 25 is not valid for the current tile provider (valid zooms: 0 - 18).
  warnings.warn(msg)

HTTPError                                 Traceback (most recent call last)
File ~/miniconda3/envs/test_env/lib/python3.9/site-packages/contextily/tile.py:396, in _retryer(tile_url, wait, max_retries)
    395     request = requests.get(tile_url, headers={"user-agent": USER_AGENT})
--> 396     request.raise_for_status()
    397 except requests.HTTPError:

File ~/miniconda3/envs/test_env/lib/python3.9/site-packages/requests/models.py:960, in Response.raise_for_status(self)
    959 if http_error_msg:
--> 960     raise HTTPError(http_error_msg, response=self)

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

During handling of the above exception, another exception occurred:

HTTPError                                 Traceback (most recent call last)
Input In [17], in <cell line: 1>()
----> 1 cx.add_basemap(gdf.plot(color="red", figsize=(16, 16)), crs=gdf.crs)

File ~/miniconda3/envs/test_env/lib/python3.9/site-packages/contextily/plotting.py:121, in add_basemap(ax, zoom, source, interpolation, attribution, attribution_size, reset_extent, crs, resampling, **extra_imshow_args)
    117     left, right, bottom, top = _reproj_bb(
    118         left, right, bottom, top, crs, {"init": "epsg:3857"}
    119     )
    120 # Download image
--> 121 image, extent = bounds2img(
    122     left, bottom, right, top, zoom=zoom, source=source, ll=False
    123 )
    124 # Warping
    125 if crs is not None:

File ~/miniconda3/envs/test_env/lib/python3.9/site-packages/contextily/tile.py:222, in bounds2img(w, s, e, n, zoom, source, ll, wait, max_retries)
    220 x, y, z = t.x, t.y, t.z
    221 tile_url = provider.build_url(x=x, y=y, z=z)
--> 222 image = _fetch_tile(tile_url, wait, max_retries)
    223 tiles.append(t)
    224 arrays.append(image)

File ~/miniconda3/envs/test_env/lib/python3.9/site-packages/joblib/memory.py:594, in MemorizedFunc.__call__(self, *args, **kwargs)
    593 def __call__(self, *args, **kwargs):
--> 594     return self._cached_call(args, kwargs)[0]

File ~/miniconda3/envs/test_env/lib/python3.9/site-packages/joblib/memory.py:537, in MemorizedFunc._cached_call(self, args, kwargs, shelving)
    534         must_call = True
    536 if must_call:
--> 537     out, metadata = self.call(*args, **kwargs)
    538     if self.mmap_mode is not None:
    539         # Memmap the output at the first call to be consistent with
    540         # later calls
    541         if self._verbose:

File ~/miniconda3/envs/test_env/lib/python3.9/site-packages/joblib/memory.py:779, in MemorizedFunc.call(self, *args, **kwargs)
    777 if self._verbose > 0:
    778     print(format_call(self.func, args, kwargs))
--> 779 output = self.func(*args, **kwargs)
    780 self.store_backend.dump_item(
    781     [func_id, args_id], output, verbose=self._verbose)
    783 duration = time.time() - start_time

File ~/miniconda3/envs/test_env/lib/python3.9/site-packages/contextily/tile.py:252, in _fetch_tile(tile_url, wait, max_retries)
    250 @memory.cache
    251 def _fetch_tile(tile_url, wait, max_retries):
--> 252     request = _retryer(tile_url, wait, max_retries)
    253     with io.BytesIO(request.content) as image_stream:
    254         image = Image.open(image_stream).convert("RGBA")

File ~/miniconda3/envs/test_env/lib/python3.9/site-packages/contextily/tile.py:399, in _retryer(tile_url, wait, max_retries)
    397 except requests.HTTPError:
    398     if request.status_code == 404:
--> 399         raise requests.HTTPError(
    400             "Tile URL resulted in a 404 error. "
    401             "Double-check your tile url:\n{}".format(tile_url)
    402         )
    403     elif request.status_code == 104:
    404         if max_retries > 0:

HTTPError: Tile URL resulted in a 404 error. Double-check your tile url:
https://stamen-tiles-a.a.ssl.fastly.net/terrain/18/148592/101396.png

This works

cx.add_basemap(gdf.plot(color="red", figsize=(16, 16)), crs=gdf.crs, source=cx.providers.Esri.NatGeoWorldMap, zoom=1)