feat(map): 为地图标记添加交互式工具提示
- 实现 `useMapTooltip` 可组合函数以管理工具提示状态 - 添加点击处理程序,在标记交互时显示工具提示 - 将标记高度偏移调整为 100 米以保持一致性 - 强制场景渲染,确保 `CLAMP_TO_GROUND` 效果立即生效
This commit is contained in:
parent
f60ecb002c
commit
a1397c591f
@ -7,8 +7,8 @@ import soldierIcon from '../assets/images/SketchPngfbec927027ff9e49207749ebaafd2
|
||||
import deviceIcon from '../assets/images/SketchPng860d54f2a31f5f441fc6a88081224f1e98534bf6d5ca1246e420983bdf690380.png'
|
||||
import emergencyBaseIcon from '../assets/images/应急基地.png'
|
||||
|
||||
// 默认高度偏移(米)
|
||||
const DEFAULT_HEIGHT_OFFSET = 10
|
||||
// 默认高度偏移(米)- 与 WuRenJi 保持一致
|
||||
const DEFAULT_HEIGHT_OFFSET = 100
|
||||
|
||||
/**
|
||||
* 地图标记管理 Composable
|
||||
@ -136,9 +136,18 @@ export function useMapMarkers() {
|
||||
// 计算中心点
|
||||
const center = Cesium.BoundingSphere.fromPoints(positions).center
|
||||
|
||||
// 将中心点转换为经纬度,然后重新创建位置,确保高度为0
|
||||
// 这样 CLAMP_TO_GROUND 才能正确工作
|
||||
const centerCartographic = Cesium.Cartographic.fromCartesian(center)
|
||||
const centerPosition = Cesium.Cartesian3.fromRadians(
|
||||
centerCartographic.longitude,
|
||||
centerCartographic.latitude,
|
||||
0 // 高度设为0,让 CLAMP_TO_GROUND 自动贴地
|
||||
)
|
||||
|
||||
// 添加标签
|
||||
const labelEntity = viewer.entities.add({
|
||||
position: center,
|
||||
position: centerPosition,
|
||||
label: {
|
||||
text: '模拟塌陷区域',
|
||||
font: '18px "Microsoft YaHei", sans-serif',
|
||||
@ -158,7 +167,7 @@ export function useMapMarkers() {
|
||||
|
||||
// 添加中心点标记
|
||||
const pointEntity = viewer.entities.add({
|
||||
position: center,
|
||||
position: centerPosition,
|
||||
point: {
|
||||
color: Cesium.Color.ORANGE,
|
||||
pixelSize: 12,
|
||||
@ -171,6 +180,10 @@ export function useMapMarkers() {
|
||||
|
||||
collapseAreaEntities.value = entities
|
||||
console.log('[useMapMarkers] 塌陷区域绘制完成')
|
||||
|
||||
// 强制渲染场景,确保 CLAMP_TO_GROUND 立即生效
|
||||
viewer.scene.requestRender()
|
||||
|
||||
return center
|
||||
}
|
||||
|
||||
@ -291,6 +304,10 @@ export function useMapMarkers() {
|
||||
|
||||
markerEntities.value.push(...entities)
|
||||
console.log(`[useMapMarkers] 添加固定标记 ${entities.length} 个`)
|
||||
|
||||
// 强制渲染场景,确保 CLAMP_TO_GROUND 立即生效
|
||||
viewer.scene.requestRender()
|
||||
|
||||
return entities
|
||||
}
|
||||
|
||||
@ -387,6 +404,10 @@ export function useMapMarkers() {
|
||||
|
||||
markerEntities.value.push(...entities)
|
||||
console.log(`[useMapMarkers] 添加随机标记 ${entities.length} 个`)
|
||||
|
||||
// 强制渲染场景,确保 CLAMP_TO_GROUND 立即生效
|
||||
viewer.scene.requestRender()
|
||||
|
||||
return entities
|
||||
}
|
||||
|
||||
@ -627,6 +648,11 @@ export function useMapMarkers() {
|
||||
|
||||
emergencyResourceEntities.value = entities
|
||||
console.log(`[useMapMarkers] 添加养护站标记 ${entities.length} 个`)
|
||||
|
||||
// 强制渲染场景,确保 CLAMP_TO_GROUND 立即生效
|
||||
if (entities.length > 0) {
|
||||
viewer.scene.requestRender()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
/**
|
||||
* 地图 Tooltip 状态管理
|
||||
* 用于显示地图标记点的轻量级信息提示框
|
||||
*/
|
||||
export function useMapTooltip() {
|
||||
// Tooltip 状态
|
||||
const tooltipState = ref({
|
||||
visible: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
title: '',
|
||||
icon: '',
|
||||
zIndex: 20,
|
||||
data: null // 业务数据,用于内容插槽渲染
|
||||
})
|
||||
|
||||
/**
|
||||
* 显示 Tooltip
|
||||
* @param {Object} options - Tooltip 配置选项
|
||||
* @param {number} options.x - 屏幕 X 坐标(像素)
|
||||
* @param {number} options.y - 屏幕 Y 坐标(像素)
|
||||
* @param {string} [options.title=''] - Tooltip 标题文本
|
||||
* @param {string} [options.icon=''] - 标题左侧图标的图片路径
|
||||
* @param {Object} [options.data=null] - 业务数据
|
||||
*/
|
||||
const showTooltip = ({ x, y, title = '', icon = '', data = null }) => {
|
||||
tooltipState.value = {
|
||||
visible: true,
|
||||
x,
|
||||
y,
|
||||
title,
|
||||
icon,
|
||||
zIndex: 20,
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏 Tooltip
|
||||
*/
|
||||
const hideTooltip = () => {
|
||||
tooltipState.value.visible = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Tooltip 位置
|
||||
* @param {number} x - 屏幕 X 坐标
|
||||
* @param {number} y - 屏幕 Y 坐标
|
||||
*/
|
||||
const updateTooltipPosition = (x, y) => {
|
||||
tooltipState.value.x = x
|
||||
tooltipState.value.y = y
|
||||
}
|
||||
|
||||
return {
|
||||
tooltipState,
|
||||
showTooltip,
|
||||
hideTooltip,
|
||||
updateTooltipPosition
|
||||
}
|
||||
}
|
||||
@ -111,11 +111,15 @@ import { useDisasterData } from "./composables/useDisasterData";
|
||||
import { useDualMapCompare } from "./composables/useDualMapCompare";
|
||||
import { useMapMarkers } from "./composables/useMapMarkers";
|
||||
import { use3DTiles } from "./composables/use3DTiles";
|
||||
import { useMapTooltip } from "./composables/useMapTooltip";
|
||||
import { useMapStore } from "@/map";
|
||||
import { request } from "@shared/utils/request";
|
||||
|
||||
// 标记点
|
||||
// 标记点图标
|
||||
import emergencyCenterIcon from "./assets/images/应急中心.png";
|
||||
import soldierIcon from "./assets/images/SketchPngfbec927027ff9e49207749ebaafd229429315341fda199251b6dfb1723ff17fb.png";
|
||||
import deviceIcon from "./assets/images/SketchPng860d54f2a31f5f441fc6a88081224f1e98534bf6d5ca1246e420983bdf690380.png";
|
||||
import emergencyBaseIcon from "./assets/images/应急基地.png";
|
||||
|
||||
// 使用灾害数据
|
||||
const disasterData = useDisasterData();
|
||||
@ -150,9 +154,182 @@ const {
|
||||
clearEmergencyResourceMarkers,
|
||||
} = useMapMarkers();
|
||||
|
||||
// 地图 Tooltip 功能
|
||||
const { tooltipState: mapTooltip, showTooltip, hideTooltip, updateTooltipPosition } = useMapTooltip();
|
||||
|
||||
// 当前显示 tooltip 的实体(用于相机移动时更新位置)
|
||||
const currentTooltipEntity = ref(null);
|
||||
|
||||
// 3D Tiles加载功能
|
||||
const { load3DTileset, waitForTilesetReady } = use3DTiles();
|
||||
|
||||
/**
|
||||
* 设置地图点击事件处理器
|
||||
* 当用户点击地图标记点时,显示 Tooltip
|
||||
*/
|
||||
const setupMapClickHandler = (viewer) => {
|
||||
if (!viewer) return;
|
||||
|
||||
// 创建 ScreenSpaceEventHandler 监听点击事件
|
||||
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
|
||||
|
||||
handler.setInputAction((click) => {
|
||||
// 获取点击位置的实体
|
||||
const pickedObject = viewer.scene.pick(click.position);
|
||||
|
||||
if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
|
||||
const entity = pickedObject.id;
|
||||
|
||||
// 检查实体是否有 properties(标记点才有)
|
||||
if (entity.properties) {
|
||||
const type = entity.properties.type?.getValue();
|
||||
|
||||
// 根据标记类型显示不同的 Tooltip
|
||||
if (type === 'soldier') {
|
||||
showMarkerTooltip(viewer, entity, click.position, soldierIcon);
|
||||
} else if (type === 'device') {
|
||||
showMarkerTooltip(viewer, entity, click.position, deviceIcon);
|
||||
} else if (type === 'emergencyBase' || type === 'station') {
|
||||
showMarkerTooltip(viewer, entity, click.position, emergencyBaseIcon);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 点击空白区域,隐藏 Tooltip
|
||||
hideTooltip();
|
||||
currentTooltipEntity.value = null;
|
||||
}
|
||||
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
||||
|
||||
// 监听相机移动事件,更新 tooltip 位置
|
||||
viewer.scene.postRender.addEventListener(() => {
|
||||
if (currentTooltipEntity.value && mapTooltip.value.visible) {
|
||||
updateTooltipPositionForEntity(viewer, currentTooltipEntity.value);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 显示标记点 Tooltip
|
||||
* @param {Cesium.Viewer} viewer - Cesium viewer 实例
|
||||
* @param {Cesium.Entity} entity - 被点击的实体
|
||||
* @param {Cesium.Cartesian2} screenPosition - 点击的屏幕坐标(备用)
|
||||
* @param {string} icon - 图标路径
|
||||
*/
|
||||
const showMarkerTooltip = (viewer, entity, screenPosition, icon) => {
|
||||
const properties = entity.properties;
|
||||
const type = properties.type?.getValue();
|
||||
|
||||
// 获取实体的 3D 位置
|
||||
const position = entity.position?.getValue(Cesium.JulianDate.now());
|
||||
|
||||
if (!position) {
|
||||
console.warn('[Tooltip] 无法获取实体位置');
|
||||
return;
|
||||
}
|
||||
|
||||
// 对于使用 CLAMP_TO_GROUND 的 billboard,需要获取实际的地形高度
|
||||
const cartographic = Cesium.Cartographic.fromCartesian(position);
|
||||
let clampedPosition = position;
|
||||
|
||||
// 使用 globe.getHeight 获取地形高度
|
||||
if (viewer.scene.globe) {
|
||||
const height = viewer.scene.globe.getHeight(cartographic);
|
||||
if (Cesium.defined(height)) {
|
||||
cartographic.height = height;
|
||||
clampedPosition = Cesium.Cartographic.toCartesian(cartographic);
|
||||
}
|
||||
}
|
||||
|
||||
// 将贴地后的 3D 坐标转换为屏幕坐标
|
||||
const canvasPosition = viewer.scene.cartesianToCanvasCoordinates(clampedPosition);
|
||||
|
||||
if (!Cesium.defined(canvasPosition)) {
|
||||
console.warn('[Tooltip] 无法转换坐标到屏幕位置');
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建 Tooltip 数据
|
||||
let title = '';
|
||||
const fields = [];
|
||||
|
||||
if (type === 'soldier') {
|
||||
title = '单兵信息';
|
||||
fields.push(
|
||||
{ label: '姓名', value: properties.name?.getValue() || '-' },
|
||||
{ label: '部门', value: properties.department?.getValue() || '-' },
|
||||
{ label: '位置', value: properties.location?.getValue() || '-' }
|
||||
);
|
||||
} else if (type === 'device') {
|
||||
title = '设备信息';
|
||||
fields.push(
|
||||
{ label: '设备名称', value: properties.name?.getValue() || '-' },
|
||||
{ label: '设备类型', value: properties.deviceType?.getValue() || '-' },
|
||||
{ label: '位置', value: properties.location?.getValue() || '-' }
|
||||
);
|
||||
} else if (type === 'emergencyBase') {
|
||||
title = '应急基地';
|
||||
fields.push(
|
||||
{ label: '名称', value: properties.name?.getValue() || '-' },
|
||||
{ label: '地址', value: properties.address?.getValue() || '-' },
|
||||
{ label: '距离', value: properties.distance?.getValue() || '-' }
|
||||
);
|
||||
} else if (type === 'station') {
|
||||
title = '养护站';
|
||||
fields.push(
|
||||
{ label: '名称', value: properties.name?.getValue() || '-' },
|
||||
{ label: '距离', value: `${properties.distance?.getValue() || 0}公里` }
|
||||
);
|
||||
}
|
||||
|
||||
// 显示 Tooltip,使用实体的屏幕坐标
|
||||
showTooltip({
|
||||
x: canvasPosition.x,
|
||||
y: canvasPosition.y,
|
||||
title,
|
||||
icon,
|
||||
data: { fields }
|
||||
});
|
||||
|
||||
// 保存当前实体,用于相机移动时更新位置
|
||||
currentTooltipEntity.value = entity;
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新 Tooltip 位置(当相机移动时)
|
||||
* @param {Cesium.Viewer} viewer - Cesium viewer 实例
|
||||
* @param {Cesium.Entity} entity - 实体对象
|
||||
*/
|
||||
const updateTooltipPositionForEntity = (viewer, entity) => {
|
||||
const position = entity.position?.getValue(Cesium.JulianDate.now());
|
||||
|
||||
if (!position) return;
|
||||
|
||||
// 对于使用 CLAMP_TO_GROUND 的 billboard,需要获取实际的地形高度
|
||||
const cartographic = Cesium.Cartographic.fromCartesian(position);
|
||||
let clampedPosition = position;
|
||||
|
||||
// 使用 globe.getHeight 获取地形高度
|
||||
if (viewer.scene.globe) {
|
||||
const height = viewer.scene.globe.getHeight(cartographic);
|
||||
if (Cesium.defined(height)) {
|
||||
cartographic.height = height;
|
||||
clampedPosition = Cesium.Cartographic.toCartesian(cartographic);
|
||||
}
|
||||
}
|
||||
|
||||
// 将贴地后的 3D 坐标转换为屏幕坐标
|
||||
const canvasPosition = viewer.scene.cartesianToCanvasCoordinates(clampedPosition);
|
||||
|
||||
// 如果标记点在视野外,隐藏 tooltip
|
||||
if (!Cesium.defined(canvasPosition)) {
|
||||
hideTooltip();
|
||||
currentTooltipEntity.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
updateTooltipPosition(canvasPosition.x, canvasPosition.y);
|
||||
};
|
||||
|
||||
// 初始化地图
|
||||
onMounted(() => {
|
||||
// 等待地图就绪后配置初始视图和模型对比图层
|
||||
@ -197,6 +374,10 @@ onMounted(() => {
|
||||
...DEFAULT_CAMERA_VIEW,
|
||||
duration: 1,
|
||||
});
|
||||
|
||||
// 设置地图点击事件监听 - 显示 Tooltip
|
||||
setupMapClickHandler(viewer);
|
||||
|
||||
// 延迟 1000ms 后设置相机到默认位置
|
||||
// setTimeout(() => {
|
||||
// camera.flyTo({
|
||||
@ -204,7 +385,7 @@ onMounted(() => {
|
||||
// duration: 1,
|
||||
// });
|
||||
// }, 5000);
|
||||
return;
|
||||
// return;
|
||||
|
||||
/**
|
||||
* 设置相机到指定的笛卡尔坐标
|
||||
@ -265,7 +446,7 @@ onMounted(() => {
|
||||
console.log("[index.vue] 开始初始化地图标记...");
|
||||
const sampledCollapseCenter = await initializeMarkers(viewer, {
|
||||
useSampledHeights: true, // 使用采样高度,确保标记位置准确
|
||||
heightOffset: 10, // 标记相对地面10米
|
||||
heightOffset: 100, // 标记相对地面100米(与 WuRenJi 保持一致)
|
||||
});
|
||||
|
||||
// 如果之前没有设置相机(配置数据缺失),现在再次尝试
|
||||
@ -409,20 +590,6 @@ const selectedCenter = ref({
|
||||
image: null,
|
||||
});
|
||||
|
||||
/**
|
||||
* 地图 Tooltip 状态管理
|
||||
* 用于显示地图标记点的轻量级信息提示框
|
||||
*/
|
||||
const mapTooltip = ref({
|
||||
visible: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
title: "",
|
||||
icon: "",
|
||||
zIndex: 20, // 高于地图控件层,低于全屏弹窗
|
||||
data: null, // 业务数据,用于内容插槽渲染
|
||||
});
|
||||
|
||||
// 返回驾驶舱
|
||||
const handleBack = () => {
|
||||
console.log("返回驾驶舱");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user