无源淹没分析比较简单,核心就是抬升面的高度。
这里将面绘制完后,获取最高和最低的高程值,传入表单框中。
点击分析,开始抬升
高程获取
灵感来自:Cesium选定范围取最高、最低高程及填挖方分析 | 开飞机的老张 (kaifeiji.cc)
Turf中文网:Turf.js中文网 (fenxianglu.cn)
过程:从Cesium中获取坐标后传入Turf中进行插值,然后返回插值点坐标,Cesium中通过Cesium.sampleTerrain获取高程值,详细见官网API:Global - Cesium Documentation
高程获取函数:
const minAndMaxHeight =async (positions) => {
//将笛卡尔坐标转为经纬度坐标,并转为turf.js的Polygon类型
const lnglats = positions.map(position => [Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(position).longitude),
Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(position).latitude)])
console.log(lnglats)
const turfPolygon = turf.lineString(lnglats) // 转为turf.js的Polygon类型
const turfExtent = turf.bbox(turfPolygon) // 获取turfPolygon的边界框
// 在turfPolygon中按网格取样点,网格间距8米
const turfSamplePoints = turf.pointGrid(turfExtent, 0.08, {
units: 'kilometers',
// mask: turfPolygon,
});
// 将turf取样点转为Cesium的取样点
const cesiumSamplePoints = []
for (let i = 0; i < turfSamplePoints.features.length; i++) {
const coord = turfSamplePoints.features[i].geometry.coordinates;
cesiumSamplePoints.push(Cesium.Cartographic.fromDegrees(coord[0], coord[1]));
}
const terrainProvider = await Cesium.createWorldTerrainAsync(); // 获取地形服务
const updatedPositions = await Cesium.sampleTerrain(terrainProvider, 11, cesiumSamplePoints) // 计算取样点的高度
let minHeight = 8888
let maxHeight = 0
// 计算取样点的最小高度和最大高度
for (let i = 0; i < updatedPositions.length; i++) {
const height = updatedPositions[i].height
if (height < minHeight) {
minHeight = height
}
if (height > maxHeight) {
maxHeight = height
}
}
console.log(minHeight, maxHeight);
return { minHeight, maxHeight }
}
提醒:
这里面的数字:0.08是插值间距,越小,插值越密
11是地形缩放等级
这两个数值极大影响计算速度,设置不当,当绘制范围过大,计算量将十分大,会直接崩溃。
下面是该功能所有代码:
<script setup>
import * as turf from '@turf/turf'
import { nextTick, reactive } from 'vue'
import * as Cesium from 'cesium'// Import Cesium
import { useCesiumStore } from '@/stores/useCesiumStore.js'
import { toRaw } from 'vue'
import { ElLoading } from 'element-plus'
import 'element-plus/theme-chalk/el-loading.css'
let viewer = null
nextTick(()=>{
viewer = toRaw(useCesiumStore().viewer)
})
const formLabelAlign = reactive({ minHeight: 0, maxHeight: 1000, speed: 1 })
let height = 0
let handler = null
let positions = []
let region = null
let activeShapePoints = [] // 存放当前绘制的多边形的点坐标
let activeShape = null // 存放当前绘制的多边形对象
//获取范围内最小和最大的高程值
const minAndMaxHeight =async (positions) => {
//将笛卡尔坐标转为经纬度坐标,并转为turf.js的Polygon类型
const lnglats = positions.map(position => [Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(position).longitude),
Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(position).latitude)])
console.log(lnglats)
const turfPolygon = turf.lineString(lnglats) // 转为turf.js的Polygon类型
const turfExtent = turf.bbox(turfPolygon) // 获取turfPolygon的边界框
// 在turfPolygon中按网格取样点,网格间距8米
const turfSamplePoints = turf.pointGrid(turfExtent, 0.08, {
units: 'kilometers',
// mask: turfPolygon,
});
// 将turf取样点转为Cesium的取样点
const cesiumSamplePoints = []
for (let i = 0; i < turfSamplePoints.features.length; i++) {
const coord = turfSamplePoints.features[i].geometry.coordinates;
cesiumSamplePoints.push(Cesium.Cartographic.fromDegrees(coord[0], coord[1]));
}
const terrainProvider = await Cesium.createWorldTerrainAsync(); // 获取地形服务
const updatedPositions = await Cesium.sampleTerrain(terrainProvider, 11, cesiumSamplePoints) // 计算取样点的高度
let minHeight = 8888
let maxHeight = 0
// 计算取样点的最小高度和最大高度
for (let i = 0; i < updatedPositions.length; i++) {
const height = updatedPositions[i].height
if (height < minHeight) {
minHeight = height
}
if (height > maxHeight) {
maxHeight = height
}
}
console.log(minHeight, maxHeight);
return { minHeight, maxHeight }
}
//水位高度更新函数
const updataHeight = () => {
if (height < formLabelAlign.maxHeight ) {
height += formLabelAlign.speed
return height
}
else{
return height
}
}
//绘制淹没区
const addPolygon = ( hierarchy ) => {
const addRegion = viewer.entities.add({
id: 'addRegion',
name: 'addRegion',
show: true,
polygon: {
hierarchy: hierarchy,
material: new Cesium.ColorMaterialProperty(Cesium.Color.fromBytes(64, 157, 253, 50))
}
})
return addRegion
}
//绘制区域
const draw = async () => {
console.log(1);
handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
viewer.entities.remove(region)
// 监听鼠标左键点击事件
handler.setInputAction( function (event) {
const earthPosition = viewer.scene.pickPosition(event.position)
if (Cesium.defined(earthPosition)){
positions.push(earthPosition)
if(activeShapePoints.length === 0){
activeShapePoints.push(earthPosition)
const dynamicPositions = new Cesium.CallbackProperty(() => {
return new Cesium.PolygonHierarchy(activeShapePoints)
}, false)
activeShape = addPolygon(dynamicPositions) //绘制第一个多边形
}
//添加当前点到activeShapePoints数组,实时渲染动态图
activeShapePoints.push(earthPosition)
}
else {
alert('请点击地球上的位置')
return
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
//鼠标移动事件
handler.setInputAction((event) => {
if ( activeShapePoints.length > 0 ) {
const newPosition = viewer.scene.pickPosition(event.endPosition) //获取鼠标移动结束位置的地理坐标
if (Cesium.defined(newPosition)) {
activeShapePoints.pop() //删除最后一个点
activeShapePoints.push(newPosition) //添加新的点
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
// 监听鼠标右键点击事件
handler.setInputAction(async function () {
if (positions.length < 3) {
return alert('至少需要绘制三个点才能进行分析')
}
viewer.entities.remove(activeShape) //删除多边形
addPolygon(positions) //绘制淹没区
const loadingInstance = ElLoading.service({
lock: true,
fullscreen: true,
text: '正在计算地形高度...',
background: 'rgba(0, 0, 0, 0.7)'
})
region = viewer.entities.getById('addRegion')
const result = await minAndMaxHeight(positions)
formLabelAlign.minHeight = result.minHeight
formLabelAlign.maxHeight = result.maxHeight
nextTick(() => {
loadingInstance.close()
})
activeShape = undefined //清空变量
activeShapePoints = [] //清空数组
positions = [] //清空数组
handler.destroy() //销毁事件处理器
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
}
//开始淹没分析
const analysis = () => {
if (!handler.isDestroyed){
return alert('请先结束绘制')
}
if(formLabelAlign.minHeight === 0 || formLabelAlign.maxHeight === 0 || formLabelAlign.speed === 0 || formLabelAlign.minHeight >= formLabelAlign.maxHeight){
return alert('请输入正确的水位高度')
}
region.polygon.material = new Cesium.ImageMaterialProperty({
image: 'src/assets/water/river.png',
repeat: new Cesium.Cartesian2(1, 1),
transparent: true,
color: Cesium.Color.WHITE.withAlpha(0.7)
})
height = formLabelAlign.minHeight
region.polygon.extrudedHeight = new Cesium.CallbackProperty(updataHeight, false)
}
//重置区域
const reset = () => {
viewer.entities.remove(region)
region = undefined
positions = []
activeShapePoints = []
// if(handler && !handler.isDestroyed) handler.destroy()
if(activeShape) viewer.entities.remove(activeShape)
}
//关闭分析面板
const emits = defineEmits(['isShowInundationAnalysis']) // 定义事件
const closeInundation = () => {
reset()
// if (handler.isDestroyed) handler.destroy()
positions = []
activeShapePoints = []
activeShape = undefined
region = undefined
formLabelAlign.minHeight = 0
formLabelAlign.maxHeight = 0
formLabelAlign.speed = 0
viewer.entities.remove(viewer.entities.getById('addRegion'))
emits('isShowInundationAnalysis', false)
}
</script>
<template>
<div color="#a0cfff" class="inundation-analysis">
<el-form label-position=right label-width="auto" :model="formLabelAlign">
<el-form-item label="请输入起始水位高度:">
<el-input v-model.lazy.number="formLabelAlign.minHeight" type="number" />
</el-form-item>
<el-form-item label="请输入终止水位高度:">
<el-input v-model.lazy.number="formLabelAlign.maxHeight" type="number" />
</el-form-item>
<el-form-item label="水位增长速度:">
<el-input v-model.lazy.number="formLabelAlign.speed" type="number" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="draw">
绘制区域
</el-button>
<el-button @click="analysis" type="primary">分析</el-button>
</el-form-item>
<el-form-item>
<el-button @click="reset">
重置
</el-button>
<el-button @click="closeInundation">关闭</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style scoped>
.inundation-analysis {
position: absolute;
top: 100px;
left: 20px;
z-index: 9999;
/* 背景色为浅蓝,透明度为0.5 */
background-color: rgba(160, 207, 255, 0.8);
/* 圆角 */
border-radius: 10px;
padding: 10px;
}
.inundation-analysis .el-input{
width: 100px;
height: 23px;
}
.inundation-analysis .el-button{
width: 100px;
/* 居中 */
margin-left: 20px;
}
</style>