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

news2024/11/23 11:39:17

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

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);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1320311.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

valgrind定位C++线程/内存等错误

Valgrind 是一套 Linux 下&#xff0c;开放源代码&#xff08;GPL V2&#xff09;的仿真调试工具的集合。 Valgrind 由内核&#xff08;core&#xff09;以及基于内核的其他调试工具组成。内核类似于一个框架&#xff08;framework&#xff09;&#xff0c;它模拟了一个 CPU 环…

【漏洞复现】CVE-2023-47261 Dokmee ECM信息泄露致远程命令执行

漏洞描述 Dokmee ECM是一款国外企业内容管理 (ECM) 软件。每个公司的办公室每个角落都存放着文档、记录和档案。Dokmee 一系列解决方案可以帮助您高效地组织、保护和管理这些文件。支持的文件:PDF、TIFF、Word、Excel、Auto-CAD 绘图、电子邮件等。Dokmee 可以帮助您立即实现…

模型部署之模型转换

一、模型转换的意义 模型部署是为了模型能在不同框架间流转。 在实际应用时&#xff0c;模型转换几户都用于工业部署&#xff0c;负责模型从训练框架到部署侧推理框架的连接&#xff0c;这是因为随着深度学习随着深度学习应用和技术的演进&#xff0c;训练框架和推理框架的职…

在linux上基于shell自动部署Java项目

一&#xff0c;安装git yum list git 列出git安装包 yum install git 在线安装git 使用 git -varsion 查看是否安装成功 安装成功 二&#xff0c; Git克隆代码 git clone 远程仓库地址 三&#xff0c;创建shell脚本 touch shell.sh shell脚本 #!/bin/sh echo echo 自动…

1130 - Host “WIN-CA4FHERGO9J‘ is not allowed to connect to this MySQL server

1、知识小课堂 1.1 Mysql MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB公司开发&#xff0c;属于Oracle旗下产品。它是最流行的关系型数据库管理系统之一&#xff0c;在WEB应用方面&#xff0c;MySQL是最好的RDBMS (Relational Database Management System&am…

详解 Jeecg-boot 框架如何配置 elasticsearch

目录 一、下载安装 Elasticsearch 1、 地址&#xff1a;https://www.elastic.co/cn/downloads/elasticsearch 2、下载完成后&#xff0c;解压缩&#xff0c;进入config目录更改配置文件 3、 修改配置完成后&#xff0c;前往bin目录启动el 4、访问&#xff1a;localhost:92…

Java对象结构

Java 对象(Object 实例)结构包括三部分:对象头、对象体、对齐字节。 Object的三个部分 对象头包括三个字段&#xff0c;第一个字段叫做 Mark Word(标记字)&#xff0c;用于存储自身运行时的数据 例如 GC 标志位、哈希码、锁状态等信息。 第二个字段叫做 Class Pointer(类对象…

排序算法——归并排序

void print_arr(int arr[], int n) {for (int i 0;i < n;i){printf("%d", arr[i]);}putchar("\n"); }//合并&#xff08;归并排序最主要的部分&#xff09; void merge(int arr[], int tempArr[],int left,int mid,int right) {//标记左半区第一个未排…

模型部署概述

一、前言 一般来说&#xff0c;学术界负责各种 SOTA(State of the Art) 模型的训练和结构探索&#xff0c;而工业界负责将这些 SOTA 模型应用落地&#xff0c;赋能百业。本文将要讲述的是&#xff0c;在 CV 场景中&#xff0c;如何实现模型的快速落地&#xff0c;赋能到产业应…

【PWN】学习笔记(三)【返回导向编程】(下)

目录 课程回顾ret2libc![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/ebe1a9a9e54f4319946621dbe89c5774.png)做题 ret2libc2ret2libc3 课程 课程链接&#xff1a;https://www.bilibili.com/video/BV1854y1y7Ro/?vd_source7b06bd7a9dd90c45c5c9c44d12e7b4e6 课程…

Redis Set类型

集合类型也是保存多个字符串类型的元素的&#xff0c;但和列表类型不同的是&#xff0c;集合中 1&#xff09;元素之间是无序的 2&#xff09;元素不允许重复 一个集合中最多可以存储2的32次方个元素。Redis 除了支持集合内的增删查改操作&#xff0c;同时还支持多个集合取交…

利用python进行数据分析 第十四章 数据分析案例

本书正文的最后一章&#xff0c;我们来看一些真实世界的数据集。对于每个数据集&#xff0c;我们会用之前介绍的方 法&#xff0c;从原始数据中ᨀ 取有意义的内容。展示的方法适用于其它数据集&#xff0c;也包括你的。本章包含了一 些各种各样的案例数据集&#xff0c;可以用…

hypervisor display显卡节点card0生成过程

ditsi 配置 lagvm/LINUX/android/vendor/qcom/proprietary/devicetree/qcom direwolf-g9ph.dts #include "direwolf-vm-la.dtsi" direwolf-vm-la.dtsi #include "display/quin-vm-display-la.dtsi" quin-vm-display-la.dtsi //对应/sys/class/drm/card…

软件测试面试八股文(超详细整理)

请你说一说测试用例的边界 参考回答&#xff1a; 边界值分析法就是对输入或输出的边界值进行测试的一种黑盒测试方法。通常边界值分析法是作为对等价类划分法的补充&#xff0c;这种情况下&#xff0c;其测试用例来自等价类的边界。 常见的边界值 1)对16-bit 的整数而言 32…

Python纯净式下载与安装

1. 下载 Download Python | Python.org 建议下老版本些的&#xff0c;毕竟求稳。 点击需要的版本&#xff0c;然后滑倒最下面&#xff0c;可以看到不同系统对应的下载选项&#xff1a; 2. 安装 如果下载慢的话&#xff0c;可以复制链接到迅雷下载&#xff0c;下载完成后&…

Docker部署MinIO对象存储服务器结合内网穿透实现远程访问

文章目录 前言1. Docker 部署MinIO2. 本地访问MinIO3. Linux安装Cpolar4. 配置MinIO公网地址5. 远程访问MinIO管理界面6. 固定MinIO公网地址 前言 MinIO是一个开源的对象存储服务器&#xff0c;可以在各种环境中运行&#xff0c;例如本地、Docker容器、Kubernetes集群等。它兼…

人工智能革命:共同探索AIGC时代的未来

一、引言 随着大数据和强大的计算能力的兴起&#xff0c;人工智能技术&#xff08;AI&#xff09;正在快速发展&#xff0c;并为各个领域带来革命性的变化。人工智能与智能计算技术&#xff08;AIGC&#xff09;的融合不仅为企业、科研机构和普通用户提供了巨大的机遇&#xff…

【算法题】 TLV解析 Ⅱ (js)

从第三个字节开始因此 const msg "0F04ABABABAB"; const msg1 "0F04ABABABAB10001FF"; function solution(msg, tags) {const tagObj {};for (let i 0; i 3 < msg.length; ) {const tag parseInt(msg.slice(i, i 2), 16);const len parseInt(m…

漏洞复现--SysAid On-premise远程代码执行(CVE-2023-47246)

免责声明&#xff1a; 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直…

Unity3D对TXT文件的操作

系列文章目录 Unity工具 文章目录 系列文章目录前言一、读取txt文档1-1、TextAsset类读取1-2、代码实现1-2、打印结果 二、使用File类读取2-1.使用ReadAllText读取代码如下&#xff1a;2-2、结果如下2-3、使用ReadAllLines读取代码如下&#xff1a;2-4、读取结果 三、文件流读…