上一篇学习了创建实体的一些基础知识,但有时还需要我们使用鼠标进行手动绘制,这一篇就来进行鼠标绘制实体的实现(点,线,矩形,圆,多边形)。
一、鼠标事件
既然是鼠标绘制,自然离不开鼠标事件,我们需要先简单了解一下cesium的鼠标事件。
1. ScreenSpaceEventHandler:
这是Cesium中用于处理鼠标和触摸事件的主要类。需要创建一个此类的实例来开始事件监听。
let handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
handler.setInputAction(event => {
略
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
上述代码示例为Cesium的canvas创建了一个新的事件处理器。
2. 常用的鼠标事件类型 (ScreenSpaceEventType):
LEFT_CLICK: 左键点击
RIGHT_CLICK: 右键点击
LEFT_DOUBLE_CLICK: 左键双击
RIGHT_DOUBLE_CLICK: 右键双击
MIDDLE_CLICK: 中键点击
MOUSE_MOVE: 鼠标移动
WHEEL: 鼠标滚轮滚动
3. 注册事件监听:
使用 setInputAction 方法可以为特定的事件类型设置回调函数。
在回调函数中的 event 对象,包含与事件相关的数据。例如,对于鼠标点击事件,可以访问 event.position 来获取鼠标点击的屏幕坐标。
handler.setInputAction(function(event) {
console.log('鼠标左键点击位置::', event.position);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
4. 关闭或销毁事件监听:
handler.destroy(); //永久性销毁事件处理器,之后它不能再被使用。
handler.removeInputAction(eventType); //仅移除特定事件类型的监听(如:handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);)
二、鼠标绘制点
(1)主要操作及思路:
允许用户点击地球的某个位置,然后在那个位置上添加一个红色的点,用户鼠标右击,结束绘制,并返回点位置。
(2)代码:
主方法:
/**
* 绘制点
*/
DrawPoints() {
return new Promise((resolve, reject) => {
let viewer = this.viewer;
let drawnPoints = [];
// 创建一个事件处理器
let handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
// 注册鼠标左键点击事件,用于绘制点
handler.setInputAction(event => {
// 获取鼠标点击的笛卡尔坐标(鼠标点击位置->笛卡尔坐标)
var cartesian = this.getCatesian3FromPX(event.position);
// 确保坐标有效
if (cartesian) {
// 添加点实体
viewer.entities.add({
position: cartesian,
point: {
color: Cesium.Color.RED,
pixelSize: 10
}
});
// 获取地理坐标(经纬度)
let cartographic = Cesium.Cartographic.fromCartesian(cartesian);
let longitude = Cesium.Math.toDegrees(cartographic.longitude);
let latitude = Cesium.Math.toDegrees(cartographic.latitude);
let height=Cesium.Math.toDegrees(cartographic.height);
// 将绘制的点添加到数组中
drawnPoints.push({ lng: longitude, lat: latitude,height:height });
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 注册鼠标右键点击事件,用于结束绘制
handler.setInputAction(() => {
// 销毁事件处理器
handler.destroy();
// 返回所有绘制的点
resolve(drawnPoints);
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
});
},
调用进行位置拾取和坐标转换的方法(此处三个方法来自测量插件):
/**
* 拾取位置点
* @param {Object} px 屏幕坐标
* @return {Object} Cartesian3 三维坐标
*/
getCatesian3FromPX: function(px) {
if (this.viewer && px) {
var picks = this.viewer.scene.drillPick(px);
var cartesian = null;
var isOn3dtiles = false,
isOnTerrain = false;
// drillPick
for (let i in picks) {
let pick = picks[i];
if (
(pick && pick.primitive instanceof Cesium.Cesium3DTileFeature) ||
(pick && pick.primitive instanceof Cesium.Cesium3DTileset) ||
(pick && pick.primitive instanceof Cesium.Model)
) {
//模型上拾取
isOn3dtiles = true;
}
// 3dtilset
if (isOn3dtiles) {
this.viewer.scene.pick(px); // pick
cartesian = this.viewer.scene.pickPosition(px);
if (cartesian) {
let cartographic = Cesium.Cartographic.fromCartesian(cartesian);
if (cartographic.height < 0) cartographic.height = 0;
let lon = Cesium.Math.toDegrees(cartographic.longitude),
lat = Cesium.Math.toDegrees(cartographic.latitude),
height = cartographic.height;
cartesian = this.transformWGS84ToCartesian({
lng: lon,
lat: lat,
alt: height
});
}
}
}
// 地形
let boolTerrain =
this.viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider;
// Terrain
if (!isOn3dtiles && !boolTerrain) {
var ray = this.viewer.scene.camera.getPickRay(px);
if (!ray) return null;
cartesian = this.viewer.scene.globe.pick(ray, this.viewer.scene);
isOnTerrain = true;
}
// 地球
if (!isOn3dtiles && !isOnTerrain && boolTerrain) {
cartesian = this.viewer.scene.camera.pickEllipsoid(
px,
this.viewer.scene.globe.ellipsoid
);
}
if (cartesian) {
let position = this.transformCartesianToWGS84(cartesian);
if (position.alt < 0) {
cartesian = this.transformWGS84ToCartesian(position, 0.1);
}
return cartesian;
}
return false;
}
},
/***
* 坐标转换 84转笛卡尔
* @param {Object} {lng,lat,alt} 地理坐标
* @return {Object} Cartesian3 三维位置坐标
*/
transformWGS84ToCartesian: function(position, alt) {
if (this.viewer) {
return position
? Cesium.Cartesian3.fromDegrees(
position.lng || position.lon,
position.lat,
(position.alt = alt || position.alt),
Cesium.Ellipsoid.WGS84
)
: Cesium.Cartesian3.ZERO;
}
},
/***
* 坐标转换 笛卡尔转84
* @param {Object} Cartesian3 三维位置坐标
* @return {Object} {lng,lat,alt} 地理坐标
*/
transformCartesianToWGS84: function(cartesian) {
if (this.viewer && cartesian) {
var ellipsoid = Cesium.Ellipsoid.WGS84;
var cartographic = ellipsoid.cartesianToCartographic(cartesian);
return {
lng: Cesium.Math.toDegrees(cartographic.longitude),
lat: Cesium.Math.toDegrees(cartographic.latitude),
alt: cartographic.height
};
}
},
getCatesian3FromPX方法说明:
该方法根据给定的屏幕坐标px,计算出对应的三维世界坐标。该三维世界坐标可以代表一个具体的点在地图上的位置。
使用drillPick方法来获取屏幕坐标点上所有的对象。如果该点上有一个或多个对象,方法会尝试从3D模型上拾取坐标。
如果拾取不在3D模型上,并且地形存在,则从地形上拾取坐标。
如果既不在3D模型上也不在地形上,则从地球椭球体上拾取坐标。
最后返回这个点的三维世界坐标,或者在无法确定时返回false。
transformWGS84ToCartesian方法说明:
该方法根据给定的地理坐标(WGS84格式)计算出相应的三维世界坐标(笛卡尔坐标)。
使用Cesium的Cartesian3.fromDegrees方法从给定的经纬度和高度计算出三维坐标。
transformCartesianToWGS84方法说明:
该方法根据给定的三维世界坐标(笛卡尔坐标)计算出相应的地理坐标(WGS84格式)。
使用Cesium的Ellipsoid.WGS84和cartesianToCartographic方法将三维世界坐标转换为地理坐标。
三、鼠标绘制线
(1)主要操作及思路:
使用左键点击事件来添加点,创建一个折线实体来表示用户点击确定的所有点之间的线段。同时每当用户移动鼠标时,显示从最后一个点击位置到当前鼠标位置的一个动态线段。
(2)代码:
/**
* 绘制折线
*/
DrawPolyline() {
return new Promise((resolve, reject) => {
let viewer = this.viewer;
let polylinePoints = [];
// 临时折线实体
let polylineEntity = viewer.entities.add({
Id:"drawingPolyline",
name:"画线",
polyline: {
//使用CallbackProperty允许我们在用户点击时动态更新线段的位置
positions: new Cesium.CallbackProperty(() => {
return polylinePoints;
}, false),
width: 2,
material: Cesium.Color.RED
}
});
// 临时动态线实体
let dynamicLineEntity = viewer.entities.add({
polyline: {
positions: new Cesium.CallbackProperty(() => {
if (lastPoint && currentMousePoint) {
return [lastPoint, currentMousePoint];
} else {
return [];
}
}, false),
width: 2,
material: Cesium.Color.RED.withAlpha(0.5) // 使用半透明红色,与主线区分
}
});
let lastPoint = null;
let currentMousePoint = null;
// 创建事件处理器
let handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
// 注册鼠标左键点击事件,用于添加点和显示点
handler.setInputAction(event => {
let cartesian = this.getCatesian3FromPX(event.position);
if (cartesian) {
polylinePoints.push(cartesian);
lastPoint = cartesian;
viewer.entities.add({
position: cartesian,
point: {
color: Cesium.Color.BLUE,
pixelSize: 10
}
});
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 鼠标移动事件,更新当前鼠标位置并重绘临时线
handler.setInputAction(event => {
currentMousePoint = this.getCatesian3FromPX(event.endPosition);
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 注册鼠标左键双击点击事件,用于结束绘制
handler.setInputAction(() => {
handler.destroy();
viewer.entities.remove(dynamicLineEntity); // 移除临时线
resolve(polylinePoints);
}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
});
},
四、鼠标绘制圆
(1)主要操作及思路:
用户首先通过单击选择圆的中心,然后通过移动鼠标选择半径。当用户双击时,绘制将完成。
(2)代码:
/**
* 绘制圆形
*/
DrawCircle() {
return new Promise((resolve, reject) => {
let viewer = this.viewer;
let centerPoint = null;
let centerPointEntity = null; // 用于存储中点实体的引用
let radius = 10;
viewer.scene.globe.depthTestAgainstTerrain = false;
let drawingCircle = viewer.entities.add({
id: "drawingCircle",
name: "画圆",
ellipse: {
semiMinorAxis: new Cesium.CallbackProperty(() => {
return radius;
}, false),
semiMajorAxis: new Cesium.CallbackProperty(() => {
return radius;
}, false),
material: Cesium.Color.BLUE.withAlpha(0.2),
outline: true,
outlineColor: Cesium.Color.RED,
outlineWidth:2,
fill: true, //为true时只显示轮廓线
}
});
let handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
handler.setInputAction(event => {
var cartesian = this.getCatesian3FromPX(event.position);
if (cartesian && centerPoint === null) {
centerPoint = cartesian;
drawingCircle.position = centerPoint;
// 添加中点实体并保存其引用
centerPointEntity = viewer.entities.add({
position: cartesian,
point: {
color: Cesium.Color.RED,
pixelSize: 10
}
});
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
handler.setInputAction(event => {
if (centerPoint) {
let cartesian = this.getCatesian3FromPX(event.endPosition);
if (cartesian) {
let distance = Cesium.Cartesian3.distance(centerPoint, cartesian);
radius = distance;
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
handler.setInputAction(() => {
if (centerPoint !== null && radius > 0) {
handler.destroy(); // 关闭鼠标事件监听,结束绘制
let circleCenter = Cesium.Cartographic.fromCartesian(centerPoint);
let lng = Cesium.Math.toDegrees(circleCenter.longitude);
let lat = Cesium.Math.toDegrees(circleCenter.latitude);
resolve({
center: { lng: lng, lat: lat },
radius: radius
});
}
}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
});
},
五、鼠标绘制矩形
(1)主要操作及思路:
首次单击选择左上角,移动鼠标调整矩形的大小,然后双击来结束绘制并保存矩形的点。通过捕获用户在地图上的鼠标操作来确定矩形的两个对角点,然后使用这两个点来定义矩形。
(2)代码:
/**
* 绘制矩形
*/
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);
});
},
六、鼠标绘制多边形
贴地:
不贴地:
(1)主要操作及思路:
点击地图以定义多边形的顶点,可以随着鼠标移动看到多边形的形状实时更新,通过双击结束多边形绘制,并返回所有的顶点。
(2)代码:
主方法:
/**
* 绘制多边形
* @param {Object} option
* @param {Boolean} option.ground 是否贴地
*/
DrawPolygon(option) {
var allPoints=[]
// 设置返回值
return new Promise((resolve, reject) => {
// 1. 获取Cesium Viewer
let viewer = this.viewer;
// 2. 创建一个用于存储多边形顶点的数组
let polygonPoints = [];
// 3. 创建一个用于显示当前绘制中的多边形的实体
let drawingPolygon = viewer.entities.add({
id: "drawingPolygon",
name: "画多边形",
polygon: {
hierarchy: new Cesium.CallbackProperty(() => {
return new Cesium.PolygonHierarchy(polygonPoints);
}, false),
material: Cesium.Color.BLUE.withAlpha(0.2),
perPositionHeight: (option&&option.ground)||false // true:不贴地/false:贴地
},
});
// 4. 创建一个用于显示当前绘制中的线的实体
let drawingLine = viewer.entities.add({
id: "drawingLine",
name: "画线",
polyline: {
positions: new Cesium.CallbackProperty(() => {
return polygonPoints;
}, false),
width: 3,
material: Cesium.Color.GREEN
}
});
// 5. 监听鼠标点击事件,将点击的点添加到顶点数组中,并添加点实体
let handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
handler.setInputAction(event => {
var cartesian = this.getCatesian3FromPX(event.position);
if (cartesian) {
// 将点坐标添加到数组中
polygonPoints.push(cartesian.clone());
// 在第一次点击时,添加一个克隆的点到数组中,用于动态更新
if (polygonPoints.length === 1) {
polygonPoints.push(cartesian.clone());
}
// 添加点实体
viewer.entities.add({
position: cartesian,
point: {
color: Cesium.Color.RED,
pixelSize: 10
}
});
//将三维笛卡尔坐标系点转为经纬度坐标点,并保存到点数组中
let cartesian3 = cartesian.clone()
// 使用Cesium.Cartographic.fromCartesian将Cartesian3对象转换为Cartographic对象
let cartographic = Cesium.Cartographic.fromCartesian(cartesian3);
allPoints.push([Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude), cartographic.height]);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 6. 监听鼠标移动事件,动态更新多边形和线的形状
handler.setInputAction(event => {
var cartesian = this.getCatesian3FromPX(event.endPosition);
if (polygonPoints.length >= 2) {
if (cartesian && cartesian.x) {
polygonPoints.pop();
polygonPoints.push(cartesian);
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 7. 监听鼠标右键点击事件,结束绘制
handler.setInputAction(() => {
var cartesian=polygonPoints[polygonPoints.length-1]
// 添加点实体
viewer.entities.add({
position: cartesian,
point: {
color: Cesium.Color.RED,
pixelSize: 10
}
});
polygonPoints.push(polygonPoints[0]);
handler.destroy(); // 关闭鼠标事件监听,结束绘制
resolve(allPoints);
}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
})
},
调用方法:
let option = {
ground: true //true:不贴地/false:贴地
};
DrawPolygon(option).then(allPoints => {
// 在这里,allPoints是结束绘制后的点坐标数组
var resultPoints=allPoints
})
(3)DrawPolygon方法说明:
1,定义返回结果的方式:
var allPoints=[]
return new Promise((resolve, reject) => {
......
}
在这个方法开始时,定义了一个allPoints数组,用于存储绘制的多边形的所有顶点,并且返回一个Promise,允许在绘制结束后将这些点的坐标返回。
2,获取Cesium Viewer:
let viewer = this.viewer;
获取Cesium的Viewer实例
3,创建一个实体以显示绘制中的多边形:
let drawingPolygon = viewer.entities.add({ ... });
这段代码通过Cesium的entities.add方法创建一个新的实体,并将它添加到地图上。这个实体用于实时显示用户绘制的多边形。
4,创建一个实体以显示绘制中的线:
let drawingLine = viewer.entities.add({ ... });
创建一个实体来实时显示用户绘制的线。
5,设置鼠标点击事件的监听:
let handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
handler.setInputAction(event => { ... }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
这段代码创建一个新的ScreenSpaceEventHandler实例来监听鼠标和触摸事件。然后设置一个函数来监听左键点击事件。每当用户点击鼠标左键时,这个函数就会被调用,并将点击的位置添加到polygonPoints数组(即多边形的顶点)和allPoints数组。
需要说明的是这一段代码:
//将三维笛卡尔坐标系点转为经纬度坐标点,并保存到点数组中
let cartesian3 = cartesian.clone()
// 使用Cesium.Cartographic.fromCartesian将Cartesian3对象转换为Cartographic对象
let cartographic = Cesium.Cartographic.fromCartesian(cartesian3);
allPoints.push([Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude), cartographic.height]);
加上这一段只是因为,如果要对获取的点数组进行进一步使用,我更习惯使用经纬度坐标
6,设置鼠标移动事件的监听:
handler.setInputAction(event => { ... }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
当用户移动鼠标时,这个函数会被调用。它用于实时更新正在绘制的多边形和线的形状。
7,设置鼠标双击事件的监听:
handler.setInputAction(() => { ... }, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
这个函数监听鼠标左键双击事件。当用户双击鼠标左键时,这个函数会被调用,表示用户完成了多边形的绘制。此时,它会添加最后一个点实体,关闭鼠标事件监听,结束绘制,并通过resolve(allPoints)将绘制的点坐标返回。
8,总说明:
这个方法允许用户通过点击和移动鼠标在Cesium地图上绘制一个多边形。在用户完成绘制(通过双击鼠标左键)后,这个方法通过Promise的resolve函数将绘制的点坐标数组allPoints返回,供其他部分的代码使用。
(比如进行填挖方的计算,将上面的点数组传到地形填挖方计算的方法,用来生成三角网)
(4)拖拽点进行形状修改
绘制完成:
拖拽点:
思路:
在完成绘制后(即在双击事件结束后),初始化一个新的鼠标事件处理器专门用于拖拽操作。在新的事件处理器中,设置监听左键按下、左键抬起和鼠标移动的事件,用于实现拖拽交互。为了在拖动点时停止地图的默认移动事件,您需要在开始拖动事件时暂时禁用默认的摄像机控制,并在拖动结束后再次启用它。
代码:
var pointEntity=viewer.entities.add({
position: cartesian,
point: {
color: Cesium.Color.RED,
pixelSize: 10
}
});
pointEntities.push(pointEntity);
let selectedPointEntity = null;
let selectedIndex = -1;
var dragHandler= new Cesium.ScreenSpaceEventHandler(viewer.canvas);
//鼠标按下
dragHandler.setInputAction(event => {
const pickedObject = viewer.scene.pick(event.position);
if (Cesium.defined(pickedObject) && pointEntities.includes(pickedObject.id)) {
selectedPointEntity = pickedObject.id;
selectedIndex = pointEntities.indexOf(selectedPointEntity);
// 禁用摄像机控制
viewer.scene.screenSpaceCameraController.enableRotate = false;
viewer.scene.screenSpaceCameraController.enableTranslate = false;
viewer.scene.screenSpaceCameraController.enableZoom = false;
viewer.scene.screenSpaceCameraController.enableTilt = false;
viewer.scene.screenSpaceCameraController.enableLook = false;
}
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
// 当鼠标移动时
dragHandler.setInputAction(event => {
if (selectedPointEntity) {
const cartesian = this.getCatesian3FromPX(event.endPosition);
if (cartesian && selectedIndex !== -1) {
selectedPointEntity.position = cartesian;
polygonPoints[selectedIndex] = cartesian;
}
}