You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何contextily自动缩放模式下无法获取部分瓦片?

问题描述

使用contextily为GeoPandas地图添加OSM图层时,设置zoom="auto"会随机触发404错误,报错对应瓦片URL不存在。

最小复现代码

import pandas as pd
import geopandas as gpd
import contextily as ctx

df = pd.DataFrame({"key": ["A"], "lon": [3.6], "lat": [43.4]})

point = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df.lon, df.lat), crs=4326).to_crs(crs="ESRI:102014")
circle = point.geometry.buffer(3000)

axe = point.plot(color="red")
circle.plot(alpha=0.35, color="red", ax=axe)
ctx.add_basemap(axe, zoom="auto", crs=point.crs)

报错信息

---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
File ~/.local/lib/python3.10/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 /usr/local/lib/python3.10/dist-packages/requests/models.py:1021, in Response.raise_for_status(self)
   1020 if http_error_msg:
--> 1021     raise HTTPError(http_error_msg, response=self)

HTTPError: 404 Client Error: Not Found for url: https://stamen-tiles-a.a.ssl.fastly.net/terrain/14/8353/5993.png

During handling of the above exception, another exception occurred:

HTTPError                                 Traceback (most recent call last)
Cell In [9], line 3
      1 axe = point.plot(color="red")
      2 circle.plot(alpha=0.35, color="red", ax=axe)
----> 3 ctx.add_basemap(axe, zoom="auto", crs=point.crs)

File ~/.local/lib/python3.10/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 ~/.local/lib/python3.10/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 /usr/local/lib/python3.10/dist-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 /usr/local/lib/python3.10/dist-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 /usr/local/lib/python3.10/dist-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 ~/.local/lib/python3.10/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 ~/.local/lib/python3.10/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/14/8353/5993.png

现象总结

  • 仅在zoom="auto"时触发,手动调低缩放级(如设为13)可正常运行
  • 复杂几何图形的点位不会触发该问题

解决方案

1. 切换瓦片提供商

问题本质是默认的Stamen Terrain瓦片在部分高缩放级别下没有预渲染对应瓦片(比如示例中的14级)。可以切换到覆盖更全的瓦片源,比如OpenStreetMap的标准瓦片:

# 替换add_basemap的source参数
ctx.add_basemap(axe, zoom="auto", crs=point.crs, source=ctx.providers.OpenStreetMap.Mapnik)

其他可选的稳定提供商还有:

  • ctx.providers.CartoDB.Positron(浅色简洁风格)
  • ctx.providers.Stamen.Toner(黑白风格,瓦片覆盖率高)

2. 限制自动缩放的最大级别

如果坚持使用Stamen Terrain,可以手动设置自动缩放的最大允许级别,避免请求不存在的高分辨率瓦片:

# 先计算自动缩放级别,再取与最大级别中的较小值
from contextily.tile import calculate_zoom

# 获取当前轴的边界
bounds = axe.get_extent()
# 计算自动缩放级别
auto_zoom = calculate_zoom(bounds[0], bounds[2], bounds[1], bounds[3], crs=point.crs)
# 设置最大允许级别为13
final_zoom = min(auto_zoom, 13)

ctx.add_basemap(axe, zoom=final_zoom, crs=point.crs)

3. 启用重试与降级机制

可以自定义瓦片获取逻辑,当请求高级别瓦片404时自动降级到低一级别:

import requests

def safe_add_basemap(ax, crs, max_zoom=18, source=ctx.providers.Stamen.Terrain):
    bounds = ax.get_extent()
    zoom = calculate_zoom(bounds[0], bounds[2], bounds[1], bounds[3], crs=crs)
    # 从计算出的级别往下尝试
    for z in range(zoom, max(0, zoom-5), -1):
        try:
            ctx.add_basemap(ax, zoom=z, crs=crs, source=source)
            return
        except requests.HTTPError as e:
            if "404" not in str(e):
                raise
    # 如果所有级别都失败,用最低级别
    ctx.add_basemap(ax, zoom=5, crs=crs, source=source)

# 使用自定义函数替换原add_basemap
safe_add_basemap(axe, crs=point.crs)

问题根源说明

  1. 瓦片提供商限制:部分瓦片源(如Stamen Terrain)不会为所有区域渲染最高级别的瓦片,尤其是人口稀少或地形简单的区域,这类区域的高分辨率瓦片不存在,自然返回404。
  2. contextily自动缩放逻辑:zoom="auto"是根据当前地图的边界范围计算所需的最高分辨率级别,不会检查该级别下的瓦片是否实际存在,因此会出现请求不存在瓦片的情况。

内容的提问来源于stack exchange,提问作者jlandercy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 20:00:58