Cesium加载矢量数据探索——从geojson到矢量切片

矢量数据由于包含确定的坐标信息,通常用于表达准确的空间位置实体,在cesium中,不支持对shp进行加载,而是需要对shp数据进行转换,一般cesium支持的shp格式如下:

  • geojson
  • topojson
  • kml
  • czml

通常来说,对于简单的shp数据,最常规的处理方式是通过geojson格式在cesium中进行加载,而cesium也为加载geojson及后续操作提供了丰富的api。链接

Cesium常规加载

cesium提供的api加载方式

const viewer = new Cesium.Viewer('cesiumContainer');
viewer.dataSources.add(Cesium.GeoJsonDataSource.load('../../SampleData/ne_10m_us_states.topojson', {
  stroke: Cesium.Color.HOTPINK,
  fill: Cesium.Color.PINK,
  strokeWidth: 3,
  markerSymbol: '?'
}));
  • cesium的GeoJsonDataSource类提供了geojson的加载方式,并且可以在加载的同时,给数据提供自定义样式。
  • 但是,在加载geojson数据过大是(上万点),场景会非常卡顿,这是什么原因 呢?

Entity图元

  • 其实,翻看上述加载geojson的实现逻辑,其本质上是通过读取geojson中每个feature的属性和坐标,然后通过一个cesium的一个图元来实例化这个feature(包含坐标、样式、属性等信息),然后依次将图元加载到图层上。
  • 因此,上述加载代码也可以改成:
//读取geojson中的每个feature
for (var i = 0; i < geojson.features.length; i++) {
    var ifeature = boundary.features[i];
    let coordinates = [];
    ifeature.geometry.coordinates[0].forEach(lnglat => {
        coordinates.push(lnglat[0]);
        coordinates.push(lnglat[1]);
    });
    let positions2 = Cesium.Cartesian3.fromDegreesArray(coordinates);
    viewer.entities.add({
        polyline: {
            positions: positions2,
            clampToGround: true,
            width: 5,
            material: new Cesium.PolylineOutlineMaterialProperty({
                color: Cesium.Color.ORANGE,
                outlineWidth: 2,
                outlineColor: Cesium.Color.BLACK
            })
        }
    });
 }
  • 可以看到,cesium根据geojson中的每个feature要素的空间属性信息,创建对应的point、polygon、polyline,然后存放入Entities集合中,在场景中进行渲染。可以说,每一个图元要素,都是创建了一个entity。
    • Cesium中,entity是指一个可视化对象,它可以代表一个物体、一个位置或其他类型的数据。一个entity可以包括多个属性,例如位置、姿态、几何形状、颜色、标签等等。它可以被创建、修改、删除,并且可以在地球上进行实时的交互式浏览。
    • 可以看到,cesium就是靠着封装了一个图元类:Entity,这个类包装类丰富的api,来保证渲染要素并且便捷的操作要素。

image.png

  • 不足点:
    • Entity实体类是Cesium提供的标准的加载实体单元的类,并给用户提供了大量便捷操作的API,但是,正式由于它的便捷性。导致了它在加载大量geojson时,大量entity实体被创建和渲染到图层中,性能随之变得卡顿。

privitive像元

Cesium为开发者提供了丰富的图形绘制和空间数据管理的API,可以分为两类,一类是面向图形开发人员的低层次API,通常被称为Primitive API,另一类是用于驱动数据可视化的高层次API,称为Entity API

  • 和Entity的区别:
    • priviutive偏向底层,图形绘制效率更高,但封装程度较低,不包含实时交互功能。
  • 简单的说,privitive更偏向于webgl,只包含了图形绘制的方法,不包含其它便于用户交互、更改、查询等API,而本质上,Entity是privitive的进一步 包装。
  • priivitive由两个部分组成:
    • Geometry
      • 定义几何形状
    • Appearance
      • 定义着色
  • privitive最大的优化在于,加载大量genjson时,可以不用像entity一样,依次挨个加载,而是将大量privitive实例进行合并,最终一次渲染,而这种方式,是其性能大幅提升的关键。
for (let i = 0; i < features.length; i++) {
              const coordinates = features[i].geometry.coordinates[0];
              const lineArray = coordinates[0].slice(0, 2).concat(coordinates[1].slice(0, 2));
              const polyline = new Cesium.SimplePolylineGeometry({
                positions: Cesium.Cartesian3.fromDegreesArray(lineArray),
                // width: 3.0,
                // vertexFormat: Cesium.PolylineMaterialAppearance.VERTEX_FORMAT,
              });

              instances.push(
                new Cesium.GeometryInstance({
                  geometry: polyline,
                  id: `${name}-${i}`, //记录索引
                  attributes: {
                    color: new Cesium.ColorGeometryInstanceAttribute(
                      76 / 255,
                      57 / 255,
                      38 / 255,
                      1,
                    ),
                  },
                }),
              );
              //改成LineString类型
              features[i].geometry.coordinates = coordinates;

              features[i].geometry.type = 'LineString';
            }
            const primitive = new Cesium.Primitive({
              geometryInstances: instances, //合并
              appearance: new Cesium.PerInstanceColorAppearance({
                flat: true,
                renderState: {
                  lineWidth: Math.min(3.0, self._viewer.scene.maximumAliasedLineWidth),
                },
              }),
            });
  • 可以看到,以上加载geojson的方式要比entity代码量多了很多,但是,其性能却是飞速提升。
  • 不足点:
    • primitive带来的性能提升仅仅是从渲染方面解决,当面对大量geojson数据时,保证cesium端能流畅渲染,然是geojson的大量数据必定在后端向前端的网络传输过程中,造成一定时间的等待,客观上也是属于界面的卡顿。参考1,参考2
    • primitie的高速渲染,带来加载的性能优化的同时,也造成了工作量的增加,而且其附属操作成本也会更高,如点击添加的某条线段,读取属性,使该线段高亮:
      • 这种操作适用entity可以很简单的实现,因为有现成的api。
      • 换成primitive实现方式则需要自己手动去实现:
        • 创建每个primitive时更定一独特索引
        • 点击时,读取索引,进而拿到该primitive
        • 根据该primitive,重现渲染该颜色

如何加载矢量切片

cesium支持矢量切片的加载在其论坛里呼声很高,但是ceisum依然坚持3dtiles的标准。

  • cesium加载3dtiles可以说也是一种切片的加载方式,这种方式将海量的模型数据,以瓦片的形式,高速率传输,理论上,基本可以实现不限数据量。
  • 这种加载方式在二维地图中经常看到,无论是影像数据还是矢量、栅格,通过切片方式加载,根据缩放层级和图层范围展示对应的数据是一种十分重要且流行的方案。
  • 但是,cesium本身没有对该方案做什么扩展,也就是说,依靠cesiuim无法实现矢量切片的加载,那么从头实现这个方法显然不现实。因此,有人开始走“曲线救国”的路线:
    • cesium支持加载不用数据源的图层,如天地图、mapbox、openlayer等。
    • 这些地图均包含加载矢量切片的功能,是否可以参考其中的源码,借助其功能,实现相关操作?
    • 经过测试,确实可以。参考3

openlayer的尝试

  • 参考4
  • 前人分享的一个根据openlayer改写的渲染方式,经过测试,换成自己发布的数据,确实可以实现mbtiles格式的切片数据加载。但是没一会,电脑开始发热,因为后台在不断请求切片数据,即使场景没有变化,也是在不断请求。因此还需要再继续完善,

mapbox的尝试

  • openlayer作为图层的方式似乎更简洁一些,但是性能上,mapbox几乎可以说是更快更强,因此我没有继续在上个前辈分享的openlayer的基础上继续学习和优化(如果后面有时间,可以多花点时间去学习)
  • 这个开源项目的地址:参考5,项目给创建了添加矢量栅格的方法,并且给出了范例,对新手很友好。
  • 但是,由于我水平有限,依然在这里踩了很多坑,因此我写这篇文章的主要目的,是分享一下我的踩坑的经过,一来加深印象,二来,要是有小伙伴在我的经过里知道有好的方案可以教教我。

踩坑:

  1. 引入mapbox-gl,可以看到,项目依赖于mapbox-gl.js文件,在此基础之上,创建了一个矢量加载类,在实际使用时,只需要实例化该类,像使用mapbox一样,传入定义好的style,即可实现加载。但实际上,很多人会报一个错:mapbox.BasicRenderer类不存在
    1. 其实原因是,项目中提供的mapbox-gl并不是真正的mapbox项目中的mapbox-gl.js文件,而是由另一位大神,根据mapbox源码中的渲染方式进行改进,从而可以实现基于mapbox的方式加载矢量切片的图层,该项目最终打包命名为mapbox-gl.js,参考6,恰好和mapbox中的文件重名。因此,有些评论里说遇到这恶鬼报错是因为mapbox-gl版本不对,其实不是一回事,不是一个项目。
    2. 在vite中使用报错,原因是该项目由webpack打包成的cmj规范,而vite恰恰不支持cmj规范,只支持ems规范。
      1. 因此,后面的思路变成:如何让vite支持cms规范和如何将cms规范转换为ems规范的问题。参考7
        1. 利用rollup插件进行重新打包。 参考8
        2. 通过第三方包进行转换。链接
      2. 这些方式都可以实现cmj->ems,但是,最终还是会有一些问题,报错:_requestTransformFn未定义
        1. 原因是this指针的问题,在源码里修改后即可

。至此,引入MVTImageryProvider进行实例化,加载到cesium中即可实现矢量瓦片的加载。

onMounted(() => {
  const cesiumViewer = new Cesium.Viewer('cesiumContainer', {
    infoBox: false
  })
  const provider = new MVTImageryProvider({
    style: sx,//mapbox风格自定义样式
   
  });
  provider.readyPromise.then(()=>{
  cesiumViewer.imageryLayers.addImageryProvider(provider)

  })
}

你可能感兴趣的:(cesium,javascript,cesium,vite)