cesium 可视域分析

本文介绍如何使用Cesium进行可视域分析,包括可视域的绘制、使用viewshed类、将结果保存至数据库及回显功能。Cesium的可视域分析在军事、城市规划等领域具有重要意义,通过3D模型提供更真实的视觉效果,帮助优化设计决策。
摘要由CSDN通过智能技术生成

前言

Cesium是一个强大的开源JavaScript库,它被广泛用于在Web浏览器中创建3D地理空间应用程序。Cesium提供了进行可视域分析的功能,这可以帮助用户确定从给定位置可以看到哪些区域。可视域分析考虑了地形、建筑物以及其他自然或人造物体对视线的遮挡。这项技术在军事和城市规划等领域有着重要应用,如军事作战中的掩体定位和城市规划中的最佳视觉观赏点选择。由于3D模型能提供更真实的视觉效果,因此在3D模型上进行可视域分析通常比在2D图像上进行更具优势,可以更好地帮助用户理解和优化设计

一、可视域的绘制

import ViewShed from './viewshed.js'
import PlotDrawTip from "@/utils/PlotDrawTip"
export default class Draw {
  constructor(viewer, handler) {
    this.viewer = viewer;
    this.handler = handler
    this.startPosition = undefined
    this.i = 0
    this.drawViewshedEntity = null
  }

  //激活
  activate() {
    this.deactivate();
    this.registerEvents(); //注册鼠标事件
    this.viewer.enableCursorStyle = true;
    this.viewer._element.style.cursor = 'crosshair';
    this.viewer.enableCursorStyle = true;
    this.viewer._element.style.cursor = 'crosshair';
    this.plotDrawTip = new PlotDrawTip(this.viewer);
    this.plotDrawTip.setContent(['左键点击开始绘制'])
  }

  //禁用
  deactivate() {
    this.i = 0
    this.drawEntity = undefined;
    this.endPosition = undefined
    this.startPosition = undefined
    this.viewer._element.style.cursor = 'default';
    this.viewer.enableCursorStyle = true;
    this.unRegisterEvents();
    if (!this.plotDrawTip) return;
    this.plotDrawTip.remove();
    this.plotDrawTip = undefined;
  }

  //注册鼠标事件
  registerEvents() {
    this.leftClickEvent();
    this.mouseMoveEvent();
  }

  leftClickEvent() {
    //单击鼠标左键画点
    this.handler.setInputAction(e => {
      this.i++
      //第一次点击
      if (this.i == 1) {
        let startPosition = this.viewer.scene.pickPosition(e.position);
        if (startPosition && Cesium.defined(startPosition)) {
          this.startPosition = startPosition
          this.plotDrawTip.setContent(['左键点击结束点,完成绘制'])
          this.drawViewshedEntity = new ViewShed(this.viewer, {
            viewPosition: this.startPosition,
            viewPositionEnd: this.startPosition,
          })
        }
      }
      //第二次点击
      if (this.i == 2) {
        let endPosition = this.viewer.scene.pickPosition(e.position);
        if (!endPosition && Cesium.defined(endPosition)) {
          this.drawViewshedEntity.updatePosition(this.endPosition)
          this.drawViewshedEntity.update()
        }
        this.deactivate()
      }

    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  }

  mouseMoveEvent() {
    this.handler.setInputAction(e => {
      this.endPosition = this.viewer.scene.pickPosition(e.endPosition)
      if (!this.endPosition) return
      this.plotDrawTip && this.plotDrawTip.updatePosition(this.endPosition);
      if (this.i == 1) {
        if (this.drawViewshedEntity) {
          this.drawViewshedEntity.updatePosition(this.endPosition)
          this.drawViewshedEntity.update()
        }
      }
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  }

  //解除鼠标事件
  unRegisterEvents() {
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  }
}

二、可视域的viewshed类

import glsl from './glsl';

/**
 * @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 ViewShed {
	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 ? this.getHeading(this.viewPosition, this.viewPositionEnd) : (options.viewHeading || 0.0);
		this.viewPitch = this.viewPositionEnd ? this.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.isSketch = true //视锥线,是否显示
	}

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

	update() {
		this.clear();
		this.add();
		if(!this.isSketch){
			this.clearSketch()
		}
	}

	/**
	 * @method 更新终点坐标,从而实时更新绘制的实体的方向和半径
	 *
	 */
	updatePosition(viewPositionEnd) {
		this.viewPositionEnd = viewPositionEnd
		this.viewDistance = Cesium.Cartesian3.distance(this.viewPosition, this.viewPositionEnd) //观测距离
		this.viewHeading = this.getHeading(this.viewPosition, this.viewPositionEnd)
		this.viewPitch = this.getPitch(this.viewPosition, this.viewPositionEnd)
	}

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

	clearSketch(){
		if (this.sketch) {
			this.viewer.entities.remove(this.sketch);
			this.sketch = null;
		}
	}

	/**
	 * @method 创建相机
	 */
	createLightCamera() {
		this.lightCamera = new Cesium.Camera(this.viewer.scene);
		this.lightCamera.position = this.viewPosition;
		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
			}
		});
	}

	/**
	 * @method 创建阴影贴图
	 */
	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;
	}

	/**
	 * @method 创建PostStage
	 * 导入的glsl是做片元着色的,具体怎么实现的我也不太明白
	 */
	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);
	}

	/**
	 * @method 创建视锥线
	 */
	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
				),
				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
				})
			})
		);
	}
	/**
	 * @method 创建视网
	 * 在实时绘制椭球实体时,其实不是一直创建entity,而是改变实体的方向(orientation)和改变椭球的半径(radii)
	 */
	drawSketch() {
		this.sketch = this.viewer.entities.add({
			name: 'sketch',
			position: this.viewPosition,
			orientation: new Cesium.CallbackProperty(() => Cesium.Transforms.headingPitchRollQuaternion(   //实体的方向
				this.viewPosition,
				Cesium.HeadingPitchRoll.fromDegrees(this.viewHeading - 90.0, this.viewPitch, 0.0))),
			ellipsoid: {                                                                 //椭球的半径
				radii: new Cesium.CallbackProperty(() => 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,                                    //轮廓的颜色
			}
		});
	}

	/**
	 * @method 获取偏航角
	 */
	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));
	}

	/**
	 * @method 获取俯仰角
	 */
	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 ViewShed;

三、保存至数据库

参考Geojson数据格式如下

{
    "type": "viewshed",
    "relevantId": "a4b43090-5927-4b21-b2d8-fb0db94f071f",
    "name": "可视域",
    "properties": {
        "destination": null,
        "orientation": null,
        "style": {
            "viewPosition": {
                "x": -2583989.8924737135,
                "y": 4663987.94986121,
                "z": 3488423.3463035254
            },
            "viewPositionEnd": {
                "x": -2583989.677120281,
                "y": 4663953.226079538,
                "z": 3488471.546363341
            },
            "viewDistance": 59.405666033692356,
            "viewHeading": 16.268876174425284,
            "horizontalViewAngle": 90,
            "verticalViewAngle": 60,
            "visibleAreaColor": "rgba(0, 255, 0, 1)",
            "invisibleAreaColor": "rgba(255 ,0, 0, 1)",
            "enabled": true,
            "size": 2048,
            "isSketch": true,
            "viewPitch": 1.0223964959573864
        },
        "entitiesData": {
            "type": "Point",
            "coordinates": [
                118.98777411497602,
                33.37118801269521,
                20.894417597967987
            ]
        }
    },
    "classId": "2",
    "layerId": "16fa9a502fbe4dc18220de4ab2d6960a"
}

三、可视域回显

这块可根据自己的业务编写

viewshedInit(ele) {
    const model = ele
    const style = model.properties.style
    this.drawViewshedEntity = new ViewShed(this.viewer, {
        viewPosition: style.viewPosition,
        viewDistance: style.viewDistance,
        viewPositionEnd: style.viewPositionEnd,
        horizontalViewAngle: style.horizontalViewAngle,
        verticalViewAngle: style.verticalViewAngle,
        viewPitch: style.viewPitch,
        viewHeading: style.viewHeading,
        visibleAreaColor: style.visibleAreaColor,
        invisibleAreaColor: style.invisibleAreaColor,
        isSketch: style.isSketch
    })
    this.drawViewshedEntity.update()
    //保存到全局数组中方便删除
    global.toolsList.push({ relevantId: model.relevantId, viewShed: this.drawViewshedEntity})
}

效果:
(1)倾斜摄影
在这里插入图片描述
(2)地形上
在这里插入图片描述
注意:如果出现地形上只能出现一个颜色的,添加

this.viewer.scene.globe.shadows = Cesium.ShadowMode.ENABLED
地形接收和投射阴影

总结

这是完整的可视域实现方式,也可以根据业务场景改动

  • 13
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

简单灬爱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值