上一篇是绘制多边形,这一篇来说绘制矩形,但又因为只说绘制矩形太短了,所以就结合一下turf.js,生成一下地形三角网
Turf.js中文网
npm install @turf/turf
import * as turf from "@turf/turf";
//画矩形
DrawRectangle() {
var allPoints = [];
// 设置返回值
return new Promise((resolve, reject) => {
let viewer = this.viewer;
let topLeftPoint = null;
let bottomRightPoint = null;
let drawingRectangle = viewer.entities.add({
id: "drawingRectangle",
name: "画矩形",
rectangle: {
coordinates: new Cesium.CallbackProperty(() => {
if (topLeftPoint === null || bottomRightPoint === null) {
return;
}
let west = topLeftPoint.longitude;
let north = topLeftPoint.latitude;
let east = bottomRightPoint.longitude;
let south = bottomRightPoint.latitude;
return new Cesium.Rectangle(west, south, east, north);
}, false),
material: Cesium.Color.BLUE.withAlpha(0.2),
closeTop: true,
closeBottom: false
}
});
let handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
handler.setInputAction(event => {
var cartesian = this.getCatesian3FromPX(event.position);
if (cartesian) {
if (topLeftPoint === null) {
topLeftPoint = Cesium.Cartographic.fromCartesian(cartesian);
}
viewer.entities.add({
position: cartesian,
point: {
color: Cesium.Color.RED,
pixelSize: 10
}
});
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
handler.setInputAction(event => {
if (topLeftPoint) {
bottomRightPoint = Cesium.Cartographic.fromCartesian(this.getCatesian3FromPX(event.endPosition));
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
handler.setInputAction(() => {
if (topLeftPoint !== null && bottomRightPoint !== null) {
handler.destroy(); // 关闭鼠标事件监听,结束绘制
let west = Cesium.Math.toDegrees(topLeftPoint.longitude);
let north = Cesium.Math.toDegrees(topLeftPoint.latitude);
let east = Cesium.Math.toDegrees(bottomRightPoint.longitude);
let south = Cesium.Math.toDegrees(bottomRightPoint.latitude);
allPoints.push({ lng: west, lat: north });
allPoints.push({ lng: east, lat: north });
allPoints.push({ lng: east, lat: south });
allPoints.push({ lng: west, lat: south });
allPoints.push(allPoints[0]); // 闭合
resolve(allPoints);
}
}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
});
},
(getCatesian3FromPX方法在上一篇)
DrawRectangle() { … }: 这是定义DrawRectangle方法的部分
var allPoints = [];: 初始化一个空数组,用于保存绘制的矩形的顶点坐标。
let topLeftPoint = null;: 初始化一个变量,用于存储矩形的左上角点。
let bottomRightPoint = null;: 初始化一个变量,用于存储矩形的右下角点。
return new Promise((resolve, reject) => { … });: 返回一个新的Promise对象,用于处理异步操作。resolve和reject是两个回调函数,分别表示异步操作成功和失败的处理。
let drawingRectangle = viewer.entities.add({ … });: 用于在Cesium Viewer中添加一个新的矩形Entity。它使用CallbackProperty动态计算矩形的坐标。
let handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);: 初始化一个新的Cesium屏幕空间事件处理器。
当用户在Cesium Viewer上单击鼠标左键时,这个事件会被触发。
if (topLeftPoint === null) { … }: 判断是否已经设置了矩形的左上角点。如果没有,则设置。
当用户在Cesium Viewer上移动鼠标时,这个事件会被触发。
if (topLeftPoint) { … }: 当左上角点设置后,根据鼠标的当前位置设置矩形的右下角点。
监听鼠标双击事件:
当用户在Cesium Viewer上双击鼠标左键时,这个事件会被触发。
当左上角和右下角点都被设置后,结束绘制,关闭鼠标事件监听。
handler.destroy(); 用于关闭鼠标事件监听,结束绘制。
计算出矩形的四个顶点的经纬度坐标,并将这些坐标点保存到allPoints数组中。
resolve(allPoints);: 最后,通过resolve函数将allPoints数组传递出去,表示异步操作成功完成。
这个DrawRectangle方法允许用户通过以下三个步骤在Cesium地图上绘制一个矩形:
单击地图以设置矩形的左上角点。
移动鼠标以实时预览矩形的形状。
双击地图以确认矩形的右下角点,并完成矩形的绘制。
方法返回一个Promise对象,当用户完成矩形的绘制后,这个Promise对象将被resolve,返回矩形的顶点坐标。
(resultPoints即上面矩形的顶点坐标返回值,num是手动穿的参数,当然数值越大点就越多了)
// 1. 创建一个矩形区域
// var rectangle = Cesium.Rectangle.fromDegrees(117.09649937089316, 36.20673458245797, 117.11797117691083, 36.230040948473906)
var rectangle = Cesium.Rectangle.fromDegrees(resultPoints[0].lng,resultPoints[2].lat,resultPoints[2].lng,resultPoints[0].lat)
// 2. 在这个矩形区域内生成点集
var width = num; // 横向点数
var height = num; // 纵向点数
var terrainProvider =viewer.terrainProvider
var positions = [];
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var longitude = Cesium.Math.lerp(rectangle.west, rectangle.east, x / (width - 1));
var latitude = Cesium.Math.lerp(rectangle.south, rectangle.north, y / (height - 1));
positions.push(Cesium.Cartographic.fromRadians(longitude, latitude));
}
}
(这里的terrainProvider即地图加载的地形,我这里使用了cesium默认带的地形:viewer.terrainProvider = Cesium.createWorldTerrain({ requestVertexNormals: true, requestWaterMask: true });
,参数terrainProvider即这里的viewer.terrainProvider。positions即上面的positions)
Cesium.sampleTerrainMostDetailed(terrainProvider, positions).then(function(samples) {
var points = samples.map(function(sample) {
return turf.point([Cesium.Math.toDegrees(sample.longitude), Cesium.Math.toDegrees(sample.latitude), sample.height],
{ z: sample.height }) // 将高度值保存到名为 'z' 的属性中);
});
//显示点集
var cartesianPoints = points.map(function(point) {
var coord = point.geometry.coordinates;
var cartographic = Cesium.Cartographic.fromDegrees(coord[0], coord[1], coord[2]);
return Cesium.Cartographic.toCartesian(cartographic);
});
var pointCollection = new Cesium.PointPrimitiveCollection();
cartesianPoints.forEach(function(position) {
pointCollection.add({
position: position,
color: Cesium.Color.RED,
pixelSize: 5
});
});
viewer.scene.primitives.add(pointCollection);
})
(需要注意的是,//显示点集后面的添加点的代码在生成三角网是不必要的,只是我觉得看着好看一点 )
即将Cesium.sampleTerrainMostDetailed(terrainProvider, positions)
方法里面改为:
Cesium.sampleTerrainMostDetailed(terrainProvider, positions).then(function(samples) {
var points = samples.map(function(sample) {
return turf.point([Cesium.Math.toDegrees(sample.longitude), Cesium.Math.toDegrees(sample.latitude), sample.height],
{ z: sample.height }) // 将高度值保存到名为 'z' 的属性中);
});
// 创建一个FeatureCollection
var featureCollection = turf.featureCollection(points);
var tin = turf.tin(featureCollection, 'z');
var geometryInstances = []; // 用于存放所有的三角形GeometryInstance
var instances = [];
// 遍历每个TIN三角形
tin.features.forEach(function(feature,i) {
var coordinates = feature.geometry.coordinates[0];
var heights = [feature.properties.a, feature.properties.b, feature.properties.c];
var positions = coordinates.map(function(coord, index) {
return Cesium.Cartesian3.fromDegrees(coord[0], coord[1], heights[index]);
});
viewer.entities.add({
name: "三角面",
id: "triangle"+i,
polygon: {
hierarchy: [positions[0], positions[1], positions[2]],
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
perPositionHeight: true,
material: Cesium.Color.fromCssColorString("#23B8BA").withAlpha(
1.0
),
// extrudedHeight: 0,
outline: true,
outlineColor: Cesium.Color.GREEN,
}
});
});
var cartesianPoints = points.map(function(point) {
var coord = point.geometry.coordinates;
var cartographic = Cesium.Cartographic.fromDegrees(coord[0], coord[1], coord[2]);
return Cesium.Cartographic.toCartesian(cartographic);
});
var pointCollection = new Cesium.PointPrimitiveCollection();
cartesianPoints.forEach(function(position) {
pointCollection.add({
position: position,
color: Cesium.Color.RED,
pixelSize: 5
});
});
viewer.scene.primitives.add(pointCollection);
})
GeneratingTriangulation(resultPoints,num) { … }: 这是定义GeneratingTriangulation方法的部分,它接受两个参数:resultPoints是一个包含矩形四个顶点坐标的数组;num是横向和纵向的点数。
var rectangle = Cesium.Rectangle.fromDegrees(…):根据resultPoints中提供的坐标,创建一个矩形区域。
var positions = [];: 初始化一个空数组,用于保存生成的点集的坐标。
通过两个嵌套循环在矩形区域内均匀地生成点集。
Cesium.sampleTerrainMostDetailed(terrainProvider, positions).then(function(samples) { … }:使用Cesium的sampleTerrainMostDetailed函数获取每个点的高程信息。
将Cesium高程采样点转换为Turf.js点格式,并保存每个点的高程(Z值)。
var tin = turf.tin(featureCollection, ‘z’);: 使用Turf.js的tin函数,基于点集和它们的Z值生成TIN三角网。
通过遍历每个TIN三角形,并为每个三角形创建一个Cesium的polygon实体。这些多边形实体有高度信息,因此它们会按照地形进行渲染。
遍历cartesianPoints(点集的笛卡尔坐标),并将它们作为点实体添加到地图上,这样可以在地图上可视化这些点。
Cesium.Rectangle.fromDegrees(…):这个函数创建一个矩形,参数是矩形的西、南、东、北边界的经纬度。
Cesium.sampleTerrainMostDetailed(terrainProvider, positions):这个函数从给定的地形服务提供商中采样最详细级别的地形数据,以获取指定位置的地形高程。
turf.tin(…):这个函数是Turf.js库中的一个函数,用于根据输入的点集生成TIN三角网。Turf.js是一个JavaScript库,用于地理空间分析。
这个方法最终的结果是在Cesium地图上绘制了一个TIN三角网,该三角网是根据指定的矩形区域和点数生成的。每个三角形都是一个单独的Cesium实体,并且这些实体的顶点高度是基于地形数据的。
在tin.features.forEach
中,添加如下代码:
const point1 = positions[0];
const point2 = positions[1];
const point3 = positions[2];
//使用我们新定义的函数计算每个三角形的坡度
var analysisResult = that.calculateSlopeAndAspect(point1,point2,point3);
/**
* 计算三角形的坡度
* @param {*} point1
* @param {*} point2
* @param {*} point3
* @returns
*/
calculateSlopeAndAspect(point1, point2, point3) {
// 计算两个向量,它们位于三角形的两边
var v1 = Cesium.Cartesian3.subtract(point1, point2, new Cesium.Cartesian3());
var v2 = Cesium.Cartesian3.subtract(point2, point3, new Cesium.Cartesian3());
// 计算这两个向量的叉积,得到三角形的法向量
var normal = new Cesium.Cartesian3();
Cesium.Cartesian3.cross(v1, v2, normal);
Cesium.Cartesian3.normalize(normal, normal);
// 计算坡度:法向量和垂直向量(0,0,1)之间的夹角
var up = new Cesium.Cartesian3(0, 0, 1);
var slopeRadians = Math.acos(Cesium.Cartesian3.dot(normal, up));
var slopeDegrees = Cesium.Math.toDegrees(slopeRadians);
// 如果坡度大于90度,则将其减少到90度以下
if (slopeDegrees > 90) {
slopeDegrees = 180 - slopeDegrees;
}
// 返回坡度值
return {slope: slopeDegrees};
},
即在 var analysisResult =...
后面添加:
var slope = analysisResult.slope;
var hue = slope / 90.0; // 将坡度从0到90度映射到色调从0到1
var saturation = 1.0; // 全饱和度
var lightness = 0.5; // 正常亮度
var alpha = 1.0; // 完全不透明
//将HSL颜色转换为RGBA,当坡度为0度时,hue变为0,颜色是红色;当坡度为90度时,hue变为1,颜色是绿色;在0到90度之间的坡度将映射到从红色到绿色之间的颜色。
var color = Cesium.Color.fromHsl(hue, saturation, lightness).withAlpha(alpha);
material: color,
将三角网高度抬升一下,方便观察,提高个300米
var positions = coordinates.map(function(coord, index) {
return Cesium.Cartesian3.fromDegrees(coord[0], coord[1], heights[index]+300);
});
(越红坡度越大)
至于最开始的第三张图,中间的黄色体的实现就不再赘述了,无非是一个个五面体构成的(顶面就是每个三角面,然后三个四边形侧面(两个顶面点加两个底面点),再加上高度值一定的三角底面)