需求分析
Cesium中的事件监听支持的类型与前端主流的事件有差别(没有mouseover,mouseout等事件),封装一个较为通用的事件管理器
存在问题
- cesium的事件监听是通过创建一个Cesium.ScreenSpaceEventHandler对象,然后调用handler.setInputAction(callback,Cesium.ScreenSpaceEventType.*)来注册某个事件类型的回调,这种实现方式导致了在一个handler中同一个ScreenSpaceEventType只能注册一个回调。
- mouseover和mouseout事件都需要监听MOUSE_MOVE事件,而MOUSE_MOVE事件是注册在地图上的。假如我需要给某些覆盖物设置mouseover事件,如果注册多个handler MOUSE_MOVE那鼠标在地图上移动将触发所有MOUSE_MOVE回调,可能出现稍微动几个像素点触发数千次回调,极大的浪费计算资源。
实现
基于Cesium版本1.95
首先定义一个map记录前端事件与cesium事件的对应关系
static EVENT_MAP = {
"click": Cesium.ScreenSpaceEventType.LEFT_CLICK,
"dblclick": Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK,
"rightclick": Cesium.ScreenSpaceEventType.RIGHT_CLICK,
"mouseover": Cesium.ScreenSpaceEventType.MOUSE_MOVE,
"mouseout": Cesium.ScreenSpaceEventType.MOUSE_MOVE,
"leftdown": Cesium.ScreenSpaceEventType.LEFT_DOWN,
}
一个<事件-覆盖物列表>的map,用于保存注册事件的覆盖物
//Map<事件, Map>
this.eventEntityMap = {};
//初始化Map<实体id, 实体>
if (!this.eventEntityMap[event]) {
this.eventEntityMap[event] = new Map();
}
this.eventEntityMap[event].set(overlay.getEntity().id, overlay);
初始化handler,由于Cesium对InputAction进行移除时无法指定移除对象,所以对一个handler只设置一次InputAction;当第一次注册事件时才去设置InputAction。
this.handler = new Cesium.ScreenSpaceEventHandler(this.map.viewer.scene.canvas);
if (!this.handler.getInputAction(EVENT_MAP[event])) {
switch (event) {
case 'mouseover':
case 'mouseout':
this.handler.setInputAction(e => {
}, EVENT_MAP[event])
break;
default:
this.handler.setInputAction(e => {
}, EVENT_MAP[event])
}
}
mouseover和mouseout事件注册同一个InputAction MOUSE_MOVE,在回调中获取鼠标指针上的覆盖物,触发mouseover事件,此时标记当前移入的覆盖物,如果下次MOUSE_MOVE事件未获取到该鼠标指针上的覆盖物,则触发mouseout事件
var picked = this.map.viewer.scene.pick(e.endPosition);
if (Cesium.defined(picked) && picked.id) {
var pickedOverlay;
//存在上次选中实体
if (this.pickedId) {
//本次选中实体与上次选中实体不同,触发上次选中实体的mouseout事件
if (this.pickedId !== picked.id) {
pickedOverlay = this.eventEntityMap['mouseout'].get(this.pickedId.id)
if (pickedOverlay) {
pickedOverlay._dispatchEvent('mouseout', e)
}
} else {
return;
}
}
//设置选中实体
this.pickedId = picked.id;
pickedOverlay = this.eventEntityMap['mouseover'].get(this.pickedId.id)
if (pickedOverlay) {
pickedOverlay._dispatchEvent('mouseover', e)
}
} else if (this.pickedId) {
pickedOverlay = this.eventEntityMap['mouseout'].get(this.pickedId.id)
if (pickedOverlay) {
pickedOverlay._dispatchEvent('mouseout', e)
}
this.pickedId = undefined;
}
完整代码
/**
* 地图通用事件与cesium事件转换
*/
class CesiumEventManager {
//支持事件
static EVENT_MAP = {
"click": Cesium.ScreenSpaceEventType.LEFT_CLICK,
"dblclick": Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK,
"rightclick": Cesium.ScreenSpaceEventType.RIGHT_CLICK,
"mouseover": Cesium.ScreenSpaceEventType.MOUSE_MOVE,
"mouseout": Cesium.ScreenSpaceEventType.MOUSE_MOVE,
"leftdown": Cesium.ScreenSpaceEventType.LEFT_DOWN,
}
constructor(map) {
this.map = map;
this.handler = new Cesium.ScreenSpaceEventHandler(this.map.viewer.scene.canvas);
//Map<事件, Map>
this.eventEntityMap = {};
}
addEventOverlay(event, overlay) {
//初始化Map<实体id, 实体>
if (!this.eventEntityMap[event]) {
this.eventEntityMap[event] = new Map();
}
this.eventEntityMap[event].set(overlay.getEntity().id, overlay);
//初始化事件action
if (!this.handler.getInputAction(CesiumEventManager.EVENT_MAP[event])) {
switch (event) {
case 'mouseover':
case 'mouseout':
this.handler.setInputAction(e => {
var picked = this.map.viewer.scene.pick(e.endPosition);
if (Cesium.defined(picked) && picked.id) {
var pickedOverlay;
//存在上次选中实体
if (this.pickedId) {
//本次选中实体与上次选中实体不同,触发上次选中实体的mouseout事件
if (this.pickedId !== picked.id) {
pickedOverlay = this.eventEntityMap['mouseout'].get(this.pickedId.id)
if (pickedOverlay) {
pickedOverlay._dispatchEvent('mouseout', e)
}
} else {
return;
}
}
//设置选中实体
this.pickedId = picked.id;
pickedOverlay = this.eventEntityMap['mouseover'].get(this.pickedId.id)
if (pickedOverlay) {
pickedOverlay._dispatchEvent('mouseover', e)
}
} else if (this.pickedId) {
pickedOverlay = this.eventEntityMap['mouseout'].get(this.pickedId.id)
if (pickedOverlay) {
pickedOverlay._dispatchEvent('mouseout', e)
}
this.pickedId = undefined;
}
}, CesiumEventManager.EVENT_MAP[event])
break;
default:
this.handler.setInputAction(e => {
var picked = this.map.viewer.scene.pick(e.position);
if (Cesium.defined(picked) && picked.id) {
var pickedOverlay = this.eventEntityMap[event].get(picked.id.id)
if (pickedOverlay) {
pickedOverlay._dispatchEvent(event, e)
}
}
}, CesiumEventManager.EVENT_MAP[event])
}
}
}
removeEventOverlay(event, overlay) {
//移除注册实体
if (this.eventEntityMap[event]) {
this.eventEntityMap[event].delete(overlay.getEntity().id);
}
//无注册实体,清除对应事件
if (this.eventEntityMap[event].size === 0) {
this.handler.removeInputAction(CesiumEventManager.EVENT_MAP[event])
}
}
}