Cesium三维地图开发实战踩坑总结

奥翔 Dev 交互 阅读 1,764
赞 15 收藏
二维码
手机扫码查看
反馈

cesium加载地形服务各种报错问题记录

今天又被cesium的地形服务整得头疼,本来以为就是简单接入个地形服务,结果各种奇奇怪怪的问题都出来了。主要是客户要求要在3D场景里展示真实的地形高度,这样建筑物就能贴合地面显示。刚开始想着挺简单的,结果折腾了一整天。

Cesium三维地图开发实战踩坑总结

一开始是用官方默认的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的地形服务配置确实比想象中复杂,各种边界情况都要考虑到。如果你有更好的方案或者遇到过其他问题,欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论