cesium剖面分析
什么是剖面分析
剖面分析是指根据指定的剖面线,输出剖面线与地形数据的表面高程沿某条线(截面)的变化,或剖面线所截的模型建筑物、地下管线等的轮廓线,通过剖面分析可以分析三维表面的高程变化情况。
实现思路
本文采用Cesium + Echarts,并基于地形数据来实现剖面分析,实现思路的具体步骤如下:
- 加载地形(这一步是前提条件,需要成功加载地形之后才可进行剖面分析);
- 在地形上选取两个高程不同的点,获取到它们的坐标数据,并将其绘制出来;
- 根据获取到的两个坐标点,绘制贴地线;
- 根据获取到的两个坐标点进行插值运算输出多个点(取样点的个数可自定义,个数越大越精确,但耗费的计算时间也越多);
- 根据地形获取到这些坐标点所在的高度,并将这些点存到数组;
- 将数组的数据渲染到Echarts图表上,形成剖面图。
实现代码
由于剖面分析这个功能比较偏向于工具,且较为独立,因此将其封装成一个独立的工具类ts文件来集成相关代码,便于后续直接导入使用。
import * as echarts from 'echarts';
import {
Viewer,
ScreenSpaceEventHandler,
GroundPrimitive,
GeometryInstance,
CorridorGeometry,
VertexFormat,
ColorGeometryInstanceAttribute,
Color,
ClassificationType,
ScreenSpaceEventType,
Math,
createWorldTerrain,
Cartographic,
sampleTerrainMostDetailed,
Cartesian3,
PointPrimitiveCollection
} from 'cesium';
/** 剖面分析 */
export default class ProfileAnalysis {
private viewer: Viewer;
/** 场景操作 */
private handler: ScreenSpaceEventHandler;
/** 用于加载图表的HTMLElement */
private echartRef: any
/** 点的可渲染集合 */
private pointCollection: PointPrimitiveCollection;
/** 贴地几何 */
private groundPrimitive: GroundPrimitive[] | null = [];
constructor(viewer: Viewer, echartRef: any) {
this.viewer = viewer;
this.handler = new ScreenSpaceEventHandler(this.viewer.scene.canvas);
this.echartRef = echartRef;
this.pointCollection = new PointPrimitiveCollection();
this.viewer.scene.primitives.add(this.pointCollection)
}
/** 取点 */
private positions: Cartesian3[] = [];
/**
* @name: start
* @Date: 2022-09-27 17:34:15
* @description: 开始剖面分析
*/
public start() {
this.handler.setInputAction((click) => {
const ray = this.viewer.camera.getPickRay(click.position);
const cartesian = this.viewer.scene.globe.pick(ray!, this.viewer.scene);
console.log('取点', cartesian);
this.pointCollection.add({
position: cartesian,
color: Color.YELLOW,
pixelSize: 10
})
this.positions.push(cartesian!)
const length = this.positions.length
if (length >= 2) {
// 绘制贴地线
let lineInstance = new GeometryInstance({
geometry: new CorridorGeometry({
vertexFormat: VertexFormat.POSITION_ONLY,
positions: [this.positions[length - 1], this.positions[length - 2]],
width: 10
}),
attributes: {
color: ColorGeometryInstanceAttribute.fromColor(new Color(0.0, 1.0, 0.0, 0.5))
}
})
let groundPrimitive = new GroundPrimitive({
geometryInstances: lineInstance,
classificationType: ClassificationType.TERRAIN
});
this.groundPrimitive?.push(groundPrimitive)
this.viewer.scene.primitives.add(groundPrimitive);
// 插值运算
this.interPoints(this.positions)
}
}, ScreenSpaceEventType.LEFT_CLICK)
}
/**
* @name: stop
* @Date: 2022-09-28 09:54:08
* @description: 结束剖面分析
*/
public stop() {
// 清除鼠标左键单击事件
this.handler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
// 清除剖面图
this.disposeChart();
// 清除取点
this.positions = [];
// 清除插值点
this.interData = [];
// 清除绘制的点和线
// this.viewer.scene.primitives.remove(this.groundPrimitive);
for (let i = 0; i < this.groundPrimitive!.length; i++) {
this.viewer.scene.primitives.remove(this.groundPrimitive![i]);
}
this.viewer.scene.primitives.remove(this.pointCollection);
}
/** 插值点 */
private interData: [number, number][] = [];
/**
* @name: interPoints
* @Date: 2022-09-27 17:24:02
* @description: 坐标点插值positionsDegrees
* @param {any} positions 坐标点数组
* @param {number} interNumber 取样点个数
*/
private interPoints(positions: any, interNumber: number = 10) {
const posDegrees = [];
for (let index = 0; index < positions.length; index++) {
const element = positions[index];
const ellipsoid = this.viewer.scene.globe.ellipsoid;
const cartographic = ellipsoid.cartesianToCartographic(element);
let x = Math.toDegrees(cartographic.longitude)
let y = Math.toDegrees(cartographic.latitude)
let data = { longitude: x, latitude: y }
posDegrees.push(data);
}
console.log('转换成经纬度的点', posDegrees);
let offset, x, y;
for (let i = 0; i < interNumber; ++i) {
offset = i / (interNumber - 1);
// 做插值
x = Math.lerp(posDegrees[posDegrees.length - 2].longitude, posDegrees[posDegrees.length - 1].longitude, offset);
y = Math.lerp(posDegrees[posDegrees.length - 2].latitude, posDegrees[posDegrees.length - 1].latitude, offset);
this.interData.push([x, y]);
}
console.log('插值点', this.interData);
// 查询地图的地形高度
const terrainProvider = createWorldTerrain({
requestVertexNormals: true
})
let interDataMap = this.interData.map((d: [number, number]) => Cartographic.fromDegrees(...d))
// 根据地形和经纬度计算出高度
sampleTerrainMostDetailed(terrainProvider, interDataMap)
.then(positions => {
const heightList = positions.map((d: any) => ({
height: d.height,
longitude: Math.toDegrees(d.longitude),
latitude: Math.toDegrees(d.latitude)
}))
console.log('剖面分析结果', heightList)
// 生成剖面图
this.initChart(heightList)
})
}
/**
* @name: initChart
* @Date: 2022-09-28 09:12:39
* @description: 生成剖面图
* @param {any} data 高程数组
*/
private initChart(data: any) {
console.log('生成剖面图');
this.disposeChart();
let myChart = echarts.init(this.echartRef, "dark");
let labelList = []
for (let i = 1; i <= data.length; i++) {
labelList.push(i);
}
myChart.setOption({
xAxis: {
type: "category",
data: labelList
},
tooltip: {
trigger: "axis"
},
yAxis: {
type: "value"
},
series: [
{
data: data.map((e: any) => {
return e.height;
}),
type: "line",
smooth: true
}
]
});
window.onresize = () => {
myChart.resize();
};
}
/**
* @name: disposeChart
* @Date: 2022-09-28 09:42:46
* @description: 销毁剖面图
*/
public disposeChart() {
echarts.dispose(this.echartRef);
}
}
使用方法:引入剖面分析类文件,实例化后即可使用该功能。
// 引入类文件
import ProfileAnalysis from '@utils/cesium/tools/profileAnalysis';
// 实例化,参数分别为cesium的viewer对象和渲染图表的容器dom
const profile = new ProfileAnalysis(viewer, echartsRef.value);
// 开始剖面分析
profile.start();
// 销毁剖面图
profile.disposeChart();
// 结束剖面分析
profile.stop();