*前言:嘗試了網上好多個版本的可視域分析,感覺都有一些問題,我這個也可能不是最完美的,但是我覺得對我來說夠用了,實現效果如下*
此示例基於vue3上實現,cesium
版本1.101.0
,vite-plugin-cesium
版本1.2.22
新建一個名為ViewshedAnalysis.js
的JS檔案
import glsl from './glsl2'
/**
* @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 ViewshedAnalysis {
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
}
add() {
this.createLightCamera()
this.createShadowMap()
this.drawFrustumOutline(); //視錐線
this.drawSketch()
this.createPostStage()
}
update() {
this.clear()
this.add()
}
/**
* @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
}
}
/**
* @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(() => {
return Cesium.Transforms.headingPitchRollQuaternion(
this.viewPosition,
Cesium.HeadingPitchRoll.fromDegrees(this.viewHeading - this.horizontalViewAngle, this.viewPitch, 0.5)
)
}, false),
ellipsoid: {
//橢球的半徑
radii: new Cesium.CallbackProperty(() => {
return new Cesium.Cartesian3(this.viewDistance,
this.viewDistance,
this.viewDistance)
}, false),
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 ViewshedAnalysis
引入js:
import ViewShed from '@/utils/analysis/visibility/ViewshedAnalysis.js'
呼叫方法:
const shootAreaAnalysis = (type) => {
store.setSelected('shootArea')
let i = 0
var horizontalViewAngle = 90 //視角水平張角
var verticalViewAngle = 60 //視角垂直張角
var endPosition = null
var viewShed = null
var handler = new Cesium.ScreenSpaceEventHandler(window.Viewer.scene.canvas)
handler.setInputAction(movement => {
i++
if (i === 1) {
var startPosition = window.Viewer.scene.pickPosition(movement.position) //滑鼠點選一次獲取開始座標
if (!startPosition) return
viewShed = new ViewShed(window.Viewer, {
viewPosition: startPosition,
viewPositionEnd: startPosition,
horizontalViewAngle: horizontalViewAngle,
verticalViewAngle: verticalViewAngle
})
// 滑鼠移動的事件
handler.setInputAction(movement => {
endPosition = window.Viewer.scene.pickPosition(movement.endPosition)
if (!endPosition) return
viewShed.updatePosition(endPosition)
if (!viewShed.sketch) {
viewShed.drawSketch()
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
}
// 滑鼠點選兩次獲取結束座標
if (i === 2) {
i = 0
endPosition = window.Viewer.scene.pickPosition(movement.position)
viewShed.updatePosition(endPosition)
viewShed.update()
handler && handler.destroy() //銷燬滑鼠事件
store.setSelected(null)
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
}
glsl2.js 檔案我找了兩個, 我測試都是可以實現的,只是效果問題,可以切換了嘗試一下
下載地址