cesium学习笔记(问题记录)——(三)

发布时间:2023年12月18日

一、根据点跟角度计算另一点坐标(三维球体)

export const getAnotherPoint = (lon: number, lat: number, angle: number, distance: number) => {
    // WGS84坐标系
    var a = 6378137;	// 赤道半径
    var b = 6356752.3142;	// 短半径
    var f = 1 / 298.257223563;	// 扁率

    var alpha1 = angle * (Math.PI / 180)
    var sinAlpha1 = Math.sin(alpha1);
    var cosAlpha1 = Math.cos(alpha1);
    var tanU1 = (1 - f) * Math.tan(lat * (Math.PI / 180));
    var cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1)), sinU1 = tanU1 * cosU1;
    var sigma1 = Math.atan2(tanU1, cosAlpha1);
    var sinAlpha = cosU1 * sinAlpha1;
    var cosSqAlpha = 1 - sinAlpha * sinAlpha;
    var uSq = cosSqAlpha * (a * a - b * b) / (b * b);
    var A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
    var B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
    var sigma = distance / (b * A), sigmaP = 2 * Math.PI;
    let sinSigma = 0
    let cosSigma = 0
    let cos2SigmaM = 0
    while (Math.abs(sigma - sigmaP) > 1e-12) {
        cos2SigmaM = Math.cos(2 * sigma1 + sigma);
        sinSigma = Math.sin(sigma);
        cosSigma = Math.cos(sigma);
        var deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) -
            B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
        sigmaP = sigma;
        sigma = distance / (b * A) + deltaSigma;
    }

    var tmp = sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1;
    var lat2 = Math.atan2(sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1,
        (1 - f) * Math.sqrt(sinAlpha * sinAlpha + tmp * tmp));
    var lambda = Math.atan2(sinSigma * sinAlpha1, cosU1 * cosSigma - sinU1 * sinSigma * cosAlpha1);
    var C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
    var L = lambda - (1 - C) * f * sinAlpha *
        (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
    return { lon: Number(lon) + L * (180 / Math.PI), lat: lat2 * (180 / Math.PI) };
}

二、渐变材质

export const getMaterial = () => {
    let appearance = new Cesium.MaterialAppearance({
        vertexShaderSource: `
            attribute vec3 position3DHigh;  
            attribute vec3 position3DLow;
            attribute float batchId;
            varying vec4 v_positionEC;
            attribute vec4 color;
            varying vec4 v_color;
            void main(){
                v_color = color;
                vec4 p = czm_computePosition(); // 获取模型相对于视点位置
                vec4 eyePosition = czm_modelViewRelativeToEye * p; // 由模型坐标 得到视点坐标
                v_positionEC =  czm_inverseModelView * eyePosition;   // 视点在 模型坐标系中的位置
                gl_Position = czm_modelViewProjectionRelativeToEye * p;  // 视点坐标转为屏幕坐标
            }`,
        fragmentShaderSource: `      
            varying vec4 v_positionEC;
            varying vec3 v_normalEC;
            varying vec4 v_color;
            void main() {
                float l = sqrt(pow(v_positionEC.x,2.0) + pow(v_positionEC.y,2.0) + pow(v_positionEC.z,2.0)); // 距离模型坐标系原点的距离
                float cy3 = fract((abs(l-27.0))/40.0); 
                // 修改渐变方向 float cy3 = 1- fract((abs(l-27.0))/40.0); 
                gl_FragColor = vec4(v_color.rgb,cy3);
            }
            `,
    })
    return appearance
}

三、根据两点坐标获取偏航角和俯仰角

function getHeadingPitch(fromPosition, toPosition) {
    let finalPosition = new Cesium.Cartesian3();
    let matrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(fromPosition);
    Cesium.Matrix4.inverse(matrix4, matrix4);
    Cesium.Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition);
    Cesium.Cartesian3.normalize(finalPosition, finalPosition);
    const resultHead = Cesium.Math.toDegrees(Math.atan2(finalPosition.x, finalPosition.y))
    const resultPitch = Cesium.Math.toDegrees(Math.asin(finalPosition.z))
    return {resultHead ,resultPitch };
}

四、添加实体

(一)点

//entities.add(entity) 
viewer.entities.add({
  // fromDegrees(经度,纬度,高度,椭球,结果)从以度为单位的经度和纬度值返回Cartesian3位置
     position: Cesium.Cartesian3.fromDegrees(108, 34, 10),
     point: {
       // 点的大小(像素)
       pixelSize: 5,
       // 点位颜色,fromCssColorString 可以直接使用CSS颜色
       color: Cesium.Color.fromCssColorString('#ee0000'),
       // 边框颜色
       outlineColor: Cesium.Color.fromCssColorString('#fff'),
       // 边框宽度(像素)
       outlineWidth: 2,
       // 显示在距相机的距离处的属性,多少区间内是可以显示的
       distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 1500),
       // 是否显示
       show: true
     }
   });

(二)线

viewer.entities.add({
     polyline: {
       // fromDegrees返回给定的经度和纬度值数组(以度为单位),该数组由Cartesian3位置组成。
       // Cesium.Cartesian3.fromDegreesArray([经度1, 纬度1, 经度2, 纬度2,])
       // Cesium.Cartesian3.fromDegreesArrayHeights([经度1, 纬度1, 高度1, 经度2, 纬度2, 高度2])
       positions: Cesium.Cartesian3.fromDegreesArray([
         120.9677706, 30.7985748,
         110.20, 34.55
       ]),
       // 宽度
       width: 2,
       // 线的颜色
       material: Cesium.Color.WHITE,
       // 线的顺序,仅当`clampToGround`为true并且支持地形上的折线时才有效。
       zIndex: 10,
       // 显示在距相机的距离处的属性,多少区间内是可以显示的
       distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 1500),
       // 是否显示
       show: true
     }
   });

(三)面

viewer.entities.add({
      polygon: {
        // 获取指定属性(positions,holes(图形内需要挖空的区域))
        hierarchy: {
          positions: Cesium.Cartesian3.fromDegreesArray([
            120.9677706, 30.7985748,
            110.20, 34.55,
            120.20, 50.55
          ]),
          holes: [{
            positions: Cesium.Cartesian3.fromDegreesArray([
              119, 32,
              115, 34,
              119, 40
            ])
          }]
        },
        // 边框
        outline: true,
        // 边框颜色
        outlineColor: Cesium.Color.WHITE,
        // 边框尺寸
        outlineWidth: 2,
        // 填充的颜色,withAlpha透明度
        material: Cesium.Color.GREEN.withAlpha(0.5),
        // 是否被提供的材质填充
        fill: true,
        // 恒定高度
        height: 5000,
        // 显示在距相机的距离处的属性,多少区间内是可以显示的
        distanceDisplayCondition: new Cesium.DistanceDisplayCondition(1000, 10000000),
        // 是否显示
        show: true,
        // 顺序,仅当`clampToGround`为true并且支持地形上的折线时才有效。
        zIndex: 10
      }
    });

(四)文字

viewer.entities.add({
      position: Cesium.Cartesian3.fromDegrees(120, 30, 5),
      // 点
      point: {
        color: Cesium.Color.RED, // 点位颜色
        pixelSize: 10 // 像素点大小
      },
      // 文字
      label: {
        // 文本。支持显式换行符“ \ n”
        text: '测试名称',
        // 字体样式,以CSS语法指定字体
        font: '14pt Source Han Sans CN',
        // 字体颜色
        fillColor: Cesium.Color.BLACK,
        // 背景颜色
        backgroundColor: Cesium.Color.AQUA,
        // 是否显示背景颜色
        showBackground: true,
        // 字体边框
        outline: true,
        // 字体边框颜色
        outlineColor: Cesium.Color.WHITE,
        // 字体边框尺寸
        outlineWidth: 10,
        // 应用于图像的统一比例。比例大于会1.0放大标签,而比例小于会1.0缩小标签。
        scale: 1.0,
        // 设置样式:FILL:填写标签的文本,但不要勾勒轮廓;OUTLINE:概述标签的文本,但不要填写;FILL_AND_OUTLINE:填写并概述标签文本。
        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
        // 相对于坐标的水平位置
        verticalOrigin: Cesium.VerticalOrigin.CENTER,
        // 相对于坐标的水平位置
        horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
        // 该属性指定标签在屏幕空间中距此标签原点的像素偏移量
        pixelOffset: new Cesium.Cartesian2(10, 0),
        // 显示在距相机的距离处的属性,多少区间内是可以显示的
        distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 1500),
        // 是否显示
        show: true
      }
    });

(五)广告牌(图片)

viewer.entities.add({
  position: Cesium.Cartesian3.fromDegrees(110.20, 34.55, 2.61),
  billboard: {
    // 图像地址,URI或Canvas的属性
    image: '/location.png',
    // 设置颜色和透明度
    color: Cesium.Color.WHITE.withAlpha(0.8),
    // 高度(以像素为单位)
    height: 50,
    // 宽度(以像素为单位)
    width: 50,
    // 逆时针旋转
    rotation: 20,
    // 大小是否以米为单位
    sizeInMeters: false,
    // 相对于坐标的垂直位置
    verticalOrigin: Cesium.VerticalOrigin.CENTER,
    // 相对于坐标的水平位置
    horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
    // 该属性指定标签在屏幕空间中距此标签原点的像素偏移量
    pixelOffset: new Cesium.Cartesian2(10, 0),
    // 应用于图像的统一比例。比例大于会1.0放大标签,而比例小于会1.0缩小标签。
    scale: 1.0,
    // 显示在距相机的距离处的属性,多少区间内是可以显示的
    distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 1500),
    // 是否显示
    show: true
  }
});

(六)模型

viewer.entities.add({
      // 设置方向
      orientation: orientation,
      position: Cesium.Cartesian3.fromDegrees(120, 30, 10000),
      model: {
        // 引入模型
        uri: '/SampleData/models/CesiumAir/Cesium_Air.glb',
        // 模型的近似最小像素大小,而不考虑缩放。这可以用来确保即使观看者缩小也可以看到模型。如果为0.0,则不强制使用最小大小
        minimumPixelSize: 1280,
        // 模型的颜色(与模型的渲染颜色混合的属性)
        color: Cesium.Color.WHITE.withAlpha(1),
        // 模型的最大比例大小
        maximumScale: 20000,
        // 设置模型轮廓(边框)颜色
        silhouetteColor: Cesium.Color.BLACK,
        // 设置模型轮廓(边框)大小
        silhouetteSize: 2,
        // 是否执行模型动画
        runAnimations: true,
        // 应用于图像的统一比例。比例大于会1.0放大标签,而比例小于会1.0缩小标签。
        scale: 1.0,
        // 显示在距相机的距离处的属性,多少区间内是可以显示的
        distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 1500),
        // 是否显示
        show: true
      }
    });

五、添加primitive

(一)点

const pointsDataSource = viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection())
// 创建点
for (const index in points) {
     pointsDataSource.add({
          pixelSize: 10,
          color: Cesium.Color.fromCssColorString('#fff'),
          position: Cesium.Cartesian3.fromDegrees(item[0], item[1], item[2]),
        });
}
// 销毁点
pointsDataSource?.removeAll();

(二)线

const newArr1 = []; let newArr2 = [];
// eslint-disable-next-line array-callback-return
points.map((item) => {
  newArr1.push(item[0]);
  newArr1.push(item[1]);
  newArr1.push(item[2]);
});
newArr2 = newArr1;
/// map原由fromDegreesArray 所需的数据属于[lon, lat, height, lon, lat, height, ...]
/// 所以需要map改造

/// 创建线
const lineDataSource = viewer.scene.primitives.add(new Cesium.PolylineCollection());
lineDataSource.add({
  width: 2,
  positions: this.Cesium.Cartesian3.fromDegreesArrayHeights(newArr2),
  material: this.Cesium.Material.fromType('Color', {
    color: this.Cesium.Color.fromCssColorString('#EADDCA'),
  }),
});

/// 销毁线
lineDataSource?.removeAll();

(三)广告牌

billboards.add({
    position: Cesium.Cartesian3.fromDegrees(item[0], item[1], item[2]),
    image: '图片地址', 
    // image:pinBuilder.fromColor(Cesium.Color.fromRandom({ alpha: 1.0 }), 20).toDataURL()
    width: 18,
    height: 18,
    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  })

加载大量贴地广告牌:BillboardCollection

const collection = new Cesium.BillboardCollection({ scene: viewer.scene })

1、heightReference属性

collection.add({
	position: position,
	image: pinBuilder.fromColor(Cesium.Color.fromRandom({ alpha: 1.0 }), 20).toDataURL(),
	verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
	horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
	heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
})

2、sampleTerrainMostDetailed方法

// 1、生成大量随机点
const points = randomPoint(500, { bbox: [100, 25, 102, 27] })
// 2、Cartographic数组
const positions = []
for (let point of points.features) {
  const coordinates = point.geometry.coordinates
  const position = Cesium.Cartographic.fromDegrees(coordinates[0], coordinates[1])
  positions.push(position)
}
// 3、高程采样
const pinBuilder = new Cesium.PinBuilder();
const collection = new Cesium.BillboardCollection()
const terrainProvider = viewer.terrainProvider
const promise = Cesium.sampleTerrainMostDetailed(terrainProvider, positions);
Promise.resolve(promise).then(function (updatedPositions) {
  for (let position of updatedPositions) {
    collection.add({
      position: Cesium.Cartesian3.fromRadians(position.longitude, position.latitude, position.height),
      image: pinBuilder.fromColor(Cesium.Color.fromRandom({ alpha: 1.0 }), 20).toDataURL(),
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
      horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
    })
  }
  viewer.scene.primitives.add(collection)
});

(四)标注

labels.add({
  position: Cesium.Cartesian3.fromDegrees(item[0], item[1], item[2]),
  text: `文字标注`,
  font: '10px sans-serif',
});

(五)几何图形

以折线和多边形为例:

let rectangleInstanceArr = [];
    //定义折线几何
    let polyline = new Cesium.PolylineGeometry({
      positions: Cesium.Cartesian3.fromDegreesArray([
        119.7,
        30.2,
        119.8,
        30.2,
        119.85,
        30.3
      ]),
      width: 10.0,
      vertexFormat:Cesium.PolylineColorAppearance.VERTEX_FORMAT
    });
    //定义多边形几何
    // let polygon = new Cesium.PolygonGeometry({
    //   polygonHierarchy: new Cesium.PolygonHierarchy(
    //     Cesium.Cartesian3.fromDegreesArray([
    //       119.7,
    //       30.2,
    //       119.8,
    //       30.2,
    //       119.85,
    //       30.3,
    //     ])
    //   ),
    //   vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
    // });
    var rectangleInstance = new Cesium.GeometryInstance({
      geometry: polyline,
      attributes: {
        color: Cesium.ColorGeometryInstanceAttribute.fromColor(
          Cesium.Color.RED
        ),
      },
    });
    rectangleInstanceArr.push(rectangleInstance);
    this.viewer.scene.primitives.add(
      new Cesium.Primitive({
        geometryInstances: rectangleInstanceArr,
        //多边形外观
        // appearance: new Cesium.PerInstanceColorAppearance({
        //   translucent: true,
        //   closed: false,
        // }),
        //折线外观
        appearance:new Cesium.PolylineColorAppearance({
            translucent:false
        }),
        asynchronous: false,
      })
    );

六、定位

(一)定位到点

1、viewer

let entity = null;
/**
 * 视图定位方法,定位到点
 * @param lon 经度
 * @param lat 纬度
 * @param alt 范围(相机距离中心点的位置为5000)
 */
function viewerFlyToLonLat(lon, lat, alt) {
	entity && viewer.entities.remove(entity);
	entity = new Cesium.Entity({
	   id: 'flyTojwd',
	   position: Cesium.Cartesian3.fromDegrees(lon, lat),
	   point: {
	       pixelSize: 10,
	       color: Cesium.Color.WHITE.withAlpha(0.9),
	       outlineColor: Cesium.Color.WHITE.withAlpha(0.9),
	       outlineWidth: 1
	   }
	});
	viewer.entities.add(entity);
	viewer.flyTo(entity, {
	   offset: {
	       heading: Cesium.Math.toRadians(0.0), //默认方向为正北,正角度为向东旋转,即水平选装,也叫偏航角
	       pitch: Cesium.Math.toRadians(-50), // 俯仰角
	       range: alt
	   }
	});
}

2、camera

/**
* 相机定位方法,定位到点
* @param lon 经度
* @param lat 纬度
* @param alt 范围(相机距离中心点的位置为5000)
*/
function cameraFlyToLonLat(lon, lat, alt) {
   viewer.camera.flyTo({
       destination: Cesium.Cartesian3.fromDegrees(lon, lat, alt),
       orientation: {
           heading: Cesium.Math.toRadians(0.0),
           pitch: Cesium.Math.toRadians(-25.0),
           roll: 0.0
       }
   });
}

(二)定位到范围

1、viewer

 /**
 * 视图定位方法,定位到范围
 * @param rect 范围数组(最西、最南、最东、最北)
 */
function viewerFlyToRange(rect) {
    if (locationRectEntity)
        viewer.entities.remove(locationRectEntity);
    	locationRectEntity = viewer.entities.add({
	        name: 'locationRectangle',
	        id: 'locationRectangle',
	        rectangle: {
	            coordinates: Cesium.Rectangle.fromDegrees(rect[0], rect[1], rect[2], rect[3]),
	            material: Cesium.Color.GREEN.withAlpha(1.0),
	            height: 10.0,
	            outline: false
	        }
	    });
	    let flyPromise = viewer.flyTo(locationRectEntity, {
	        duration: 5,
	        offset: new Cesium.HeadingPitchRange(0.0, Cesium.Math.toRadians(-20.0))
	    });
}

2、camera

/**
* 相机定位方法,定位到范围
* @param rect 范围数组(最西、最南、最东、最北)
*/
function cameraFlyToRange(rect) {
  viewer.camera.flyTo({
      destination: Cesium.Rectangle.fromDegrees(rect[0], rect[1], rect[2], rect[3]),
      duration: 5,
      orientation: {
          heading: Cesium.Math.toRadians(0.0),
          pitch: Cesium.Math.toRadians(-25.0),
          roll: 0.0
      }
  });
}

七、可视域分析

(一)ShadowMap

阴影贴图:new Cesium.ShadowMap (options)
在这里插入图片描述
Cesium用它来实现阴影效果。
ShadowMap的一个比较明显的缺点是阴影边缘锯齿化很严重,而PCF则能有效地克服Shadow Map阴影边缘的锯齿。

(二)Frustum

new Cesium.FrustumOutlineGeometry (options):绘制视锥的轮廓线
在这里插入图片描述

(三)Camera

在这里插入图片描述
给定视锥的起点、向前的方向和向上的方向,把视锥的方向确定下来,设置了视锥的视野角度、宽高比、近平面距离和远平面距离,把视锥的形状确定下来了。

// 创建一个沿负z轴向下的,位于原点的,视野为60度的,宽高比为1:1的相机。
var camera = new Cesium.Camera(scene);
camera.position = new Cesium.Cartesian3();
camera.direction = Cesium.Cartesian3.negate(Cesium.Cartesian3.UNIT_Z, new Cesium.Cartesian3());
camera.up = Cesium.Cartesian3.clone(Cesium.Cartesian3.UNIT_Y);
camera.frustum.fov = Cesium.Math.PI_OVER_THREE;
camera.frustum.near = 1.0;
camera.frustum.far = 2.0;

(四)视网

用EllipsoidGraphics绘制的实体对象。
在这里插入图片描述

(五)具体实现

// ViewShed.js

/**
 * 可视域分析。
 *
 * @author Helsing
 * @date 2020/08/28
 * @alias ViewShedStage
 * @class
 * @param {Cesium.Viewer} viewer Cesium三维视窗。
 * @param {Object} options 选项。
 * @param {Cesium.Cartesian3} options.viewPosition 观测点位置。
 * @param {Cesium.Cartesian3} options.viewPositionEnd 最远观测点位置(如果设置了观测距离,这个属性可以不设置)。
 * @param {Number} options.viewDistance 观测距离(单位`米`,默认值100)。
 * @param {Number} options.viewHeading 航向角(单位`度`,默认值0)。
 * @param {Number} options.viewPitch 俯仰角(单位`度`,默认值0)。
 * @param {Number} options.horizontalViewAngle 可视域水平夹角(单位`度`,默认值90)。
 * @param {Number} options.verticalViewAngle 可视域垂直夹角(单位`度`,默认值60)。
 * @param {Cesium.Color} options.visibleAreaColor 可视区域颜色(默认值`绿色`)。
 * @param {Cesium.Color} options.invisibleAreaColor 不可视区域颜色(默认值`红色`)。
 * @param {Boolean} options.enabled 阴影贴图是否可用。
 * @param {Boolean} options.softShadows 是否启用柔和阴影。
 * @param {Boolean} options.size 每个阴影贴图的大小。
 */
class ViewShedStage {

    constructor(viewer, options) {
        this.viewer = viewer;
        this.viewPosition = options.viewPosition;
        this.viewPositionEnd = options.viewPositionEnd;
        this.viewDistance = this.viewPositionEnd ? Cesium.Cartesian3.distance(this.viewPosition, this.viewPositionEnd) : (options.viewDistance || 100.0);
        this.viewHeading = this.viewPositionEnd ? getHeading(this.viewPosition, this.viewPositionEnd) : (options.viewHeading || 0.0);
        this.viewPitch = this.viewPositionEnd ? getPitch(this.viewPosition, this.viewPositionEnd) : (options.viewPitch || 0.0);
        this.horizontalViewAngle = options.horizontalViewAngle || 90.0;
        this.verticalViewAngle = options.verticalViewAngle || 60.0;
        this.visibleAreaColor = options.visibleAreaColor || Cesium.Color.GREEN;
        this.invisibleAreaColor = options.invisibleAreaColor || Cesium.Color.RED;
        this.enabled = (typeof options.enabled === "boolean") ? options.enabled : true;
        this.softShadows = (typeof options.softShadows === "boolean") ? options.softShadows : true;
        this.size = options.size || 2048;

        this.update();
    }

    add() {
        this.createLightCamera();
        this.createShadowMap();
        this.createPostStage();
        this.drawFrustumOutine();
        this.drawSketch();
    }

    update() {
        this.clear();
        this.add();
    }

    clear() {
        if (this.sketch) {
            this.viewer.entities.removeById(this.sketch.id);
            this.sketch = null;
        }
        if (this.frustumOutline) {
            this.frustumOutline.destroy();
            this.frustumOutline = null;
        }
        if (this.postStage) {
            this.viewer.scene.postProcessStages.remove(this.postStage);
            this.postStage = null;
        }
    }
}

export default ViewShed;

创建相机:

createLightCamera() {
    this.lightCamera = new Cesium.Camera(this.viewer.scene);
    this.lightCamera.position = this.viewPosition;
    // if (this.viewPositionEnd) {
    //     let direction = Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(this.viewPositionEnd, this.viewPosition, new Cesium.Cartesian3()), new Cesium.Cartesian3());
    //     this.lightCamera.direction = direction; // direction是相机面向的方向
    // }
    this.lightCamera.frustum.near = this.viewDistance * 0.001;
    this.lightCamera.frustum.far = this.viewDistance;
    const hr = Cesium.Math.toRadians(this.horizontalViewAngle);
    const vr = Cesium.Math.toRadians(this.verticalViewAngle);
    const aspectRatio =
        (this.viewDistance * Math.tan(hr / 2) * 2) /
        (this.viewDistance * Math.tan(vr / 2) * 2);
    this.lightCamera.frustum.aspectRatio = aspectRatio;
    if (hr > vr) {
        this.lightCamera.frustum.fov = hr;
    } else {
        this.lightCamera.frustum.fov = vr;
    }
    this.lightCamera.setView({
        destination: this.viewPosition,
        orientation: {
            heading: Cesium.Math.toRadians(this.viewHeading || 0),
            pitch: Cesium.Math.toRadians(this.viewPitch || 0),
            roll: 0
        }
    });
}

创建阴影贴图:

createShadowMap() {
    this.shadowMap = new Cesium.ShadowMap({
        context: (this.viewer.scene).context,
        lightCamera: this.lightCamera,
        enabled: this.enabled,
        isPointLight: true,
        pointLightRadius: this.viewDistance,
        cascadesEnabled: false,
        size: this.size,
        softShadows: this.softShadows,
        normalOffset: false,
        fromLightSource: false
    });
    this.viewer.scene.shadowMap = this.shadowMap;
}

创建PostStage:

createPostStage() {
    const fs = glsl
    const postStage = new Cesium.PostProcessStage({
        fragmentShader: fs,
        uniforms: {
            shadowMap_textureCube: () => {
                this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
                return Reflect.get(this.shadowMap, "_shadowMapTexture");
            },
            shadowMap_matrix: () => {
                this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
                return Reflect.get(this.shadowMap, "_shadowMapMatrix");
            },
            shadowMap_lightPositionEC: () => {
                this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
                return Reflect.get(this.shadowMap, "_lightPositionEC");
            },
            shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness: () => {
                this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
                const bias = this.shadowMap._pointBias;
                return Cesium.Cartesian4.fromElements(
                    bias.normalOffsetScale,
                    this.shadowMap._distance,
                    this.shadowMap.maximumDistance,
                    0.0,
                    new Cesium.Cartesian4()
                );
            },
            shadowMap_texelSizeDepthBiasAndNormalShadingSmooth: () => {
                this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
                const bias = this.shadowMap._pointBias;
                const scratchTexelStepSize = new Cesium.Cartesian2();
                const texelStepSize = scratchTexelStepSize;
                texelStepSize.x = 1.0 / this.shadowMap._textureSize.x;
                texelStepSize.y = 1.0 / this.shadowMap._textureSize.y;

                return Cesium.Cartesian4.fromElements(
                    texelStepSize.x,
                    texelStepSize.y,
                    bias.depthBias,
                    bias.normalShadingSmooth,
                    new Cesium.Cartesian4()
                );
            }camera_projection_matrix: this.lightCamera.frustum.projectionMatrix,
            camera_view_matrix: this.lightCamera.viewMatrix,
            helsing_viewDistance: () => {
                return this.viewDistance;
            },
            helsing_visibleAreaColor: this.visibleAreaColor,
            helsing_invisibleAreaColor: this.invisibleAreaColor,
        }
    });
    this.postStage = this.viewer.scene.postProcessStages.add(postStage);
}

创建视锥线:

drawFrustumOutline() {
    const scratchRight = new Cesium.Cartesian3();
    const scratchRotation = new Cesium.Matrix3();
    const scratchOrientation = new Cesium.Quaternion();
    const position = this.lightCamera.positionWC;
    const direction = this.lightCamera.directionWC;
    const up = this.lightCamera.upWC;
    let right = this.lightCamera.rightWC;
    right = Cesium.Cartesian3.negate(right, scratchRight);
    let rotation = scratchRotation;
    Cesium.Matrix3.setColumn(rotation, 0, right, rotation);
    Cesium.Matrix3.setColumn(rotation, 1, up, rotation);
    Cesium.Matrix3.setColumn(rotation, 2, direction, rotation);
    let orientation = Cesium.Quaternion.fromRotationMatrix(rotation, scratchOrientation);

    let instance = new Cesium.GeometryInstance({
        geometry: new Cesium.FrustumOutlineGeometry({
            frustum: this.lightCamera.frustum,
            origin: this.viewPosition,
            orientation: orientation
        }),
        id: Math.random().toString(36).substr(2),
        attributes: {
            color: Cesium.ColorGeometryInstanceAttribute.fromColor(
                Cesium.Color.YELLOWGREEN//new Cesium.Color(0.0, 1.0, 0.0, 1.0)
            ),
            show: new Cesium.ShowGeometryInstanceAttribute(true)
        }
    });

    this.frustumOutline = this.viewer.scene.primitives.add(
        new Cesium.Primitive({
            geometryInstances: [instance],
            appearance: new Cesium.PerInstanceColorAppearance({
                flat: true,
                translucent: false
            })
        })
    );
}

创建视网:

drawSketch() {
    this.sketch = this.viewer.entities.add({
        name: 'sketch',
        position: this.viewPosition,
        orientation: Cesium.Transforms.headingPitchRollQuaternion(
            this.viewPosition,
            Cesium.HeadingPitchRoll.fromDegrees(this.viewHeading - this.horizontalViewAngle, this.viewPitch, 0.0)
        ),
        ellipsoid: {
            radii: new Cesium.Cartesian3(
                this.viewDistance,
                this.viewDistance,
                this.viewDistance
            ),
            // innerRadii: new Cesium.Cartesian3(2.0, 2.0, 2.0),
            minimumClock: Cesium.Math.toRadians(-this.horizontalViewAngle / 2),
            maximumClock: Cesium.Math.toRadians(this.horizontalViewAngle / 2),
            minimumCone: Cesium.Math.toRadians(this.verticalViewAngle + 7.75),
            maximumCone: Cesium.Math.toRadians(180 - this.verticalViewAngle - 7.75),
            fill: false,
            outline: true,
            subdivisions: 256,
            stackPartitions: 64,
            slicePartitions: 64,
            outlineColor: Cesium.Color.YELLOWGREEN
        }
    });
}

获取偏航角和俯仰角:

function getHeading(fromPosition, toPosition) {
    let finalPosition = new Cesium.Cartesian3();
    let matrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(fromPosition);
    Cesium.Matrix4.inverse(matrix4, matrix4);
    Cesium.Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition);
    Cesium.Cartesian3.normalize(finalPosition, finalPosition);
    return Cesium.Math.toDegrees(Math.atan2(finalPosition.x, finalPosition.y));
}

function getPitch(fromPosition, toPosition) {
    let finalPosition = new Cesium.Cartesian3();
    let matrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(fromPosition);
    Cesium.Matrix4.inverse(matrix4, matrix4);
    Cesium.Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition);
    Cesium.Cartesian3.normalize(finalPosition, finalPosition);
    return Cesium.Math.toDegrees(Math.asin(finalPosition.z));
}

自定义材质:

export default `
 #define USE_CUBE_MAP_SHADOW true
 uniform sampler2D colorTexture;
 uniform sampler2D depthTexture;
 varying vec2 v_textureCoordinates;
 uniform mat4 camera_projection_matrix;
 uniform mat4 camera_view_matrix;
 uniform samplerCube shadowMap_textureCube;
 uniform mat4 shadowMap_matrix;
 uniform vec4 shadowMap_lightPositionEC;
 uniform vec4 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness;
 uniform vec4 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth;
 uniform float helsing_viewDistance;
 uniform vec4 helsing_visibleAreaColor;
 uniform vec4 helsing_invisibleAreaColor;

 struct zx_shadowParameters
 {
     vec3 texCoords;
     float depthBias;
     float depth;
     float nDotL;
     vec2 texelStepSize;
     float normalShadingSmooth;
     float darkness;
 };

 float czm_shadowVisibility(samplerCube shadowMap, zx_shadowParameters shadowParameters)
 {
     float depthBias = shadowParameters.depthBias;
     float depth = shadowParameters.depth;
     float nDotL = shadowParameters.nDotL;
     float normalShadingSmooth = shadowParameters.normalShadingSmooth;
     float darkness = shadowParameters.darkness;
     vec3 uvw = shadowParameters.texCoords;
     depth -= depthBias;
     float visibility = czm_shadowDepthCompare(shadowMap, uvw, depth);
     return czm_private_shadowVisibility(visibility, nDotL, normalShadingSmooth, darkness);
 }

 vec4 getPositionEC(){
     return czm_windowToEyeCoordinates(gl_FragCoord);
 }

 vec3 getNormalEC(){
     return vec3(1.);
 }

 vec4 toEye(in vec2 uv,in float depth){
     vec2 xy=vec2((uv.x*2.-1.),(uv.y*2.-1.));
     vec4 posInCamera=czm_inverseProjection*vec4(xy,depth,1.);
     posInCamera=posInCamera/posInCamera.w;
     return posInCamera;
 }

 vec3 pointProjectOnPlane(in vec3 planeNormal,in vec3 planeOrigin,in vec3 point){
     vec3 v01=point-planeOrigin;
     float d=dot(planeNormal,v01);
     return(point-planeNormal*d);
 }

 float getDepth(in vec4 depth){
     float z_window=czm_unpackDepth(depth);
     z_window=czm_reverseLogDepth(z_window);
     float n_range=czm_depthRange.near;
     float f_range=czm_depthRange.far;
     return(2.*z_window-n_range-f_range)/(f_range-n_range);
 }

 float shadow(in vec4 positionEC){
     vec3 normalEC=getNormalEC();
     zx_shadowParameters shadowParameters;
     shadowParameters.texelStepSize=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.xy;
     shadowParameters.depthBias=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.z;
     shadowParameters.normalShadingSmooth=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.w;
     shadowParameters.darkness=shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.w;
     vec3 directionEC=positionEC.xyz-shadowMap_lightPositionEC.xyz;
     float distance=length(directionEC);
     directionEC=normalize(directionEC);
     float radius=shadowMap_lightPositionEC.w;
     if(distance>radius)
     {
         return 2.0;
     }
     vec3 directionWC=czm_inverseViewRotation*directionEC;
     shadowParameters.depth=distance/radius-0.0003;
     shadowParameters.nDotL=clamp(dot(normalEC,-directionEC),0.,1.);
     shadowParameters.texCoords=directionWC;
     float visibility=czm_shadowVisibility(shadowMap_textureCube,shadowParameters);
     return visibility;
 }

 bool visible(in vec4 result)
 {
     result.x/=result.w;
     result.y/=result.w;
     result.z/=result.w;
     return result.x>=-1.&&result.x<=1.
     &&result.y>=-1.&&result.y<=1.
     &&result.z>=-1.&&result.z<=1.;
 }

 void main(){
     // 釉色 = 结构二维(颜色纹理, 纹理坐标)
     gl_FragColor = texture2D(colorTexture, v_textureCoordinates);
     // 深度 = 获取深度(结构二维(深度纹理, 纹理坐标))
     float depth = getDepth(texture2D(depthTexture, v_textureCoordinates));
     // 视角 = (纹理坐标, 深度)
     vec4 viewPos = toEye(v_textureCoordinates, depth);
     // 世界坐标
     vec4 wordPos = czm_inverseView * viewPos;
     // 虚拟相机中坐标
     vec4 vcPos = camera_view_matrix * wordPos;
     float near = .001 * helsing_viewDistance;
     float dis = length(vcPos.xyz);
     if(dis > near && dis < helsing_viewDistance){
         // 透视投影
         vec4 posInEye = camera_projection_matrix * vcPos;
         // 可视区颜色
         // vec4 helsing_visibleAreaColor=vec4(0.,1.,0.,.5);
         // vec4 helsing_invisibleAreaColor=vec4(1.,0.,0.,.5);
         if(visible(posInEye)){
             float vis = shadow(viewPos);
             if(vis > 0.3){
                 gl_FragColor = mix(gl_FragColor,helsing_visibleAreaColor,.5);
             } else{
                 gl_FragColor = mix(gl_FragColor,helsing_invisibleAreaColor,.5);
             }
         }
     }
 }`;

在使用的时候需开启深度检测

八、自定义弹窗

(一)思路

添加一个鼠标左键点击事件,当鼠标点击时,利用Vue.extend()动态添加一个dom元素,将DOM元素渲染到cesium容器中,并利用cesium中提供的 viewer.scene.postRender 实时更新坐标位置。

(二)实现方法

1、创建地图容器

viewer = new Cesium.Viewer('cesiumContainer',{
	// terrainProvider: Cesium.createWorldTerrain(),
	// animation: false, // 控制场景动画的播放速度控件
	// baseLayerPicker: true, // 底图切换控件
	// baselLayerPicker:false,// 将图层选择的控件关掉,才能添加其他影像数据
	// // fullscreenButton: false, // 全屏控件
	// geocoder: false, // 地理位置查询定位控件
	// homeButton: true, // 默认相机位置控件
	// timeline: false, // 时间滚动条控件
	// infoBox: false, //是否显示信息框
	// sceneModePicker: false, //是否显示3D/2D选择器
	// selectionIndicator: false, // 点击点绿色弹出 是否显示选取指示器组件
	// sceneMode: Cesium.SceneMode.SCENE3D, //设定3维地图的默认场景模式:Cesium.SceneMode.SCENE2D、Cesium.SceneMode.SCENE3D、Cesium.SceneMode.MORPHING
	// navigationHelpButton: false, // 默认的相机控制提示控件
	// scene3DOnly: true, // 每个几何实例仅以3D渲染以节省GPU内存
	// navigationInstructionsInitiallyVisible: false,
	// showRenderLoopErrors: false, //是否显示渲染错误
	// orderIndependentTranslucency:false,//设置背景透明
});

2、billboard 添加目标点位

点位的数据格式:

poin :  [{
	id:'12321321' ,
	name: "北京西路测试点",
	type: "固定枪机",
	state: "在线",
	position: { 
		x: 116.4568,
		y: 39.8926
	} ,
	text:'X'
}],

将添加点的方法封装到类中:

/**
 * @param {Viewer} viewer
 * 
*/
export default class DragEntity{
    constructor(val){
        this.viewer = val.viewer,
    }
    addEntity(value){
        //数据格式{id:543595234324_432423,position:[122.8,39.9],text:"L"}
        let pinBuilder = new Cesium.PinBuilder();
        let poin = this.viewer.entities.add({
            id:value.id,
            name: value.name,
            position: Cesium.Cartesian3.fromDegrees(value.position.x, value.position.y),
            billboard: {
              image: pinBuilder.fromText(value.text,Cesium.Color.ROYALBLUE, 48).toDataURL(),
              verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
            },
            monitoItems:{
                    data:value
                },
        });
        return poin
    }
    
}

3、添加左键点击事件

leftDownAction(){
  let viewer = this.$store.state.viewer
    this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    let _this = this
    let id
    _this.handler.setInputAction(function (movement) {
        let pick = viewer.scene.pick(movement.position); 
        if (Cesium.defined(pick) && (pick.id.id) ) {
            // _this.leftDownFlag = true;
            id= pick.id.id;
             console.log(id)
        }
        
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    
},

4、弹窗

export default class Bubble {
    constructor(val){
        this.viewer = val.viewer
        this.div=document.createElement("div");
        // this.addDynamicLabel({id:1,position:val.position,title:"cl弹窗"});
    }
    addDynamicLabel(data){
        let div = this.div
        div.id = data.id;
        // div.style.display="inline"
        div.style.position = "absolute";
        div.style.width = "300px";
        div.style.height = "30px";
        let HTMLTable = `
            <div style="background:#00ffef66;height:200px;border:"1px soild #08f8a7">${data.text}
                <div style="">
            </div>
        `;
        div.innerHTML = HTMLTable;
        this.viewer.cesiumWidget.container.appendChild(div);
        let gisPosition = data.position._value
        this.viewer.scene.postRender.addEventListener(() => {
            const canvasHeight = this.viewer.scene.canvas.height;
            const windowPosition = new Cesium.Cartesian2();
            Cesium.SceneTransforms.wgs84ToWindowCoordinates(
                this.viewer.scene,
                gisPosition,
                windowPosition
            );
            div.style.bottom = canvasHeight - windowPosition.y +220 + "px";
            const elWidth = div.offsetWidth;
            div.style.left = windowPosition.x - elWidth / 2 + "px";
        }, this);
    }
    clearDiv(id){
        if(this.div){
            var parent = this.div.parentElement;
            parent.removeChild(this.div);
            // this.div.removeNode(true);
            this.viewer.scene.postRender.removeEventListener(this.addDynamicLabel,this)
        }
        
    }
}

修改点击事件:

import Bubble from './bubble/index.js'
leftDownAction(){
 	let viewer = this.$store.state.viewer
 	let bubble = new  Bubble({
		viewer:viewer 
	})
   this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
   let _this = this
   let id
   _this.handler.setInputAction(function (movement) {
       let pick = viewer.scene.pick(movement.position); 
       if (Cesium.defined(pick) && (pick.id.id) ) {
           // _this.leftDownFlag = true;
           id= pick.id.id;
           let entiy = this.poinEntity[id];
           bubble.addDynamicLabel(entiy);
       }else{
			bubble.clearDiv();
		}
   }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
},

九、cesium 广告牌优化

entityCollection 集合加上集群聚合功能,数据量临界点在 3w~4w 左右,就会出现界面卡顿。fps 低于 20 并且波动很大,延迟保持在 100ms 左右。
当数据量大于 10w+时,基本上 fps 处于 0-5,延迟大于 200ms,加载数据时延迟直接飙升几千都可能出现,同时(entityCollection 的)数据量过大直接导致浏览器崩溃无法加载。

抛开后台接口数据传递处理的优化,只针对前端 cesium 界面的所有优化方法中,billboardCollection 广告牌集合,也适合界面显示大量的 pointPrimitiveCollection(点集合)、labelCollection(label 集合)造成的界面卡顿。

优化方法:primitiveCollection与 primitiveCluster 结合使用

在不需要聚合集群的情况下,只使用 primitiveCollection就能够完美的解决广告牌 10w+造成的界面卡顿崩溃等问题。不需要聚合功能时,就不添加 primitiveCluster 来处理优化。因为聚合会监听摄像机的改变事件时刻改变聚合数量状态,反而会出现卡顿情况。

(一)primitiveCollection

广告牌集合添加代码(如下),其他的集合如 point、label,大同小异:

const billboardCollection = viewer.scene.primitives.add(
  new Cesium.BillboardCollection()
);
billboardCollection.add({
  position: Cesium.Cartesian3.fromDegrees(114.49, 41.23, 0),
  width: 38,
  height: 38,
  image: "xxxxx"
});

此时往 billboardCollection 中添加的 billboard 就会直接呈现在界面上,能够轻松应对 10w+的数据量。对比EntityCollection的添加方式效果很明显:

// 之前的添加方式
const entityCollection = new Cesium.EntityCollection();

const billboard = new Cesium.BillboardGraphics({
  width: 38,
  height: 38,
  image: "xxxxx"
});
const entity = new Cesium.Entity({
  position: Cesium.Cartesian3.fromDegrees(114.49, 41.23, 0),
  billboard: billboard,
  s1: "xxx",
  s2: "xxx",
  s3: "xxx",
  s4: "xxx",
  s5: "xxx"
});
entityCollection.add(entity);

(二)PrimitiveCluster

primitiveCollection 的聚合功能官方并没有提供,在官方文档中只提供了 EntityCluster 方法,对 entityCollection 集合进行聚合操作。通过 EntityCluster 方法聚合时需要配合 datasource 对象使用,因为在原生的 datasource 对象自身有 clustering 属性。
由于我们直接使用的 Primitive 方式将 billboard 添加到地图中,就跳过了 datasource 的步骤。因此我们需要自己来定义一个 PrimitiveCluster 方法来创建一个 cluster 对象,针对原语集合进行聚合,结合其他博主文档提供的方法,PrimitiveCluster.js 具体实现方法总结如下:

一)往 cesium 的包文件或者依赖文件中添加 PrimitiveCluster 方法
1.添加的路径:
(1)npm 包中----- node_modules\cesium\Source\DataSources\PrimitiveCluster.js
(2)引入外部文件方式 ---- Source\DataSources\PrimitiveCluster.js
2.复制同目录下 EntityCluster.js 内容到 PrimitiveCluster.js 中
3.文件内全局修改名称,EntityCluster -> PrimitiveCluster、 entityCluster -> primitiveCluster
4.屏蔽 getScreenSpacePositions 方法中的代码块

(EntityCluster 中 item.id 指向的就是 entity 实体对象,在 primitiveCollection 中 item.id 为 undefined 会包错)

/* var canClusterLabels =
  primitiveCluster._clusterLabels && defined(item._labelCollection);
var canClusterBillboards =
  primitiveCluster._clusterBillboards && defined(item.id._billboard);
var canClusterPoints =
  primitiveCluster._clusterPoints && defined(item.id._point);
if (canClusterLabels && (canClusterPoints || canClusterBillboards)) {
  continue;
} */
第 4 步时 如果在添加广告牌时需要为广告牌添加唯一的标识 id(如下添加方式),则可以不用屏蔽源代码,添加的 id 能够规避此处报错
billboardCollection.add({
  id: "xxx",
  position: Cesium.Cartesian3.fromDegrees(114.49, 41.23, 0),
  width: 38,
  height: 38,
  image: "xxxxx"
});
5.在 PrimitiveCluster.js 的上级目录(node_modules\cesium\Source\Cesium.js)中找到入口文件 cesium.js,导入 PrimitiveCluster 方法
export { default as PrimitiveCluster } from "./DataSources/PrimitiveCluster.js";
可以直接通过 new Cesium.PrimitiveCluster()的方式来调用
二)PrimitiveCluster 方法来实现聚合
1.往 scene.primitives 中添加原语集合 primitives
2.创建一个空 billboardCollection 广告牌集合
3.通过 PrimitiveCluster 方法创建一个 cluster 实例对象 primitiveCluster
4.将 primitiveCluster 添加到集合 primitives 中
5.配置 primitiveCluster 对象的基本参数(不配置有默认参数)
6.将空 billboardCollection 广告牌集合赋予primitiveCluster._billboardCollection,手动添加聚合内容
primitiveCluster._labelCollection;
primitiveCluster._pointCollection;
7.调用_initialize 方法初始化 cluster 实例的事件监听
8.之后与 datasource 聚合方式的.then 方法一致,将 dataSource.clustering.clusterEvent.addEventListener 换成 primitiveCluster.clusterEvent.addEventListener
const primitives = viewer.scene.primitives.add(
  new Cesium.PrimitiveCollection()
);
const billboardCollection = new Cesium.BillboardCollection();

const primitiveCluster = new Cesium.PrimitiveCluster();
primitives.add(primitiveCluster);
primitiveCluster.enabled = true; //开启聚合功能
primitiveCluster.pixelRange = 15; //范围
primitiveCluster.minimumClusterSize = 2; //最小聚合数量
primitiveCluster._billboardCollection = billboardCollection;
primitiveCluster._initialize(viewer.scene);

primitiveCluster.clusterEvent.addEventListener(function(
  clusteredEntities,
  cluster
) {
  // ... 处理聚合显示广告牌代码块与dataSource处理方式一致
});

按照上面的方式完成聚合后,往 billboardCollection 集合中添加 billboard 广告牌就会在页面呈现出来并且聚合显示。但是数据量 10w+的情况下,在处理摄像机视角改变的监听事件时会出现卡顿问题。

(三)优化 PrimitiveCluster 卡顿问题

在 PrimitiveCluster.js 的_initialize 方法中,可以看到原方法使用 createDeclutterCallback 方法创建了一个回调方法,并将这个回调方法添加到了 scene.camera.changed 监听中。因此只要 scene.camera 视角改变,就会执行聚合的处理逻辑方法返回两个参数 clusteredEntities 与 cluster。
primitiveCluster.clusterEvent.addEventListener(function(
  clusteredEntities,
  cluster
) {
  // ... 处理聚合显示广告牌代码块与dataSource处理方式一致
});
只需要在_initialize 方法加一个防抖的定时器,让它事件处理频率降低就能达到优化的效果。同时暴露 delay 时间参数可以在实例化后进行配置改变
//1.PrimitiveCluster构造函数中添加_delay参数
this._delay = defaultValue(options.delay, 800)

//2.在PrimitiveCluster.prototype拦截器Object.defineProperties方法中添加_delay的访问以及设置方法
delay: {
  get: function () {
    return this._delay;
  },
  set: function (value) {
    this._delay = value;
  },
},

// 3._initialize方法改造
PrimitiveCluster.prototype._initialize = function(scene) {
  this._scene = scene;
  var cluster = createDeclutterCallback(this);
  this._cluster = cluster;
  var _t = null;
  const _self = this;
  this._removeEventListener = scene.camera.changed.addEventListener(function(amount) {
    if (_t) {
      clearTimeout(_t);
      _t = null;
    }
    _t = setTimeout(() => {
      cluster(amount);
    }, _self._delay);
  });
};

(四)功能代码记录

import * as Cesium from "cesium/Cesium";
import defaultValue from "./core/defaultValue";

/**
 * @_v 引入外部创建的Viewer实例(new Cesium.Viewer(...))
 * @myPrimitives 原语集合,可以包含页面显示的pointPrimitiveCollection、billboardCollection、labelCollection、primitiveCollection、primitiveCluster
 * @myPrimitiveCluster 自定义原语集群
 * @myBillboardCollection 广告牌集合(站点显示的内容数据)
 *
 * @desc 使用primitiveCollection原语集合与primitiveCluster原语集群,处理地图界面显示广告牌billboard数量 > 10w 级时,界面卡顿,浏览器崩溃等问题
 */
class CommomSiteTookit {
  static _v = null;
  myPrimitives = null;
  myPrimitiveCluster = null;
  myBillboardCollection = null;

  constructor() {}

  /**
   * @desc 使用commomSiteTookit实例前,必须先初始化该实例的_v对象
   */
  init(viewer) {
    this._v = viewer;
  }

  /**
   * @param [options] 具有以下属性的对象
   * @param [options.delay=800] 防抖处理定时器的time
   * @param [options.enabled=true] 是否启用集群
   * @param [options.pixelRange=15] 用于扩展屏幕空间包围框的像素范围
   * @param [options.minimumClusterSize=2] 可集群的屏幕空间对象的最小数量
   *
   * @desc 处理原语集合,并实现聚合集群功能方法
   * @return billboardCollection集合,可直接往集合里添加广告牌billboard,呈现在页面上
   */
  load(options = {}) {
    let billboardCollection = new Cesium.BillboardCollection();

    if (Cesium.defined(this.myPrimitives)) {
      this._v.scene.primitives.remove(this.myPrimitives);
    }
    this.myPrimitives = this._v.scene.primitives.add(
      new Cesium.PrimitiveCollection()
    );

    const primitiveCluster = new Cesium.PrimitiveCluster();
    this.myPrimitives.add(primitiveCluster);
    primitiveCluster.delay = defaultValue(options.delay, 800);
    primitiveCluster.enabled = defaultValue(options.enabled, true);
    primitiveCluster.pixelRange = defaultValue(options.pixelRange, 15);
    primitiveCluster.minimumClusterSize = defaultValue(
      options.minimumClusterSize,
      2
    );
    primitiveCluster._billboardCollection = billboardCollection;
    primitiveCluster._initialize(this._v.scene);

    let removeListener;
    let pinBuilder = new Cesium.PinBuilder();
    /* 定义广告牌 fromText(显示文字,颜色,大小) */
    let pin50 = pinBuilder.fromText("50+", Cesium.Color.RED, 40).toDataURL();
    let pin40 = pinBuilder.fromText("40+", Cesium.Color.ORANGE, 40).toDataURL();
    let pin30 = pinBuilder.fromText("30+", Cesium.Color.YELLOW, 40).toDataURL();
    let pin20 = pinBuilder.fromText("20+", Cesium.Color.GREEN, 40).toDataURL();
    let pin10 = pinBuilder.fromText("10+", Cesium.Color.BLUE, 40).toDataURL();
    /* 数量小于十个的聚合广告牌 */
    let singleDigitPins = new Array(8);
    for (let i = 0; i < singleDigitPins.length; ++i) {
      singleDigitPins[i] = pinBuilder
        .fromText("" + (i + 2), Cesium.Color.VIOLET, 40)
        .toDataURL();
    }

    const _ = this;
    function customStyle() {
      if (Cesium.defined(removeListener)) {
        removeListener();
        removeListener = undefined;
      } else {
        removeListener = primitiveCluster.clusterEvent.addEventListener(
          function(clusteredEntities, cluster) {
            cluster.label.show = false;
            cluster.billboard.show = true;
            cluster.billboard.id = cluster.label.id;
            cluster.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
            /* 根据站点(参数)的数量给予对应的广告牌  */
            if (clusteredEntities.length >= 50) {
              cluster.billboard.image = pin50;
            } else if (clusteredEntities.length >= 40) {
              cluster.billboard.image = pin40;
            } else if (clusteredEntities.length >= 30) {
              cluster.billboard.image = pin30;
            } else if (clusteredEntities.length >= 20) {
              cluster.billboard.image = pin20;
            } else if (clusteredEntities.length >= 10) {
              cluster.billboard.image = pin10;
            } else {
              cluster.billboard.image =
                singleDigitPins[clusteredEntities.length - 2];
            }
          }
        );
      }
      // force a re-cluster with the new styling
      let pixelRange = primitiveCluster.pixelRange;
      primitiveCluster.pixelRange = 0;
      primitiveCluster.pixelRange = pixelRange;
      _.myPrimitiveCluster = primitiveCluster;
    }
    this.myBillboardCollection = billboardCollection;
    // start with custom style
    customStyle();
    return billboardCollection;
  }

  /**
   * @params enable bool值控制开启或关闭集群
   * @desc 控制集群生效与否
   */
  enableCluster(enable) {
    if (Cesium.defined(this.myPrimitiveCluster)) {
      this.myPrimitiveCluster.enabled = enable;
    }
  }

  /**
   * @params id 站点ID
   * @return 返回可操作的广告牌[siteBillboard.image = 'xxxx']
   * @desc 根据id在集合中获取指定站点广告牌
   */
  getSiteBillboardById(id) {
    if (!Cesium.defined(this.myBillboardCollection)) return undefined;
    const _b = this.myBillboardCollection;
    const l = _b.length;
    let siteBillboard = undefined;
    for (let i = 0; i < l; i++) {
      if (id == _b.get(i).id) {
        siteBillboard = _b.get(i);
        break;
      }
    }
    return siteBillboard;
  }

  /**
   * @desc 删除所有站点广告牌
   */
  removeAll() {
    if (Cesium.defined(this.myPrimitives)) {
      this._v.scene.primitives.remove(this.myPrimitives);
    }
  }

  /**
   * @params show bool值 控制显示或隐藏
   * @desc 隐藏或显示所有站点广告牌
   */
  showStatus(show = true) {
    this.myPrimitives.show = show;
  }

  /**
   * @desc 根据id删除指定站点广告牌
   */
  remove(id) {
    const billboard = this.getSiteBillboardById(id);
    billboard && this.myBillboardCollection.remove(billboard);
  }

  /**
   * @desc 销毁(目前退出页面时直接viewer销毁)
   */
  destroy() {
    this.myPrimitives = null;
    this.myPrimitiveCluster = null;
    this.myBillboardCollection = null;
    // this._v.scene.primitives.destroy()
  }
}

export default new CommomSiteTookit();

在执行commomSiteTookit.init(viewer)后,加载数据主要的操作在 load 方法中,load 返回的 billboardCollection,可以动态的添加 billboard 数据,直接呈现在界面,代码如下:

const list = ['10w+数据']
const l = list.length
const data = commomSiteTookit.load({
  enabled: true,
  delay: 1200,
  pixelRange: 20
});
for (let i = 0; i < l; i++) {
  data.add({
    image: `xxxx`,
    scaleByDistance: new Cesium.NearFarScalar(1.5e2, 1, 1.5e7, 0.2),
    width: 38, // default: undefined
    height: 38, // default: undefined
    position: Cesium.Cartesian3.fromDegrees(
      list[i].longitude,
      list[i].latitude,
      0
    ),
    id: list[i].id + ""
  });
}

十、可视域分析

①利用高程判别:视角初始位置和视域目标点之间组成的一条直线,我们暂且称之为当前视角下的“视域线”,对该直线进行空间插值,获得直线上一系列的空间插值点。对于获取到的每个插值点P(x,y,z)通过getHeight()方法来获取当前场景下(x,y)点的地表高程h,对比z与h的高程关系。若z<h,则证明,该插值点位于地表下,可以归属于不可视域点;反之,若z>h,则归属于可视域点。这样就可以获取到视域线在当前插值精度下的可视域情况。
②pick取交:视角初始位置和视域目标点之间组成的一条射线(方向:视角位置→目标点),我们也称之为“视域线”,通过pickFromRay()方法,可以返回当前视域线与场景(地形或模型等)相交情况result。若result存在,则视角初始位置与result.position之间的区域归属于可视域范围,result.position与目标点之间的区域归属于不可视域范围;若result不存在,即无交点出现,全区域归属于可视域范围。

var createViewershed = function () {
    // 开启地形深度监测
    viewer.scene.globe.depthTestAgainstTerrain = true;

    // 设定初始视角位置点
    var viewPoint = Cesium.Cartesian3.fromDegrees(115.77774943, 40.51669238, 1000);
    var viewPointEntity = viewer.entities.add({
        position: viewPoint,
        ellipsoid: {
            radii: new Cesium.Cartesian3(5, 5, 5),
            material: Cesium.Color.YELLOW
        },
    });

    // 视角位置创建坐标轴
    var transform = Cesium.Transforms.eastNorthUpToFixedFrame(viewPoint);
    var modelMatrixPrimitive = viewer.scene.primitives.add(new Cesium.DebugModelMatrixPrimitive({
        modelMatrix: transform,
        length: 10.0
    }));

    // 世界坐标转换为投影坐标
    var webMercatorProjection = new Cesium.WebMercatorProjection(viewer.scene.globe.ellipsoid);
    var viewPointWebMercator = webMercatorProjection.project(Cesium.Cartographic.fromCartesian(viewPoint));

    // 排除碰撞监测的对象
    var objectsToExclude = [viewPointEntity, modelMatrixPrimitive];

    // 目标点集合
    var TargetPoints = [];

    // 视域点和目标点的距离
    var radius = 0;

    // 计算45°和135°之间的目标点
    for (var i = 45; i <= 135; i++) {
        var linePoints = [];//记录一条线上的所有目标点
        var pointsNum = 15;//该方向插值点数
        var lineDis = 200;//每两个插值点之间的距离m1
        for (let index = 0; index < pointsNum; index++) {
            radius = index * lineDis;
            // 度数转弧度
            var radians = Cesium.Math.toRadians(i);
            // 计算目标点
            var toPoint = new Cesium.Cartesian3(viewPointWebMercator.x + radius * Math.cos(radians), viewPointWebMercator.y + radius * Math.sin(radians), 0);
            // 投影坐标转世界坐标
            toPoint = webMercatorProjection.unproject(toPoint);
            // TargetPoints.push(Cesium.Cartographic.toCartesian(toPoint.clone()));
            var m_cartesian3 = Cesium.Cartographic.toCartesian(toPoint.clone());
            var m_ellipsoid = viewer.scene.globe.ellipsoid;
            var m_cartographic = m_ellipsoid.cartesianToCartographic(m_cartesian3);
            var m_height = viewer.scene.globe.getHeight(m_cartographic);
            var m_point = Cesium.Cartesian3.fromDegrees(m_cartographic.longitude / Math.PI * 180, m_cartographic.latitude / Math.PI * 180, m_height);
            linePoints.push({
                data: m_point,
                show: true
            });
        }
        TargetPoints.push({
            id: i,
            points: linePoints
        });
    }
    pickFromRay();

    function pickFromRay() {
        for (var i = 0; i < TargetPoints.length; i++) {
            var cur_LinePoints = TargetPoints[i].points;
            cur_LinePoints.forEach(element => {
                // 计算射线的方向,目标点left 视域点right
                var direction = Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(element.data, viewPoint, new Cesium.Cartesian3()), new Cesium.Cartesian3());
                // 建立射线
                var ray = new Cesium.Ray(viewPoint, direction);
                // var results = viewer.scene.drillPickFromRay(ray, 10, objectsToExclude); // 计算所有的交互点,最大不超过10个
                var result = viewer.scene.pickFromRay(ray, objectsToExclude); // 计算交互点,返回第一个
                var buffer = ReturnDistance(element.data, result.position);
                // var M_color = Cesium.Color.GREEN;
                if (buffer > 10) {
                    // M_color = Cesium.Color.RED;
                    element.show = false;
                }
                //添加当前视域目标点可视化信息
                // viewer.entities.add({
                //     name: "aaaa" + i,
                //     position: element.data,
                //     ellipsoid: {
                //         radii: new Cesium.Cartesian3(30, 30, 30),
                //         material: M_color
                //     },
                // })
            });

        }
        drawViewshedLine(TargetPoints);
    }

    function drawViewshedLine(data) {
        for (let index = 0; index < data.length; index++) {
            const element = data[index].points;
            var startIndex = 0;
            for (let i = 0; i < element.length; i++) {
                var defaultColor = new Cesium.Color(0.1, 1, 0.1, 0.3);
                // console.log("第" + i + '个点的起点是:' + startIndex);
                const m_linestart = element[startIndex];
                var m_lineshow = m_linestart.show;
                const m_lineCurrent = element[i];
                const m_lineEnd = element[i + 1];
                if (m_lineEnd && m_lineCurrent.show != m_lineEnd.show) {
                    if (!m_lineshow) {
                        defaultColor = new Cesium.Color(1, 0.1, 0.1, 0.3);
                    }
                    viewer.entities.add({
                        polyline: {
                            positions: [m_linestart.data, m_lineEnd.data],
                            width: 2,
                            material: defaultColor,
                            clampToGround: true
                        }
                    });
                    startIndex = i + 1;
                }
                else if (!m_lineEnd) {
                    if (!m_lineshow) {
                        defaultColor = new Cesium.Color(1, 0.1, 0.1, 0.3);
                    }
                    viewer.entities.add({
                        polyline: {
                            positions: [m_linestart.data, m_lineCurrent.data],
                            // arcType: Cesium.ArcType.NONE,
                            width: 2,
                            material: defaultColor,
                            // depthFailMaterial: defaultColor,
                            clampToGround: true
                        }
                    });
                }

            }
        }
    }

    //空间两点距离计算函数
    function ReturnDistance(pos0, pos1) {
        var distance = 0;
        var point1cartographic = Cesium.Cartographic.fromCartesian(pos0);
        var point2cartographic = Cesium.Cartographic.fromCartesian(pos1);
        /**根据经纬度计算出距离**/
        var geodesic = new Cesium.EllipsoidGeodesic();
        geodesic.setEndPoints(point1cartographic, point2cartographic);
        var s = geodesic.surfaceDistance;
        return s.toFixed(2);
    }

    // 处理交互点
    function showIntersection(result, destPoint, viewPoint) {
        // 如果是场景模型的交互点,排除交互点是地球表面
        if (Cesium.defined(result) && Cesium.defined(result.object)) {
            drawLine(result.position, viewPoint, Cesium.Color.GREEN); // 可视区域
            drawLine(result.position, destPoint, Cesium.Color.RED); // 不可视区域
        } else {
            drawLine(viewPoint, destPoint, Cesium.Color.GREEN);
        }
    }
}
文章来源:https://blog.csdn.net/CheryW/article/details/133805275
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。