多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Cesium加载WMTS白板与Input string异常:成因定位与修复实践

Cesium加载WMTS白板与Input string异常:成因定位与修复实践 Cesium 里加载 WMTS 服务碰到白板加一个 “Input string was not in a correct format.” 的异常这组合拳打下来确实让人头皮发麻。我最早遇到这个报错是在对接某地方基础地理信息平台的影像服务时控制台红字一出地图上干干净净连个网格都不给。这个异常信息看着像 .NET 里的 FormatException实际上在 Cesium 的 JavaScript 环境里出现多半是 WMTS 的 Capabilities 文档里某个数值字段不能被正确解析导致的。这个组合问题的麻烦点在于白板和异常可能来自同一个根因也可能是两个独立问题叠加。比如 XML 里 TileMatrix 的 ScaleDenominator 写成了“1.0E-5”这种科学计数法Cesium 解析失败抛异常地图服务直接中断又比如服务本身正常但代码里没配 tilingScheme瓦片请求路径对不上照样白板。这篇文章就沿着这两条线往下挖把异常来源、白板成因、排查手段、修复方案一次性讲清楚适合正在对接各种 WMTS 影像、矢量瓦片服务的 Cesium 开发者参考。1. 先搞清楚这俩症状到底在说什么1.1 白板和异常是不是同一个问题很多人看到白板和异常同时出现第一反应是“异常导致了白板”。这个判断只对了一半。Cesium 的WebMapTileServiceImageryProvider在初始化阶段会去请求服务的 GetCapabilities 文档拿到 XML 之后做解析。解析失败时provider 创建不成功图层自然就不渲染表现为白板同时控制台抛异常。这种情况属于一因一果。但还有一种情况provider 初始化成功了异常也不是每次都抛但地图还是白板。这时候往往不是解析的问题而是瓦片请求的 URL 格式和服务器实际提供的格式对不上或者请求返回了 404、跨域错误。这类问题会表现为请求发出去但没瓦片回来地图区域一片空白。所以第一步要做的不是急着改代码而是先确认异常是每次都稳定出现还是随机出现图层是彻底加载不出来还是某些层级有、某些层级没有这个判断直接决定后面排查的方向。提示区分“初始化失败”和“请求失败”最简单的办法是在 Network 面板里看有没有发出去瓦片请求。如果一个瓦片请求都没有那基本是 provider 初始化阶段就挂了如果有请求但返回错误或空内容那是请求链路的问题。1.2 WMTS 在 Cesium 里是怎么被加载的要理解后续的排查动作得先知道 Cesium 加载 WMTS 的完整链路。WebMapTileServiceImageryProvider的工作流程大致是根据传入的url拼接 GetCapabilities 请求地址加上serviceWMTSrequestGetCapabilities参数。请求回来的是 XML 文档Cesium 用浏览器内置的DOMParser解析它。从文档中提取Contents里的Layer、TileMatrixSet、ResourceURL或ResourceTemplate等关键信息。用这些信息构造瓦片 URL 模板后续每个瓦片请求都按这个模板拼接。渲染阶段按当前相机的范围和层级逐个请求瓦片并贴到地球表面。这个过程中第三步是解析逻辑最重的部分也是标题里那个异常的高发区。Cesium 在解析 TileMatrixSet 时会把TileWidth、TileHeight、ScaleDenominator、TopLeftCorner这些字段从 XML 字符串转成数值。任何一个字段的字符串格式不符合规范转换就会失败。如果 Capabilities 文档本身不符合 OGC WMTS 标准或者某些字段用非标准方式填写Cesium 解析器就会遇到“无法转换”的情况最终抛出类似 “Input string was not in a correct format” 的异常。这是整个问题最核心的机制也是理解后续所有排查动作的基础。2. “Input string was not in a correct format”到底是谁抛的2.1 异常的真实来源这个异常的文本格式确实很像 .NET 的FormatException但 Cesium 是纯 JavaScript 库浏览器环境里没有 .NET 运行时。所以这里要理解的是这是 Cesium 内部在字符串转数值失败时主动抛出的错误Cesium 早期版本在底层函数里用了类似整型解析的工具函数失败时就抛出了这个与 .NET 异常文本相同的消息。具体来说Cesium 在解析 TileMatrix 的ScaleDenominator时期望的格式是形如5.364418e-5或0.00005364418这样的浮点数字符串。但如果服务端返回的是5.364418E-05这种大小写混合的科学计数法或者返回了unknown、空字符串、带单位“m”的数值Cesium 的解析函数就会报错。实际工作中我发现这类异常最常见于两种情况一是服务端自己实现的 WMTS 发布工具对标准支持不完整二是 Capabilities 文档经过了某些中间层网关的格式转换导致数值字段损坏。比如某厂商的地图服务器在输出 XML 时把ScaleDenominator节点写成了自闭合标签但没填内容就会直接导致解析失败。2.2 最容易中招的 5 个元凶根据我这些年对接各种 WMTS 服务的经验能触发这个异常的字段主要集中在以下几个方面字段名期望格式服务端的常见坑TileWidth / TileHeight正整数如 256写成了带引号的字符串或者“256.0”带小数ScaleDenominator浮点数如 100000.0写成“1:100000”或“1.0E-5”风格TopLeftCorner两个用空格隔开的浮点数用逗号分隔或包含单位“m”MatrixWidth / MatrixHeight正整数写成了十六进制如“0x20”TileMatrix 的 Identifier字符串但通常建议纯数字包含特殊符号“/”或反斜杠影响后续 URL 拼接这 5 个字段里最容易被忽略的是TileMatrix的Identifier。有些服务会把 Identifier 写成“EPSG:3857_10”这种带前缀的形式这在标准上是允许的但 Cesium 默认会用它去拼瓦片 URL。如果服务器端的路径规则不认这个前缀就会发生请求失败而请求失败的表现往往就是白板还不是这个异常。2.3 自己先别慌三步定位异常碰到这个异常我的建议是不要立刻改代码先做三步定位第一步在控制台把完整的堆栈信息打开。浏览器控制台默认可能只显示一行错误信息点开箭头可以看到完整堆栈。异常堆栈会指向 Cesium 源码的具体位置比如parseTileMatrixSet、extractTileMatrixSet这类内部函数。看到函数名就能初步判断是哪个字段出问题。第二步直接访问 GetCapabilities 地址。在浏览器地址栏里输入服务地址加上必要的参数把 XML 文档直接打开。然后用开发者工具的搜索功能逐个检查可疑字段重点看TileWidth、ScaleDenominator、TopLeftCorner三个字段的原始内容。第三步如果 XML 内容量太大不好直接看就复制到本地用格式化工具处理一下或者写一个简单脚本把这个 provider 的初始化过程包起来在 try/catch 里把异常信息打印到日志面板同时把捕获到的 XML 片段一起打印。这个做法在对接第三方服务时特别管用因为很多时候服务方不会承认自己返回的数据有问题你拿着 XML 片段去沟通对方就不好推脱了。注意千万不要在 provider 创建失败后直接换用UrlTemplateImageryProvider绕过解析。这个方案虽然能快速躲开异常但你会失去 Capabilities 文档中的层级范围、分辨率信息后续瓦片层级配置全靠手写非常容易出新的白板问题。3. 实操修复从 Capabilities 到完全跑通3.1 用开发者工具拿到真实报错堆栈定位到具体问题后修复思路就清晰了。但先别急着改服务端因为很多时候服务端不是你负责的你无权修改对方的发布配置。这种情况下我们的思路是要么在客户端做预处理要么换一种 provider 加载方式。在动手之前把开发者工具的 Network 面板打开选中 Fetch/XHR 过滤刷新页面。你会看到 Cesium 发出的 GetCapabilities 请求。点击这条请求可以在 Response 标签页里看到完整的 XML 内容。这一步非常关键因为这能确认 Cesium 拿到的 XML 和我们预期的完全一致。我在实际操作中发现有些服务经过了反向代理或负载均衡直接访问源站的 Capabilities 和通过代理访问的返回内容可能不同。如果你在浏览器地址栏里直接访问源站地址测试没问题但代码里配置的是代理地址就可能出现“地址栏能打开但 Cesium 报错”的诡异情况。所以一定要以开发者工具里抓到的实际响应内容为准。3.2 拉取并检查 GetCapabilities 文档拿到 XML 后重点检查以下几个位置第一确认ows:Operation节点里的GetTile请求地址。有些服务把 GetCapabilities 和 GetTile 放在不同的地址上Cesium 默认会用 GetCapabilities 的地址去拼 GetTile 请求如果服务端把两者分开就需要在 provider 配置里显式指定tileUrl或url参数。第二确认ResourceURL模板。标准做法是模板里包含{TileMatrixSet}、{TileMatrix}、{TileRow}、{TileCol}这些占位符。Cesium 会用内部值填充这些占位符。如果模板里用了{Style}占位符但服务端不区分样式或者反过来用了自定义维度名Cesium 就不知道该怎么替换。第三逐个检查 TileMatrixSet 里的数值字段。用表格对照上面列出的期望格式逐项确认。尤其注意ScaleDenominator是不是科学计数法TopLeftCorner是不是空格分隔。有个技巧如果 XML 内容太长可以直接在浏览器控制台里运行一段简短的解析脚本把每个 TileMatrixSet 的字段提取出来输出成表格。这样对比起来非常直观也方便在文档里记录。3.3 四种修复手段改服务端/预处理/自定义 provider/换服务根据权限和场景不同修复手段可以分成四类我按推荐程度排序。第一类改服务端配置。这适用于你自己负责 GIS 服务发布的情况。以常见的 GeoServer 或 ArcGIS Server 为例在发布 WMTS 服务时注意选择正确的输出格式自定义 Capabilities 模板时不要删减或改动数值节点。核心原则是服务端返回的 XML 必须是合法、无缺失、字段格式标准的内容。这类修复最彻底一劳永逸。第二类客户端预处理。适用于服务端你无法修改但 XML 内容只是个别字段不规范的情况。可以用 XMLHttpRequest 或 fetch 把 GetCapabilities 拿回来用 DOMParser 解析把不合规的字段修正后再传给 Cesium。具体做法是在创建 provider 之前拦截请求手动处理 XML。这个方案比想象中简单核心代码就是拼接一个新的 Capabilities 文本字符串。第三类自定义 provider 配置。如果服务是基于某个成熟平台发布的大部分字段都没问题只是个别字段解析有歧义可以考虑不完全依赖自动解析而是在创建 provider 时手动传入tileMatrixSetID、layer、style、tileMatrixLabels等参数强制指定这些值绕过 Capabilities 中的某些问题。第四类换加载方式。如果异常实在无法解决且服务支持以简单瓦片方式访问可以改用UrlTemplateImageryProvider。但要注意这个方案需要你手动拼 URL 模板、填写minimumLevel、maximumLevel、rectangle这些参数。参数写错了就不是报错而是白板所以我对这个方案的态度是救急可以长期不推荐。3.4 修复后的验证清单修复完成后不要急着看瓦片能不能出来先做一轮系统性验证控制台是否还有异常输出。Network 面板是否有瓦片请求发出。瓦片请求的 URL 是否规范TileMatrix、TileRow、TileCol等参数是否被正确替换。瓦片请求返回的状态码是否为 200。地图上正常显示影像或矢量瓦片。这一步验证的是“链路完全走通”。很多人修复异常后只看到地图有内容了就觉得万事大吉但其实可能有一段区域是空的或者某些层级没有数据。所以建议在验证时把相机从低层级到高层级各缩放一轮并在地图上拖拽几个不同区域确保所有范围内的瓦片都能正常加载。4. 白板问题排查数据没问题也不显示的几类原因4.1 坐标参考系与 tilingScheme 不匹配异常修完之后地图是否就不再白板答案是不一定。本身 WMTS 服务的坐标参考系和 Cesium 默认的WebMercatorTilingScheme不一致是非常常见的白板原因。这正是 1.1 节说的“两个独立问题叠加”的情形。Cesium 默认的WebMercatorTilingScheme是 EPSG:3857也就是 Web Mercator 投影。如果你的 WMTS 服务是基于 EPSG:4326 或 EPSG:4490 发布的那么 provider 内部自动生成的瓦片坐标体系就和服务端的矩阵集不一致。Cesium 按默认的 3857 方式计算瓦片编号发出去的请求在服务端看来是不合法的自然就返回不到正确内容。解决方案是在创建 provider 时手动指定tilingScheme。比如服务是 EPSG:4326 的就传new Cesium.GeographicTilingScheme()。如果是自定义坐标参考系比如中国常用的 CGCS2000GeographicTilingScheme也基本适用因为 Cesium 内部对于地理坐标系的处理本质是以经纬度为基准的。提示判断服务到底用的什么投影直接看 Capabilities 文档里每个 TileMatrixSet 的CRS节点值。常见值有urn:ogc:def:crs:EPSG::3857、urn:ogc:def:crs:EPSG::4326、urn:ogc:def:crs:EPSG::4490等。这个信息是所有后续配置的基础。4.2 分辨率层级和经纬度范围对不上WMTS 的每个 TileMatrix 定义了一层瓦片每层有独立的分辨率由ScaleDenominator推导和行列数由MatrixWidth、MatrixHeight定义。Cesium 在请求瓦片时会根据当前相机的高度计算需要请求哪个层级的瓦片然后通过 URL 模板里的{TileMatrix}占位符传给服务端。如果你的服务只发布了特定层级范围比如只有 0 到 5 级但 Cesium 端没有限制maximumLevel相机放大到第 8 级时请求就会超出服务范围。服务端要么返回空内容要么返回 404表现为白板。修复方式有两种一种是在 provider 配置里显式传入minimumLevel和maximumLevel另一种是在分析 Capabilities 后把支持的最大最小层级作为配置项传入。这种方式比自动解析更可靠因为有些服务的 Capabilities 里写的层级和实际能加载的层级并不一致。另外如果你的服务只覆盖某个局部区域比如一个城市但 Cesium 初始化时把全世界都渲染了。这种情况下其他区域请求瓦片必然失败看起来也是“半块地图白板”。配置rectangle参数限定图层显示范围是解决这个问题的正确姿势。4.3 URL 模板和维度替换符的坑WMTS 的 URL 模板里有各种占位符Cesium 支持的典型占位符包括{z}、{TileMatrix}、{TileRow}、{TileCol}、{Style}、{TileMatrixSet}等。执行顺序Cesium 先用自己的变量替换这些占位符然后用最终字符串去发请求。这里容易踩的坑是服务方在模板里自定义了维度比如{LAYER}大写、{TILEMATRIXSET}全大写而 Cesium 默认用小写替换。大小写不匹配就会导致请求 URL 里留着没被替换的大写占位符服务端无法识别返回错误。另一个更隐蔽的坑是Capabilities 文档里ResourceURL节点的template属性中用到了{TileMatrix}但 Cesium 实际替换时用的是瓦片层级号比如 10而服务端期望的是具体的矩阵集名称比如EPSG:3857_10。这种不一致就需要在配置里指定tileMatrixLabels将每个层级对应的矩阵标识符显式传入。还需要注意一个细节Cesium 对{TileRow}和{TileCol}的编号规则是左上角为原点而行号向下递增。如果你的服务端用的是 TMS 的编号规则原点在左下角行号向上递增就需要对行号做翻转处理。这个问题同样表现为白板而且特别容易让人摸不着头脑。4.4 CORS 跨域导致的假白板最后这一类的白板不是 Cesium 加载逻辑本身的问题而是浏览器跨域策略把请求拦截了。Cesium 发出去的瓦片请求如果跨域服务端没有返回正确的 CORS 头浏览器会拦截响应代码层面可能看不到明显异常但瓦片就是加载不出来。怎么判断是不是这个问题Network 面板里找到瓦片请求看 Response Headers 里有没有Access-Control-Allow-Origin。没有这个头或者值不正确基本就能确定是 CORS 问题。解决方式也简单在服务端配置里加上跨域头或者在开发阶段先关掉浏览器跨域限制做验证。不过这属于环境问题最终上线还是要推动服务端把跨域头配置好。我一直坚持一个原则所有瓦片服务在生产环境都应该正确配置 CORS这不是可选项是必选项。5. 排查速查表与实战心得5.1 症状-原因-方案对照表把常见场景整理成一张速查表方便大家直接对照现象可能原因排查方向解决方案白板 Input string 异常Capabilities 里数值字段不规范检查 ScaleDenominator / TopLeftCorner / TileWidth修复服务端 XML或客户端预处理白板异常偶发或没有CRS 与 tilingScheme 不匹配查看 Capabilities 里的 CRS 值手动传入 tilingScheme白板请求返回 404URL 模板占位符不匹配核对 Network 面板瓦片 URL设置 tileMatrixLabels 或手动模板白板请求被拦截跨域头缺失查看响应头 CORS服务端配置 Access-Control-Allow-Origin某层级白板其他正常层级范围不匹配查看 Capabilities 的矩阵集范围设置 minimumLevel / maximumLevel局部区域白板rectangle 范围不对查看服务覆盖范围设置 rectangle 参数这张表是我每次排查 WMTS 问题都会过一遍的框架。因为白板和异常的组合太多样了有一个结构化的排查路径能省下大量试错时间。5.2 我踩过的几个印象深刻的坑第一个坑是某省级影像服务把TopLeftCorner写成了带单位的字符串例如 “4200000.0m, 500000.0m”。从标准角度看这是完全错误的但服务方坚持说其他客户端都能正常加载。实际上很多 GIS 桌面客户端在解析 XML 时做了容错处理把单位符号提取后忽略掉而 Cesium 的解析器相对严格直接抛异常。最后我的处理方式是在客户端用预处理方式把单位字符串剥掉再做后续加载。第二个坑是某单位的内网服务用了自签证书导致 GetCapabilities 请求直接失败。这个不是 Cesium 的锅但表现也是白板加异常。排查时差点跑偏到解析问题上后来发现是证书信任问题。这个案例说明遇到异常先排除网络、证书、跨域这类环境因素再深入代码逻辑顺序不能乱。第三个坑记忆比较深。一个三维项目的影像服务Cesium 侧无论如何配置都白板后来抓包发现瓦片请求发出去后服务端始终返回 400。两边排查很久最后发现是服务端的路径里包含了中文字符请求 URL 里的中文没有做 URL 编码服务端不认。把路径改成英文就一切都好了。5.3 可以复用的调试套路基于这些经验我整理了一个固定的调试套路每次接新服务都按这个顺序走先抓包看请求和响应。这一步能区分环境问题、网络问题、服务问题。再按 5.1 的表格逐项排查不要跳步。遇到异常优先看 Capabilities 里数值字段的原始格式而不是急着改代码。代码层面的调试建议在项目里写一个独立的地图调试页把所有 provider 配置参数打印出来方便对照。跟服务方沟通时带上抓包结果和 XML 片段比空口描述现象效率高得多。这个套路用了好几年基本覆盖了 WMTS 加载过程中 90% 以上的问题场景。微信和电话里说不清楚的最后都是靠这个套路定位到根因的。6. 写在最后的一点经验经过这么多项目打磨我对 Cesium 加载 WMTS 的体会是大部分问题都不是 Cesium 本身的 bug而是服务端数据标准化程度不够和端侧配置不匹配之间的矛盾。遇到白板和异常不要总想着绕过把 Capabilities 当成合同一样逐条审把瓦片请求当成快递一样逐个查问题就能精准定位。对接第三方服务时建立一套自己的检查清单和速查表能让你少熬夜、少背锅。希望这篇文章里的排查路径和实际案例能给你的项目带来真正可落地的参考价值。
返回列表