無論是前端系統(tǒng),還是二維/三維GIS應用系統(tǒng),都離不開各種事件的應用,尤其是鼠標的單擊、雙擊事件。 Cesium 根據(jù)事件的類型、用途,將事件應用分成了三大類。一種是以鼠標操作(左鍵、中鍵、右鍵操作等)為主的 ScreenSpaceEventHandler 類,另一種是通用的事件類 Event ,該類通常在容器類內部實例化,并作為某個屬性的類型直接被調用,比如 viewer.clock.onTick、viewer.selectedEntityChanged、camera.moveStart、camera.moveEnd、scene.preRender、cesium3DTileset.allTilesLoaded等這些屬性都是 Event 類型;最后一種則是相機控制方面的事件類 screenSpaceCameraController,該類通過與CameraEventType類配合實現(xiàn)相機的控制。下面我主要介紹幾個比較常用的事件應用。
鼠標事件
鼠標事件可以說是GIS系統(tǒng)里面關于事件應用最常用的一個了,點擊地圖上的某一個 graphic,并獲取其屬性信息,就是鼠標事件應用最熟悉的一個場景了。Cesium為實現(xiàn)這一功能,分成了兩個過程。首先,傳遞viewer.canvas參數(shù)實例化ScreenSpaceEventHandler類,比如實例化后的名稱為handler;其次,為 handler 注冊鼠標事件的監(jiān)聽;最后,在監(jiān)聽事件的回調方法中獲取 event.position ,并將其作為參數(shù)執(zhí)行scene.pick 方法獲取對應的選中對象。
1)ScreenSpaceEventHandler
對 ScreenSpaceEventHandler 類進行實例化,注冊事件、注銷事件代碼如下:
var handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
let eventType = Cesium.ScreenSpaceEventType.LEFT_CLICK;
//注冊事件
handler.setInputAction((event) => {
console.log(event);
}, eventType);
//注銷事件
handler.removeInputAction(eventType);
上面代碼中的事件類型 eventType 直接采用了 ScreenSpaceEventType 中的常量,示例中注冊鼠標左擊事件。根據(jù)實際場景需求,eventType 的值可按需賦值,包括以下幾種:


2)要素拾取
假如應用場景是點擊要素獲取其屬性信息,這個時候就需要在鼠標左鍵的注冊事件中獲取 event 結果,核心代碼如下:
var picked = viewer.scene.pick(event.position);
這個時候就可以根據(jù)獲取到的對象類型進行操作了。
if (Cesium.defined(picked)) {
if (picked.id && picked.id instanceof Cesium.Entity) {
console.log("選中了Entity");
}
if (picked.primitive instanceof Cesium.Primitive) {
console.log("選中了Primitive");
}
if (picked.primitive instanceof Cesium.Model) {
console.log("選中了模型");
}
if (picked instanceof Cesium.Cesium3DTileFeature) {
console.log("選中了3DTile");
}
}
3)Entity選擇
Cesium 針對于通過 Entity 方式添加的幾何圖形,提供了一個非常方便的屬性selectedEntityChanged(viewer類事件類型的屬性)來幫助我們獲取選中的Entity,通過這個屬性,用戶無需再寫注冊鼠標事件了。示例代碼如下:
viewer.selectedEntityChanged.addEventListener(function (entity) {
console.log(entity.id);
});
在某些場景中,我們可能需要跟蹤某一輛車或某一個人員,這是我們可以把車輛或人員Entity賦給viewer.trackedEntity,相機就會自動跟蹤你綁定的Entity了。實際場景中,我們并不是始終跟蹤某一個車輛,有時需要切換到另一個車輛,當你切換正在跟蹤的車輛時,其實我們觸發(fā)了viewer.trackedEntityChanged事件,這樣我們就可以在此事件中實時獲取車輛行駛狀態(tài)了。
viewer.trackedEntityChanged.addEventListener(function (entity) {
console.log(entity.id);
});
相機事件
相機控制事件類 screenSpaceCameraController 并不是像鼠標事件相關類 ScreenSpaceEventHandler 那樣需要提前實例化。Cesium在Viewer類的實例化過程中,也實例化了其他很多類,其中就包括ScreenSpaceCameraController類,并把實例化結果賦值給了viewer.scene.screenSpaceCameraController。所以,我們直接去操作viewer.scene.screenSpaceCameraController就可以了。
1)通過鼠標控制
通過鼠標控制相機的方式取決于CameraEventType的常量,包括以下幾種:

其中,鼠標的默認操作如下:

下面我們修改默認的鼠標操作,實現(xiàn)中鍵縮放、右鍵旋轉。核心代碼如下:
viewer.scene.screenSpaceCameraController.tiltEventTypes = [
Cesium.CameraEventType.RIGHT_DRAG,
Cesium.CameraEventType.PINCH,
{
eventType: Cesium.CameraEventType.LEFT_DRAG,
modifier: Cesium.KeyboardEventModifier.CTRL,
},
{
eventType: Cesium.CameraEventType.RIGHT_DRAG,
modifier: Cesium.KeyboardEventModifier.CTRL,
},
];
viewer.scene.screenSpaceCameraController.zoomEventTypes = [
Cesium.CameraEventType.MIDDLE_DRAG,
Cesium.CameraEventType.WHEEL,
Cesium.CameraEventType.PINCH,
];
2)通過鍵盤控制
主要是通過操作鍵盤實現(xiàn)相機的漫游,比如前進、后退、向上、向下等等,是不是感覺自己在玩穿越火線游戲,響起熟悉的聲音:headshot、double kill、multi kill、fire in the hole、......,打住?。。】顸c鼠標也不會發(fā)射子彈的。好了,我們把感覺拉回來現(xiàn)場,繼續(xù)學習Cesium。實現(xiàn)鍵盤漫游主要是通過鍵盤調用相機的moveForward、moveBackward、moveLeft、moveRight、moveUp、moveDown方法。下面為部分核心代碼,查看完整代碼請瀏覽GitHub地址https://github.com/ls870061011/cesium_training/tree/main/examples中的3_2部分。
viewer.clock.onTick.addEventListener(function (clock) {
var camera = viewer.camera;
if (flags.looking) {
var width = canvas.clientWidth;
var height = canvas.clientHeight;
// Coordinate (0.0, 0.0) will be where the mouse was clicked.
var x = (mousePosition.x - startMousePosition.x) / width;
var y = -(mousePosition.y - startMousePosition.y) / height;
var lookFactor = 0.05;
camera.lookRight(x * lookFactor);
camera.lookUp(y * lookFactor);
}
// Change movement speed based on the distance of the camera to the surface of the ellipsoid.
var cameraHeight = ellipsoid.cartesianToCartographic(camera.position)
.height;
var moveRate = cameraHeight / 100.0;
if (flags.moveForward) {
camera.moveForward(moveRate);
}
if (flags.moveBackward) {
camera.moveBackward(moveRate);
}
if (flags.moveUp) {
camera.moveUp(moveRate);
}
if (flags.moveDown) {
camera.moveDown(moveRate);
}
if (flags.moveLeft) {
camera.moveLeft(moveRate);
}
if (flags.moveRight) {
camera.moveRight(moveRate);
}
});
場景渲染事件
場景渲染事件主要包括以下四種:
- scene.preUpdate: 更新或呈現(xiàn)場景之前將引發(fā)的事件
- scene.postUpdate: 場景更新后以及渲染場景之前立即引發(fā)的事件
- scene.preRender: 場景更新后以及渲染場景之前將引發(fā)的事件
-
scene.postRender: 渲染場景后立即引發(fā)的事件
事件的添加和移除代碼示例如下:
viewer.scene.preUpdate.addEventListender(callbackFunc);
viewer.scene.preUpdate.removeEventListender(callbackFunc);
比如我們自己寫了一個指北針、標簽,都可以在scene.preRender監(jiān)聽事件的回調函數(shù)中更新指北針狀態(tài)或者是標簽的位置信息。下面的部分核心代碼,為場景重新選然后更新自定義標簽位置。
viewer.scene.scene-preRender.addEventListener(() => {
if (positions instanceof Array && htmlSize instanceof Array) {
positions.map((ele, index) => {
const html = document.getElementById(`infoTip${index}`);
if (html) {
const canvasPosition = ConversionUtil.degreesToCartesian2(ele.x, ele.y, ele.z);
if (canvasPosition) {
html.style.top = `${canvasPosition.y - htmlSize[index].offsetHeight}px`;
html.style.left = `${canvasPosition.x - htmlSize[index].offsetWidth}px`;
}
}
});
}
)