资讯动态

无需ArcGIS Server:用Nginx在Cesium中加载ArcGIS切片

发布时间:2026/10/17 1:21:43 来源:尧图企业网站定制
做 Cesium 项目时最容易让人懵的一个需求就是“加载非 ArcGIS Server 发布的 ArcGIS 切片”。我第一次接到这种需求第一反应是反问那 Server 地址总该有吧对方说没有 Server就给我一筐切片文件。后来想明白了ArcGIS 切片本质上就是一套带规则的静态图片只要把规则对上Cesium 完全可以直接把它们拉出来显示中间不需要再架一个重型 GIS 服务。1. 为什么会有这种需求1.1 我遇到的几种真实场景这种需求不是个例我梳理了一下实际项目里最常见的是下面几类第一类是“存量数据”问题。某单位内网里有一批很多年前用 ArcMap 切好的缓存当初生成之后直接拷贝到某个磁盘目录里并没有配套部署 ArcGIS Server。等后来想把这些数据接到新系统里时怎么在 Cesium 里显示就成了难题。重新买授权、重新配环境对很多项目来说性价比太低。第二类是不想暴露重型服务。有些对外展示项目只需要一个静态底图前端要求尽量简单能部署一个 Nginx 就绝不上 ArcGIS Server。反正切片都已经生成好了Server 在这条链路里本质上就是个“读文件 回头发 HTTP 响应的转发器”为什么不直接用静态服务器替代第三类是前端团队主导的项目。现在不少 Web GIS 项目由前端工程师负责他们对 GIS 服务的部署不是很熟但对静态资源和 URL 模板非常熟。如果切片文件可以直接丢到静态服务器上再在 Cesium 里用 URL 模板去匹配整个接入成本会低很多。1.2 Server 在切片加载链路里到底扮演什么角色想明白这件事得理解 ArcGIS Server 发布缓存底图时的流程。Server 接收浏览器的瓦片请求比如http://host/arcgis/rest/services/xxx/MapServer/tile/5/12/23然后它内部根据切片方案把这个 z/x/y 换算成缓存目录里的某一级某一列某一行的文件再把文件内容吐回去。换句话说对于已经切好且没有动态变化需求的数据Server 的角色就是“翻译器 静态文件阅读器”。既然这层翻译逻辑是固定的我们完全可以自己写规则或者用现成的 URL 模板去模拟。ArcGIS 缓存的目录结构本身就写得很直白规则就在 conf.xml 里。只要 Cesium 的 Provider 能把请求落点到正确路径上剩下的就是一次普通的 HTTP 图片请求。1.3 一句话说明技术路线整条技术路线可以压缩成四步读懂缓存目录结构和 conf.xml 里的切片方案用 Nginx 或任意静态服务器把缓存根目录发布出去在 Cesium 里用 UrlTemplateImageryProvider 按缓存路径规则拼 URL根据切片坐标系选择 WebMercator 还是 Geographic 的 TilingScheme如果你只是需要一个能跑的方案可以直接跳到第 3 节如果希望以后不再被各种诡异缓存坑到建议把第 2 节看完很多坑都是因为没读 conf.xml 导致的。2. 读懂 ArcGIS 缓存目录结构与 conf.xml2.1 先认清缓存目录里有什么一套标准的 ArcGIS 切片缓存目录结构通常长这样arcgis_cache/ Layers/ _alllayers/ L00/ R00/ C00.png C01.png R01/ C00.png C01.png L01/ R00/ C00.png ... conf.xml这里有几个容易混淆的地方。Layers一般对应地图文档里的图层组名_alllayers表示所有图层合成后的底图切片。如果你是用 ArcMap 的“缓存”工具生成的根目录下还有conf.xml它是整套切片的“身份证”后面所有解析都靠它。L00是第 0 级R00是第 0 行C00是第 0 列。列号向右增加行号向下增加。这种编号方式和常见 Web 墨卡托 XYZ 瓦片非常像区别只在于目录命名里多了字母前缀而且经常带两位数字的零填充。另外缓存里也可能不是 PNG而是 JPEG。这取决于当初切片时选的格式。在 URL 拼接时文件后缀要跟实际目录里的文件一致不然静态服务器返回的 MIME 类型不对浏览器能打开但 Cesium 不一定认。2.2 conf.xml 里的关键信息conf.xml 是 XML 格式里面通常包含SpatialReference、TileCacheInfo、LODInfos、TileOrigin、CacheTileFormat等节点。刚开始接触的人容易忽略它但我可以负责任地说90% 的加载失败都是因为没看 conf.xml 就直接写 URL。重点看四个信息SpatialReference切成缓存用的坐标系到底是 Web MercatorEPSG:3857还是 WGS84 经纬度EPSG:4326。这决定了 Cesium 里用哪个 TilingScheme。TileOrigin瓦片编号的起点坐标。一般 Web 墨卡托缓存的原点在左上角也就是(-20037508.3427892, 20037508.3427892)。WGS84 缓存的原点通常是(-180, 90)。LODInfos每一级的比例尺、分辨率、级数。这里能直接数出一共有多少级对应 Cesium 里的maximumLevel。CacheTileFormat确认瓦片格式是 PNG 还是 JPEG用于后续写 URL 后缀。举个例子某个缓存的 LOD 列表里有 LevelID 从 0 到 14那说明这套缓存最小支持第 0 级最大到第 14 级。如果最小级是 2那前端里就把minimumLevel设为 2否则 Cesium 会去请求不存在的 L00、L01。2.3 为什么说 ArcGIS 缓存和 XYZ 的关系很近Web 墨卡托模式下ArcGIS 缓存和常见的 XYZ 瓦片服务非常像原点都在左上角列号向右加行号向下加第 0 级通常是整个世界一张图之后逐级四叉树切割。所以对大多数标准 Web 墨卡托缓存Cesium 的请求坐标完全可以和 R/C 对应上。比如 Cesium 请求第 5 级、第 12 列、第 3 行对应的路径就是L05/R03/C12.png如果你拿到的是 WGS84 地理坐标缓存规则类似只是换成了 Geographic 切片方案。Cesium 同样支持只需要把tilingScheme改成GeographicTilingScheme。但这里也要提醒一句如果缓存不是标准全球切图而是只针对某个局部范围生成并且 LOD 的起点也不是从 0 开始那 R/C 编号虽然仍按左上角原点递增但 Cesium 请求时可能会因为缺少某个范围内的四级瓦片而出现大量 404。这种情况不是路径规则错了而是数据覆盖范围有限需要在前端限制显示范围。3. 实操用 Nginx UrlTemplateImageryProvider 直接加载3.1 静态服务器配置我通常用 Nginx配置很薄。假设缓存目录在服务器上的实际路径是/data/arcgis_cache/希望在 Web 上以/arcgis_cache/这个前缀访问可以这样写server { listen 80; server_name cache.example.local; location /arcgis_cache/ { alias /data/arcgis_cache/; add_header Access-Control-Allow-Origin *; expires 7d; } }这里有两个比较重要的点。第一是 CORS。如果你的 Cesium 页面部署在另一个域名或端口Nginx 必须添加Access-Control-Allow-Origin响应头否则浏览器会因为跨域拦截图片请求Cesium 里看到的是一片空白但 Network 面板里又好像有请求。第二是缓存过期时间。切片基本是不可变文件建议给足expires让浏览器把图片缓存住后端压力能小不少。不过也要注意有时候开发阶段改动很频繁临时把expires关掉或者设成no-cache更方便。3.2 Cesium 端加载代码Cesium 加载这种静态切片最合适的是UrlTemplateImageryProvider。它本身就是干这事的按照你给定的 URL 模板去请求瓦片。这里以 Web 墨卡托缓存为例const provider new Cesium.UrlTemplateImageryProvider({ url: http://cache.example.local/arcgis_cache/Layers/_alllayers/L{z}/R{y}/C{x}.png, tilingScheme: new Cesium.WebMercatorTilingScheme(), minimumLevel: 0, maximumLevel: 14, rectangle: Cesium.Rectangle.fromDegrees(116.0, 39.0, 117.0, 40.0), // 按实际数据范围调整 }); const viewer new Cesium.Viewer(cesiumContainer, { baseLayer: false, }); viewer.imageryLayers.addImageryProvider(provider);这段代码就做了一件事告诉 Cesium把瓦片坐标请求组装成这种 URLhttp://cache.example.local/arcgis_cache/Layers/_alllayers/L{z}/R{y}/C{x}.png{z}会替换成级别{x}替换成列号{y}替换成行号。因为 ArcGIS 缓存的行号方向和 Cesium 的行号方向都是从左上角向下递增所以R{y}这一步通常能直接对上。rectangle参数不是必须的但如果这套缓存只覆盖一个城市最好把它传进去。否则 Cesium 以为自己可以拉全球数据会请求大量超出缓存范围的瓦片浪费流量和请求数。3.3 第一轮调试怎么排查写完之后如果地图没出来我建议先不要急着改代码打开浏览器 DevTools 的 Network 面板看 Cesium 到底发出去了什么请求。如果请求 URL 返回 404优先检查两件事缓存的路径前缀是否写对了尤其是图层目录名。有人用 ArcMap 生成缓存时输出图层名不叫 Layers可能叫“行政区”或者“BaseMap”此时 URL 里也要对应修改。L{z}是否匹配实际目录名。缓存目录如果是L00、L01而 Cesium 请求的是L0/R1/C2.png自然 404。如果请求 URL 正常返回图片但 Cesium 里还是看不到那可能是 TilingScheme 选错了。例如实际缓存是 WGS84 地理坐标系但代码里用了 WebMercatorTilingScheme瓦片坐标和真实经纬度对应不上表现就是错位或者一片空白。4. 遇到非标准缓存零填充目录名与行列转换4.1 最常见的零填充问题ArcGIS 缓存目录经常写成两位数字L00、L01一直到L14。但 Cesium 的{z}是普通整数不会自动补零于是产生了L0/R1/C2.png和目录中L00/R01/C02.png不匹配的问题。我早期做项目时就在这上面吃过亏直接拼{z}结果前 15 级全部 404人还以为是 Nginx 路径配错了排查了半天才发现是零填充惹的祸。解决办法有三种第一种改目录名。把L00改成L0、L01改成L1。如果缓存文件不多或者你可以用脚本批量处理这是最直接的方式。缺点是需要动数据文件可能影响其他地方引用。第二种用 URL 模板的自定义标签。Cesium 的UrlTemplateImageryProvider支持customTags可以在生成 URL 时做任意字符串转换。处理零填充的代码就像这样function padZero(value, length) { return String(value).padStart(length, 0); } const provider new Cesium.UrlTemplateImageryProvider({ url: http://cache.example.local/arcgis_cache/Layers/_alllayers/L{level}/R{row}/C{col}.png, customTags: { level: function (imageryProvider, x, y, level) { return padZero(level, 2); }, row: function (imageryProvider, x, y, level) { return padZero(y, 2); }, col: function (imageryProvider, x, y, level) { return padZero(x, 2); }, }, tilingScheme: new Cesium.WebMercatorTilingScheme(), maximumLevel: 14, });这样{level}、{row}、{col}这些自定义标签会先经过你的函数格式化再拼进最终 URL。代码并不复杂但这是官方文档之外很多人不知道的套路。第三种用轻量代理中转把所有非标准规则消化在后端。这个方案我放在第 4.3 节详细说。4.2 行号方向不一致怎么办多数 ArcGIS 缓存的行号是自上而下和 Cesium 的{y}一致。但也有例外比如某些工具生成的缓存采用 TMS 标准行号自下而上。这种情况下直接写R{y}会取到完全错误的行。Cesium 的 URL 模板里有个现成的{reverseY}标签可以直接把行号反向换算换算公式是reverseY (2^level) - 1 - y如果你需要处理这种缓存URL 可以写成R{reverseY}/C{x}.png并用 Cesium 对应的 XYZ 或自定义标签机制去触发。实际使用前最好先找一张已知位置的瓦片手动验证别盲信代码。怎么验证拿到缓存里某个Rxx/Cxx.png打开图片看它是什么位置再用 Cesium 或者其他工具算一下该位置对应的瓦片坐标对照一下就知道行号方向了。4.3 最不折腾的方案用一层中转服务如果缓存规则实在别扭比如目录名里带中文、小级别不是从 0 开始、行号方向特殊、或者以后还想加鉴权最简单的方式就是写一个特别小的服务端应用对外统一暴露标准 XYZ 接口对内翻译成老缓存的路径。Express 写起来很薄大致是const express require(express); const path require(path); const app express(); const cacheRoot /data/arcgis_cache/Layers/_alllayers; app.get(/tile/:z/:x/:y.png, (req, res) { const z req.params.z; const x req.params.x; const y req.params.y; const fileName L${String(z).padStart(2, 0)}/R${String(y).padStart(2, 0)}/C${String(x).padStart(2, 0)}.png; res.sendFile(path.join(cacheRoot, fileName)); }); app.listen(8080);这个代理层的最大好处是把所有乱七入糟的路径逻辑放在服务端改前端永远只面对标准 XYZ URL。以后即使底层缓存换了目录结构前端代码也不用动。我实际经手的项目中最终上线的大多数都是这种方案而不是在前端堆customTags。4.4 什么时候该放弃直接加载有一种情况我不建议硬解如果 conf.xml 里的 LODs 并不是标准四叉树比例尺每一级的范围、行列数完全是自定义的那 Cesium 的瓦片坐标跟缓存文件位置之间就不存在简单的映射关系。这时候强行匹配 URL 只有一个结果错位、黑屏、请求 404。真正省时的做法是用 GDAL 等工具把原切片重新整理成标准 XYZ 瓦片或者重新生成一套 Web 墨卡托缓存。虽然要花点时间但比起在前端反复试错收益高多了。想判断是否标准最简单的办法是看第 0 级如果缓存里有 L00 且 R00/C00 是世界范围的一张图那基本上是标准方案。如果 L00 只有几张图并且范围不对那很可能不是标准比例尺切法。5. 常见问题与排查实录我在接入过程中踩过不少坑也帮别人排查过不少问题。下面列一个速查表基本覆盖了最常遇到的几类情况现象可能原因处理方式请求 URL 404但文件确实存在目录名带零填充或图层名不对补零 / customTags / 代理层转换路径地图整体错位或偏移TilingScheme 选错看 conf.xml 的 SpatialReference换成 WebMercator 或 Geographic部分级别没有瓦片maximumLevel 设太高或缓存缺层级按 LODInfos 数据调整最大级别页面跨域图片加载不出来静态服务器缺少 CORS在 Nginx 添加 Access-Control-Allow-Origin图片有黑色背景瓦片格式是 JPEG或源数据背景色异常换 PNG检查图片本身是否带透明通道0 级请求过多导致压力大minimumLevel 没设对按 conf.xml 设置起始级别并限制 rectangle缓存是 TMS 风格行号反向行号方向不一致用 {reverseY} 或代理层换算拉出全世界的空白瓦片rectangle 没限制缓存只覆盖局部范围传 rectangle限制可视区域这里再补充一个真实场景。某次客户提供的缓存目录第一层目录名不是Layers而是他们自己定义的中文图层名。Nginx 配置没问题URL 模板也没问题但就是 404。后来打开目录一看_alllayers的上级目录完全不是常规名字改成对应名称后立刻正常。所以拿到缓存以后不要想当然最好先ls一下目录结构确定实际路径。排查时还有一个快速技巧在浏览器地址栏直接打开一个瓦片 URL比如http://cache.example.local/arcgis_cache/Layers/_alllayers/L03/R01/C02.png如果图片能打开说明静态服务、跨域、路径都没有大问题问题基本集中在 Cesium 侧的坐标映射上。6. 我的落地经验和几点建议6.1 先读 conf.xml 再写代码这句话我说过不止一次因为在真实项目里真的太容易吃亏。拿到缓存后第一件事不是写代码而是读 conf.xml。看SpatialReference是 3857 还是 4326看LODInfos里到底有多少级看TileOrigin的坐标起始点。全部确认之后再写 Cesium 代码基本能做到一次过。有一次我接手一个项目上一任开发留了一堆 Cesium 代码加载始终黑屏。我打开 conf.xml 一看缓存的坐标系是 4326但代码里写死的是WebMercatorTilingScheme整个坐标体系都是错的。光这个问题就折腾了好几天。6.2 给瓦片请求设置合适的边界很多人的第一版代码只写了url没有写rectangle于是 Cesium 默认认为这套缓存覆盖全球。当地图缩小到全国甚至全球范围时Cesium 会拼命请求大量根本不存在的瓦片服务端日志里全是 404。解决办法是给UrlTemplateImageryProvider传rectangle限制在真实数据范围内。这个范围可以从 conf.xml 的切片范围信息或者源数据范围获取也可以通过一个简单方式验证打开某一张瓦片看出边界经纬度逐步调整。6.3 本地开发时注意 Nginx 缓存问题开发阶段我建议把 Nginx 的expires关掉或者设置得很短否则浏览器把错误的旧瓦片缓存下来改了路径也看不到效果非常耽误事。等确定没问题了再开启长时间缓存。还有一点很多前端页面调试时用的是file://协议打开这时候发起的网络请求很容易触发跨域问题。最好都通过本地静态服务器或 dev server 预览保持http://协议环境。6.4 如果只是短期展示临时方案也比硬解强如果你只是需要给客户演示一两天最省事的做法甚至可以不配 Nginx直接用 Node 写个几十行的静态文件服务把缓存目录映射到 HTTP 路径。等演示完再决定要不要上正式环境。总之先让瓦片通过 HTTP 能拿到再谈 Cesium 加载。7. 更进一步除了直接加载还有什么选择7.1 用地理服务器重新发布如果你手里有 ArcGIS 缓存但又希望前端用 WMTS、WMS 这类标准服务接口同时还想省掉 ArcGIS Server可以把数据整理后用开源地理服务器发布。底层还是同一批瓦片但对外提供的是标准 WMTS 接口Cesium 里用WebMapTileServiceImageryProvider就能接。这种方式适合那些“不想碰 URL 模板”的团队缺点是要多维护一个服务进程。7.2 把 ArcGIS 缓存转成标准 XYZ其实 GDAL 和一些小工具都能处理 ArcGIS 缓存。标准 XYZ 的目录结构就是tiles/{z}/{x}/{y}.png没有 L、R、C 前缀也不存在零填充问题。转换完成之后Nginx 直接发静态目录Cesium 配置一个最简单的 URL 模板就行。转换省去了各种规则的匹配换来的是通用性和长期稳定性。7.3 是否需要考虑动态投影如果你的缓存是本地坐标系或者高斯投影那不能直接在 Cesium 全球框架下使用因为 Cesium 的地球模型基于 WebMercator 或 WGS84。强行加载结果就是瓦片位置完全对不上。这种情况往往需要先把空间数据重投影再重新切片。这不是 Cesium 的问题而是底图数据本身就不适合用于全球地图底图。实际项目中那些可以正常加载的非 ArcGIS Server 切片绝大多数都是标准 Web 墨卡托或 WGS84 缓存。所以标题里的需求能成立的前提是数据本身采用了 Cesium 能直接支持的切片坐标系。目前我接手类似需求时已经养成了一个固定习惯不管缓存看起来多简单都先打开 conf.xml把 SpatialReference 和 LODInfos 看一遍再决定 TilingScheme 怎么设置。很多让人头疼的错位问题在第一步就能避免。至于非标准目录和特殊行列映射尽量用一个薄薄的中转服务搞定而不是在前端堆积大量转换逻辑这能少走非常多的弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑