需求:在cesium地图上,添加一个标注,点击标注弹窗显示内容,地图拖拉移动,弹窗内容跟随移动,始终保持在标注点未知上方。
实现:如图。
环境:vue、cesium。初始化地图相关代码就略过。
1.加载标注。
async loadMarker(item) {
let self = this;
let height = await this.getMapHeight(item.lng, item.lat)
let marker = self.viewer.entities.add({
clickType: 'marker', // 点击标记,用于分派点击事件
name: item.name,
extra: item,
position: Cesium.Cartesian3.fromDegrees(item.lng, item.lat, height),
billboard: {
image: CESIUM_URL + `/map/img/ddc-icon.png`,
scale: 0.4,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
pixelOffset: new Cesium.Cartesian2(0, 20),
},
click: function (entity, e) {
self.viewer.scene.globe.depthTestAgainstTerrain = true;
if (self.viewer.camera.positionCartographic.height > 90000) {
const camera = self.viewer.scene.camera;
camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(item.lng, item.lat - 0.06, 5000.0),
duration: 5,
orientation: {
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-35.0),
roll: 0.0,
},
complete: function () {
if (self.clickMarker && typeof self.clickMarker === "function") {
self.clickMarker(entity, e, item)
}
}
});
} else {
if (self.clickMarker && typeof self.clickMarker === "function") {
self.clickMarker(entity, e, item)
}
}
},
});
self.markers[item.name] = marker;
}
这里标注点的position,需要带高度,贴地高度之前有文档发过,代码中的click,并不是可以直接注册事件,这个到后面注册事件的时候直接分派。click/clickType/extra这几个字段是我额外添加的。目的就是点击的时候识别到clickType==marker,就直接执行额外添加的click函数。
2.注册点击事件。
// 注册鼠标左点击事件
registerLeftClick() {
let self = this;
let handler = new Cesium.ScreenSpaceEventHandler(self.viewer.canvas);
function leftClickHandler(e) {
// 先判断是否获取到目标要素
let target = self.viewer.scene.pick(e.position);
if (target) {
// 分派业务逻辑
if (!target.id) {
self.mapClick(e);
return;
}
if (target.id.clickType === 'marker') {
// 点击标注
if (typeof target.id.click == "function") {
target.id.click(target.id, e);
}
}
}
}
handler.setInputAction(leftClickHandler, Cesium.ScreenSpaceEventType.LEFT_CLICK);
}
registerLeftClick这个函数会在初始化地图之后调用。
3.vue调用标注添加,注入clickMarker点击逻辑函数。
onMounted(() => {
setTimeout(() => {
map = new CimRander();
map.initMap("home_map_container").then((_viewer: any) => {
viewer.value = _viewer;
_viewer.scene.postRender.addEventListener(updatePopupPosition);
map.loadBoundary();
setTimeout(() => {
map.loadMarker({
name: "marker1",
lng: 114.100807,
lat: 22.663633,
});
}, 1300);
map.clickMarker = (entity: any, e: any, item: any) => {
selectedEntity.data = entity;
selectedEntity.win = layerInfoWin;
layerInfoWin.show = true;
updatePopupPosition();
};
});
}, 10);
});
const layerInfoWin = reactive<any>({
title: "标题",
mj: "34.23亩",
lx: "标注类型",
dw: "点",
show: false,
position: {
left: "-5000px",
top: "-5000px",
},
});
// 更新弹出框位置
const updatePopupPosition = () => {
// 计算弹窗位置
if (!selectedEntity.data) return;
let cesiumPosition: any;
if (selectedEntity.data.position instanceof Cesium.Cartesian3) {
cesiumPosition = selectedEntity.data.position;
} else {
cesiumPosition = selectedEntity.data.position.getValue(
viewer.value.clock.currentTime
);
}
//如果视角和位置都没有变化,直接返回
let camera = viewer.value.camera;
let _thiscache =
cesiumPosition.x +
"=" +
cesiumPosition.y +
"-" +
cesiumPosition.z +
"-" +
camera.positionWC.x +
"=" +
camera.positionWC.y +
"-" +
camera.positionWC.z +
"-" +
camera.heading +
"-" +
camera.pitch +
"-" +
camera.roll;
if (_thiscache == _camera_cache.data) {
return true;
}
_camera_cache.data = _thiscache;
// 这一步是将 3D 世界中的点位转换为 2D 屏幕上的像素位置。
const canvasPosition = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
viewer.value.scene,
cesiumPosition
);
// 检查转换是否成功。有时候,如果点位不在当前视图中,则转换可能失败。
if (canvasPosition) {
// 更新弹窗位置
selectedEntity.win.position.left = canvasPosition.x + 2 + "px";
selectedEntity.win.position.top = canvasPosition.y - 0 + "px";
}
};
代码主要是添加标注点marker1,监听场景渲染变化。调用updatePopupPosition。selectedEntity存点击选中的点。layerInfoWin弹框的信息,主要的是position。操作地图过程中,需要实时更新弹窗的position。
总结,主要目的是为了记录学习过程,代码中的很多变量没有解释,都是比较简单的vue响应式变量,或者是类中的属性。