引言
遥感影像动辄几十 GB,直接给浏览器一个 GeoTIFF 是不可行的:用户只看屏幕上的几百万像素,却要下载整幅影像。瓦片服务的核心思路就是把影像切成规则的小块并建立金字塔,前端按视野与缩放级别只取需要的瓦片,把传输量压到屏幕分辨率量级。
工程难点不在切片本身,而在整条管线的衔接。坐标系不统一会让瓦片错位,金字塔层级规划不当会让存储爆炸或缩放模糊,压缩格式选错会让透明区变黑或体积翻倍,缓存策略不当会让服务在流量高峰被打穿。这些问题在开发环境都看不出来,上线后才暴露。
另一个趋势是静态切片与动态切片的分工。静态切片把瓦片预生成好放对象存储,成本低、并发高、易上 CDN;动态切片由服务端按请求实时渲染,适合频繁更新的数据与按需波段合成。成熟的系统往往是两者结合:底图用静态,专题图层用动态。
本文按「金字塔原理 → 网格方案 → 切片工具 → 格式压缩 → 静态发布 → 动态服务 → 矢量瓦片 → 前端优化 → 运维容量」的顺序展开,给出可直接落地的命令、配置与参数。影像读取与重投影的基础见 栅格数据读写与 GDAL ,波段与色彩处理见 遥感影像基础与波段组合 。
需要说明的是,瓦片服务既是遥感问题也是 Web 工程问题。前者决定数据怎么切、切成什么样,后者决定怎么发、怎么缓存、怎么扛住流量。只懂一边都会踩坑:只懂遥感会切出体积巨大又无法缓存的瓦片,只懂 Web 会把坐标与投影处理得错漏百出。
目录
- 瓦片金字塔与切片原理
- 瓦片网格与坐标方案
- 影像切片工具链
- 瓦片格式与压缩
- 静态发布与对象存储
- 动态切片服务
- 矢量瓦片与 MVT
- 前端加载与性能优化
- 服务运维与容量规划
- 权衡取舍
- 常见坑清单
- 小结
1. 瓦片金字塔与切片原理
金字塔是同一影像的多分辨率表示。第 0 级是最高分辨率的原始切片,每往上一级分辨率减半,瓦片数量变为 4 倍,覆盖范围不变。用户缩小时加载高层级瓦片,放大时加载低层级,始终保持屏幕上的像素量大致恒定。
瓦片金字塔
z=0: 1 张瓦片覆盖全球
z=1: 4 张
z=2: 16 张
z=n: 4^n 张
分辨率(z) = 初始分辨率 / 2^z
瓦片编号用 x、y、z 三个整数唯一确定,z 是层级,x 是列号,y 是行号。瓦片尺寸通常取 256 或 512 像素。512 像素瓦片在同样的地图范围内瓦片数量减少到四分之一,HTTP 请求数更少,但单张体积更大,是现代框架的常见选择。
为什么切片有效
切片有效的根本原因是缓存。同一份底图瓦片被所有用户共享,第一次请求后即可被 CDN 与浏览器缓存,命中率可达 95% 以上。若直接传输整幅影像,每个用户每次都要下载完整数据,无法共享缓存。
切片的代价是存储冗余。金字塔的总瓦片数是最高层的约 1.33 倍(几何级数求和),加上多份格式与多个版本,存储会成倍增长。规划时必须预留冗余空间。
瓦片数量与存储估算
全球范围 z=0 到 z=n 的总瓦片数 = (4^(n+1) - 1) / 3
z=16 时约 5.7 亿张,z=18 时约 91 亿张
单张 30KB 计,z=18 全球约 2.7 PB,显然不可行
实际只切业务范围,范围占比 p 时瓦片数约为全球的 p 倍
估算时先按范围与最大层级算出瓦片数,再乘平均体积得到存储需求,最后按增长预期留 2 到 3 倍余量。很多项目在规划阶段低估存储,上线后被迫删减层级,造成返工。
2. 瓦片网格与坐标方案
瓦片网格定义了经纬度到瓦片编号的映射,不同网格之间不兼容,选错会导致瓦片错位。
Web Mercator 与全球网格
Web Mercator(EPSG:3857)是全球 Web 地图的事实标准,把地球投影为正方形,全球范围约 ±20037508.34 米。它的优点是投影公式简单、切图方便;缺点是高纬度面积严重变形,两极无法表示。
XYZ 方案与 TMS 方案是同一网格的两种编号约定,唯一区别是 y 轴方向:XYZ 的 y 从北向南递增,TMS 的 y 从南向北递增。两者混用会让影像上下颠倒,这是最常见的集成错误。MapLibre 与 Leaflet 默认 XYZ,部分 WMS 与 GDAL 工具输出 TMS,务必在配置里显式声明。
| 方案 | 投影 | y 轴方向 | 典型使用者 |
|---|---|---|---|
| XYZ | EPSG:3857 | 北到南 | MapLibre、Leaflet |
| TMS | EPSG:3857 | 南到北 | GDAL、部分 WMS |
| WMTS | 任意 | 由矩阵集定义 | 行业标准服务 |
| GCJ-02 网格 | 加密坐标 | 北到南 | 国内部分底图 |
| 自定义网格 | 本地投影 | 自定义 | 工程坐标系项目 |
国内坐标与自定义网格
国内地图服务存在坐标加密问题,GCJ-02 与 BD-09 是在 WGS84 基础上的非线性偏移,直接用 WGS84 影像叠加会错位几百米。涉及国内底图时必须做坐标转换,且转换不可逆(只能用近似反算)。
工程坐标系项目(如城建、水利)常用自定义投影与本地网格,此时不能套用 Web Mercator 网格,需要自定义 TileMatrixSet。自定义网格的关键是把原点、分辨率、瓦片尺寸定义清楚,并保证与业务数据的坐标一致。
3. 影像切片工具链
切片工具按输入输出分几类,选型取决于数据规模与更新频率。
GDAL 命令行
GDAL 提供最通用的切片能力。gdal2tiles 直接输出 XYZ 或 TMS 目录结构,支持多进程并行;gdal_translate 可把大影像转成 MBTiles。处理超大影像前,建议先转成 COG,让 GDAL 能按需读取而不必整幅载入。
gdal_translate -of COG -co COMPRESS=DEFLATE -co BLOCKSIZE=512 -co OVERVIEWS=IGNORE_EXISTING in.tif out_cog.tif # 转 COG
gdal2tiles.py --profile=mercator --zoom=5-16 --processes=8 -w none out_cog.tif tiles/ # 并行切片
gdal_translate -of MBTILES out_cog.tif out.mbtiles # 打包为 MBTiles
现代工具链
rio-tiler 与 titiler 是基于 rasterio 的 Python 方案,切片逻辑与动态渲染共用同一套代码,适合需要即时处理的场景。GeoServer 与 MapServer 是传统重量级服务,功能全面但资源占用高,适合已有 GIS 基础设施的团队。
| 工具 | 类型 | 适合场景 | 资源占用 |
|---|---|---|---|
| gdal2tiles | 命令行 | 一次性批量切片 | 低 |
| rio-tiler | Python 库 | 嵌入自定义服务 | 低 |
| titiler | 服务 | 动态切片与按需渲染 | 中 |
| GeoServer | 服务 | 企业 GIS 集成 | 高 |
| MapServer | 服务 | 高性能地图服务 | 中 |
大影像的分块处理
单幅超过内存的影像不能直接切片。标准做法是先转 COG,让 GDAL 通过分块与概览按需读取,再对 COG 切片。若影像由多幅拼接而成,先做镶嵌(mosaic)并统一坐标系与色彩,再切片,否则接缝处会出现色差与错位。
镶嵌时要注意重叠区处理:取最新、取均值、取最大值各有用途。时序监测常用取最新,正射底图常用取均值。镶嵌后必须检查是否有未覆盖的缝隙,缝隙会在瓦片中表现为白线。
4. 瓦片格式与压缩
瓦片格式直接决定传输体积与视觉质量,是优化空间最大的一环。
格式对比
- PNG:无损,支持透明,适合含透明区的专题图层与线划图,体积大。
- JPEG:有损,体积小,适合照片类底图,但不支持透明,透明区会变黑。
- WebP:同时支持有损与无损及透明,比 PNG 小 25% 以上,浏览器支持已很普及。
- AVIF:压缩率最高,但编码慢、兼容性略逊,适合可离线预编码的场景。
| 格式 | 透明 | 压缩率 | 编码速度 | 建议 |
|---|---|---|---|---|
| PNG | 支持 | 低 | 快 | 专题图层、线划 |
| JPEG | 不支持 | 中 | 快 | 卫星底图 |
| WebP | 支持 | 高 | 中 | 通用首选 |
| AVIF | 支持 | 最高 | 慢 | 离线预编码 |
gdal_translate -of MBTILES -co TILE_FORMAT=WEBP -co QUALITY=85 out_cog.tif out_webp.mbtiles # WebP 瓦片
gdaladdo -r average --config COMPRESS_OVERVIEW DEFLATE out_cog.tif 2 4 8 16 32 # 生成概览层
COG 与 MBTiles
COG(Cloud Optimized GeoTIFF)是带内部概览与分块的 GeoTIFF,支持 HTTP 范围请求,是动态切片与对象存储发布的基础。MBTiles 是 SQLite 单文件格式,便于分发与备份,但不支持并发写,适合静态成品而非频繁更新。
概览层(overview)是 COG 的关键。没有概览,缩小时的读取会退化为全分辨率扫描,性能差几个数量级。生成概览时用 average 重采样保色彩,用 nearest 保分类值。
色彩与波段处理
遥感影像常是多波段的,发布前要转成三波段 RGB 或做拉伸。常用手段有线性拉伸、百分比截断拉伸(如 2% 到 98%)、以及针对单波段的直方图均衡。拉伸参数应在整幅影像上统一计算,若逐瓦片拉伸会导致相邻瓦片亮度不一致,拼接后出现方块感。
多光谱与假彩色合成也是瓦片服务的常见需求。真彩色用红绿蓝波段,假彩色常用近红外替代红波段突出植被。这些组合最好在切片前固化成独立图层,而不是依赖前端实时合成,以降低前端复杂度。
5. 静态发布与对象存储
静态切片是最简单可靠的发布方式:把瓦片目录同步到对象存储,前面挂 CDN 即可。
目录结构与同步
瓦片目录按 z/x/y 组织,与前端请求路径一一对应,对象存储的 key 直接就是路径,无需任何服务端逻辑。
aws s3 sync ./tiles s3://my-bucket/tiles --cache-control "public, max-age=604800" # 同步并设缓存
aws s3 cp ./tiles s3://my-bucket/tiles --recursive --content-encoding gzip # 全量上传
缓存与跨域
缓存头是静态发布的灵魂。瓦片内容几乎不变,可设长缓存(如 7 天到 1 年),但更新时要用新路径或版本号绕过缓存。ETag 与 Last-Modified 帮助浏览器做条件请求,减少重复下载。
跨域(CORS)必须正确配置,否则浏览器会拦截瓦片请求。需要允许的响应头至少包含 Content-Type 与 ETag,允许的来源按需限定,不要图省事用通配符配合凭据。若用 CDN,CORS 头要在回源响应中带上并被 CDN 缓存。
6. 动态切片服务
动态切片在请求时按需渲染,无需预生成全部瓦片,特别适合数据频繁更新、波段组合多变或按权限过滤的场景。
titiler 部署
titiler 是基于 FastAPI 与 rio-tiler 的现代动态切片服务,支持直接从 COG 切片、波段表达式、重采样与色彩映射。它无状态,可水平扩展,配合对象存储即可服务海量影像。
from fastapi import FastAPI # Web 框架
from titiler.core.factory import TilerFactory # 切片工厂
app = FastAPI()
cog = TilerFactory() # 默认提供 XYZ 与 WMTS 端点
app.include_router(cog.router, prefix="/cog", tags=["COG"]) # 挂载路由
uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4 # 启动 4 进程
动态与静态的取舍
动态切片省去预生成,但每个请求都要读数据、解码、重采样、编码,CPU 开销大,且难以被 CDN 缓存(除非 URL 唯一确定)。实践中常用混合策略:热点区域预生成静态瓦片,长尾区域用动态服务兜底,两者通过同一 URL 前缀对外,前端无感知。
动态服务的性能关键在 COG 的分块与概览质量。分块过大导致单次读取浪费,过小导致请求数增多;概览缺失或层级不足会导致缩小时读取全分辨率数据。一般建议分块 512×512,概览层级覆盖到瓦片最小层级。
动态服务的缓存策略
动态切片默认难以缓存,因为 URL 往往只含影像 ID 与瓦片编号,若数据更新则缓存失效。解决方法是把版本号或数据指纹写进 URL,让 URL 唯一对应一份数据,之后即可放心使用长缓存。这既保留了动态的灵活性,又拿到了静态的缓存收益。
服务端还可加一层内存或 Redis 缓存,缓存最近访问的热点瓦片。热点通常集中在城市中心与常用缩放级别,缓存命中率往往能达到很高,能显著削峰。
7. 矢量瓦片与 MVT
矢量瓦片把矢量数据按瓦片组织,前端渲染,相比栅格瓦片可动态改样式、无极缩放、体积更小。
生成方式
tippecanoe 是最常用的命令行工具,能从 GeoJSON 生成 MVT 并自动做简化与抽稀;PostGIS 的 ST_AsMVT 可直接在数据库里聚合生成,适合动态查询场景。
SELECT ST_AsMVT(t, 'roads', 4096, 'geom') -- 生成名为 roads 的图层
FROM (
SELECT ST_AsMVTGeom(ST_Transform(geom, 3857), ST_TileEnvelope(12, 3400, 1650)) AS geom, name, type
FROM roads WHERE geom && ST_TileEnvelope(12, 3400, 1650) -- 空间过滤
) AS t;
图层与属性设计
MVT 支持多图层与任意属性,但属性越多体积越大。只保留渲染需要的字段,长字段名换成短码,字符串枚举转整数,能显著减小体积。几何要按缩放级别做简化,低层级用抽稀后的概览几何,高层级用完整几何。
矢量瓦片的一个常见问题是坐标系必须是 EPSG:3857,且坐标要按瓦片范围做局部归一化(MVT 规范用 4096 或 8192 的整数网格)。坐标系错了会让矢量错位,归一化错了会让精度丢失。
8. 前端加载与性能优化
前端是瓦片服务的最终消费者,加载策略直接决定体验。
MapLibre 配置要点
const map = new maplibregl.Map({
container: "map",
style: {
version: 8,
sources: {
sat: { type: "raster", tiles: ["/tiles/{z}/{x}/{y}.webp"], tileSize: 512, maxzoom: 18 }
},
layers: [{ id: "sat", type: "raster", source: "sat" }]
}
});
优化手段
- tileSize 与真实瓦片尺寸一致,设置错误会导致瓦片被错误缩放。
- 限制最大缩放级别,超过数据分辨率的缩放只会拉伸模糊,浪费带宽。
- 合理预取:当前视野外一圈的瓦片提前加载,减少拖动的空白等待。
- 浏览器缓存与服务端缓存配合,二次访问应几乎零请求。
- 高 DPI 屏幕若需要 512 像素瓦片,应使用 512 尺寸的瓦片而非 256 放大。
前端还要处理瓦片缺失的优雅降级。空白瓦片不要返回 404 让控制台刷屏,返回一张 1×1 的透明 PNG 或 204 状态更合适,但要与真正的错误区分开。
移动端与离线加载
移动端网络波动大,瓦片加载要更保守。可降低最大缩放级别、用更小体积的 WebP、并对已访问区域做持久缓存。若需要离线使用,通常预下载指定范围与层级的瓦片包,用 MBTiles 存到本地,由应用从本地读取。
离线包的体积要提前估算并让用户可见。一个城市 z=10 到 z=16 的底图包可能达到数百 MB 到数 GB,必须在下载前提示体积与用途,避免用户流量被意外消耗。
9. 服务运维与容量规划
瓦片服务上线后的核心指标是缓存命中率与响应延迟。
关键指标
| 指标 | 含义 | 目标 |
|---|---|---|
| 缓存命中率 | CDN 命中请求占比 | 大于 90% |
| P95 延迟 | 95 分位响应时间 | 小于 200ms |
| 回源率 | 回源请求占比 | 小于 10% |
| 瓦片缺失率 | 404 或空白占比 | 小于 1% |
| 单瓦片体积 | 平均传输大小 | 小于 50KB |
容量规划
容量估算的起点是每日瓦片请求数与平均瓦片体积。假设日请求 1000 万次、平均 30KB,则日流量约 300GB,全部回源的带宽需求远超单机能力,必须依赖 CDN。回源带宽按回源率折算,10% 回源即 30GB/日,普通实例足够。
金字塔级别要按需裁剪。若业务最大只到 z=16,就不要切到 z=20,否则存储与切片时间成倍增加却无人访问。同理,只切业务覆盖范围,不要对全球空白区切片。用 gdal2tiles 的 zoom 参数与范围裁剪控制输出。
监控要覆盖服务与数据两侧。服务侧看 QPS、延迟、错误率、回源率;数据侧看瓦片缺失、体积异常、更新滞后。数据侧问题往往更隐蔽,一次失败的更新可能让某个区域长期返回旧瓦片。
灰度发布与数据更新
底图更新不能一次性全量替换,否则新数据的错误会立刻影响所有用户。稳妥做法是灰度:先切一个小范围或低层级,验证无误后再逐步扩大。配合版本化路径,新旧瓦片可以共存,出问题随时回滚。
更新策略还要考虑缓存穿透。当瓦片路径变化时,所有缓存失效,回源量会在短时间内激增。可以分批切换路径,或在 CDN 上做预热,把新瓦片提前推到边缘节点,避免切流瞬间打爆回源。
权衡取舍
| 决策点 | 选项 A | 选项 B | 建议 |
|---|---|---|---|
| 发布方式 | 静态预切片 | 动态切片 | 底图静态,专题动态 |
| 瓦片尺寸 | 256 | 512 | 现代框架用 512 |
| 格式 | PNG(无损透明) | WebP(体积小) | 通用首选 WebP |
| 存储 | 目录加对象存储 | MBTiles 单文件 | 在线用目录,分发用 MBTiles |
| 切片范围 | 全层级全范围 | 按需裁剪 | 一律按需裁剪 |
核心权衡是成本与灵活性。静态切片成本低但更新慢,动态切片灵活但算力贵。多数系统的最优解是混合,且用 CDN 把静态部分的成本压到最低。
成本对照
| 方案 | 一次性成本 | 运行成本 | 更新成本 |
|---|---|---|---|
| 静态预切片 | 切片算力与存储 | 极低(CDN 为主) | 高(需重切) |
| 动态切片 | 低 | 高(每请求算力) | 极低(数据即改即用) |
| 混合 | 中 | 中 | 中 |
上表的成本是相对量级,实际差异可达一个数量级以上。静态切片的一次性切片时间随层级指数增长,但此后几乎只有存储与 CDN 费用;动态切片的单次请求成本虽小,乘以请求量后往往远超静态方案的存储开销。
判断依据是更新频率。数据一年更新一次,静态切片几乎必然更划算;数据每天更新,动态切片或增量切片才是正解。增量切片只重切变化区域,是静态方案应对高频更新的折中手段。
常见坑清单
- 瓦片上下颠倒:XYZ 与 TMS 的 y 轴约定混用,配置里显式声明编号方案。
- 影像整体错位几百米:用了 GCJ-02 底图配 WGS84 数据,做坐标转换或统一到同一基准。
- 透明区变黑:用了 JPEG 存带透明的瓦片,改用 PNG 或 WebP。
- 缩小时加载极慢:COG 缺概览层,用 gdaladdo 生成并覆盖到最小层级。
- 瓦片请求被浏览器拦截:CORS 头缺失或未在 CDN 回源响应中带上。
- 更新后前端仍是旧瓦片:缓存头过长且路径未变,用版本化路径或缩短缓存。
- 拖动时大片空白:预取不足或并发限制太低,调整预取范围与并发数。
- 存储暴涨:切了无人访问的高层级或全球范围,按业务范围与最大级别裁剪。
- 动态服务高峰被打满:无缓存且未水平扩展,加 CDN 或改静态预切片。
- 矢量瓦片属性体积过大:保留了全部字段,只留渲染字段并做枚举压缩。
- 瓦片边缘出现细白线:切图时未做边界缓冲或镶嵌有缝,开启重采样溢出并检查覆盖。
- 相邻瓦片亮度不一致:逐瓦片拉伸而非全图统一,改为全图统计后统一拉伸参数。
小结
影像瓦片服务的本质是「用空间换时间、用预计算换带宽」。理解金字塔与网格是前提,选对格式与存储是基础,缓存策略与容量规划决定上线后的稳定性。把这几件事做扎实,一个静态目录加 CDN 就能支撑起很高的并发。
对工程团队而言,建议先把底图做成静态 COG 加瓦片目录,用对象存储与 CDN 发布,把成本压到最低;只有当数据频繁更新或需要按需渲染时,才引入 titiler 这类动态服务。无论哪种方式,都要把坐标方案、瓦片编号约定、格式与缓存策略在配置里写死,避免跨团队集成时的隐性错误。
还有一点容易被忽略:瓦片服务是长期运行的基础设施,数据格式与目录结构一旦对外发布就很难更改。因此在设计初期就要把版本化、多格式并存、按需裁剪这些能力留好接口,宁可多花一点设计成本,也不要等到需要兼容时再推倒重来。
下一步可把切片管线接入数据平台做自动化,相关思路见 数据湖技术选型 ,影像的预处理与栅格读写优化可参考 栅格数据读写与 GDAL ,整体遥感数据链路见 遥感与空间数据概览 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。