Cesium三维地图开发实战踩坑总结
cesium加载地形服务各种报错问题记录
今天又被cesium的地形服务整得头疼,本来以为就是简单接入个地形服务,结果各种奇奇怪怪的问题都出来了。主要是客户要求要在3D场景里展示真实的地形高度,这样建筑物就能贴合地面显示。刚开始想着挺简单的,结果折腾了一整天。
一开始是用官方默认的Cesium World Terrain,结果发现有些区域的数据质量不好,而且有时候还会出现连接超时。后来改成自己部署的地形服务,结果又遇到了跨域问题,还有证书相关的错误。这里我就把这几天遇到的各种问题和解决方法记录一下,免得下次再踩同样的坑。
跨域问题折腾了大半天
首先遇到的就是跨域问题。之前一直以为cesium的请求都是内部处理的不会有问题,结果发现地形服务的请求还是会被浏览器拦截。错误信息大概是这样的:
Access to fetch at 'https://terrain.example.com/...' from origin 'http://localhost:3000' has been blocked by CORS policy
后来试了下发现需要在服务器端配置CORS头。但是因为地形服务是我们自己部署的TileServer,所以可以修改nginx配置:
// nginx配置
location /terrain/ {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization";
}
不过这样做其实不太安全,生产环境建议指定具体的域名而不是用*。还有一个方法是在cesium初始化的时候配置资源路径:
// 配置cesium
const viewer = new Cesium.Viewer('cesiumContainer', {
terrainProvider: new Cesium.CesiumTerrainProvider({
url: 'https://your-terrain-server.com/terrain',
requestVertexNormals: true,
requestWaterMask: true
}),
// 关键配置,允许跨域请求
imageryProvider: new Cesium.UrlTemplateImageryProvider({
url: 'https://your-tile-server.com/tiles/{z}/{x}/{y}.png',
enablePickFeatures: false
})
});
SSL证书问题让我怀疑人生
接着是SSL证书问题。本地开发环境下用的是HTTP,但是地形服务部署在HTTPS服务器上,结果cesium直接拒绝加载非安全源的数据。Chrome控制台一堆红字,说是Mixed Content的错误。
这里踩了个坑,一开始想着在cesium的配置里加什么allowInsecureRequests之类的参数,结果发现根本没用。后来才意识到这是浏览器的安全策略,只能通过调整服务器或者使用代理来解决。
开发环境下我用了webpack dev server的proxy配置:
// webpack.config.js
module.exports = {
devServer: {
proxy: {
'/terrain': {
target: 'https://terrain-server.com',
changeOrigin: true,
pathRewrite: { '^/terrain': '' },
secure: false // 忽略SSL证书验证
}
}
}
};
这样就可以通过本地的HTTP请求来访问远程的HTTPS地形服务了。生产环境的话就老老实实用HTTPS。
地形数据格式踩坑记
另一个大坑是地形数据格式的问题。原来以为只要是标准的Terrain Tiles格式就都能正常加载,结果发现不同版本的协议支持不一样。有些地形服务用的是较老的version 1.0,而cesium最新版本默认支持的是1.1。
如果版本不匹配的话,就会出现数据解析失败的情况,错误信息看起来还挺迷惑人的:
"Failed to process terrain data: Invalid magic number"
解决方法是在TerrainProvider的配置里指定正确的参数:
const terrainProvider = new Cesium.CesiumTerrainProvider({
url: 'https://terrain-server.com/terrain',
requestVertexNormals: true, // 请求顶点法线
requestWaterMask: true, // 请求水面遮罩
credit: new Cesium.Credit('Terrain Data © OpenTopography'), // 添加数据来源说明
ellipsoid: Cesium.Ellipsoid.WGS84 // 指定椭球体
});
有时候还需要根据地形服务的具体格式来调整这些参数。requestVertexNormals和requestWaterMask会增加请求的数据量,如果地形服务不支持这些扩展的话就要设为false。
性能优化不可忽视
还有一个问题是性能。地形数据的加载对网络和渲染性能要求都很高,特别是用户拖拽视角的时候,如果地形数据加载不及时会出现明显的卡顿。这里我做了一些优化尝试:
- 调整LOD级别,避免加载过多不必要的细节
- 设置合适的地形缓存大小
- 合理配置地形的可见范围
具体配置如下:
const terrainProvider = new Cesium.CesiumTerrainProvider({
url: 'https://jztheme.com/terrain',
requestVertexNormals: false, // 如果不需要光照效果可以关闭
requestWaterMask: false, // 同上
maximumLevel: 15, // 最大层级,限制细节等级
minimumLevel: 3 // 最小层级
});
// 设置viewer的地形相关配置
viewer.terrainProvider = terrainProvider;
// 调整地形渲染参数
viewer.scene.globe.depthTestAgainstTerrain = true;
viewer.scene.globe.enableLighting = false; // 如果不需要光照可以关闭
这里的maximumLevel要根据你提供的地形数据实际情况来设置,如果地形服务只有到12级的数据,就不要设置更高的值,否则会一直请求失败。
错误处理和降级方案
最后说一下错误处理。地形加载失败是很常见的,可能是网络问题也可能是服务暂时不可用,这时候最好有个降级方案,比如切换回平面模式或者使用备用的地形服务。
terrainProvider.readyPromise.then(() => {
console.log('Terrain provider ready');
viewer.terrainProvider = terrainProvider;
}).catch(error => {
console.error('Terrain provider failed:', error);
// 降级到平面模式
viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider();
// 或者尝试备用服务
const backupTerrainProvider = new Cesium.CesiumTerrainProvider({
url: 'https://backup-terrain-service.com'
});
backupTerrainProvider.readyPromise.then(() => {
viewer.terrainProvider = backupTerrainProvider;
}).catch(backupError => {
console.error('Backup terrain provider also failed:', backupError);
// 最终降级方案
viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider();
});
});
这套错误处理逻辑虽然看起来复杂,但是能保证用户体验不至于太差。至少让用户知道当前处于什么状态,而不是直接白屏或者卡死。
以上是我踩坑后的总结,cesium的地形服务配置确实比想象中复杂,各种边界情况都要考虑到。如果你有更好的方案或者遇到过其他问题,欢迎评论区交流。

暂无评论