feat(3d-situational-awareness): 添加坍塌边界可视化和相机同步改进

- 添加新的collapseBoundary.js配置文件,包含灾难坍塌边界的笛卡尔坐标
- 实现在左右两侧地图上绘制蓝色实线和虚线的坍塌边界
- 更新useDisasterData中的挖掘机计算,使用forcePreset中的设备而非公式
- 增强useDualMapCompare,改进相机同步、查看器实例存储和清理功能
- 修改index.vue以支持异步标记加载、储备中心的可选完整数据加载以及场景初始化中的边界绘制
This commit is contained in:
Zzc 2025-11-25 19:21:38 +08:00
parent 00468b053b
commit 037d1257ba
4 changed files with 291 additions and 33 deletions

View File

@ -111,7 +111,10 @@ export function useDisasterData() {
bases: forcePreset.value.bases,
// 挖掘机建议数
excavators: Math.max(1, Math.ceil(stationsCount / 2)),
// excavators: Math.max(1, Math.ceil(stationsCount / 2)),
// 应急装备
excavators: forcePreset.value.equipment,
// 阻断信息:固定建议
blockInfo: '需发布',

View File

@ -75,6 +75,9 @@ export function useDualMapCompare() {
// viewer.scene.screenSpaceCameraController.enableTilt = false
// viewer.scene.screenSpaceCameraController.enableLook = false
// 将 viewer 实例存储到容器上,供其他模块访问
container._cesiumViewer = viewer
leftViewer.value = viewer
console.log('[useDualMapCompare] 左侧Viewer初始化成功交互已启用') // 关键修改
@ -261,6 +264,32 @@ export function useDualMapCompare() {
// 如果只是加载左侧模型Viewer已存在
if (loadLeftModel && leftViewer.value) {
console.log('[useDualMapCompare] 加载左侧灾前模型...')
// 在加载模型前,确保左侧相机位置与右侧同步
if (rightViewerInstance && leftViewer.value) {
console.log('[useDualMapCompare] 同步相机位置(延迟加载场景)...')
const rightCamera = rightViewerInstance.camera
try {
leftViewer.value.camera.setView({
destination: rightCamera.position.clone(),
orientation: {
heading: rightCamera.heading,
pitch: rightCamera.pitch,
roll: rightCamera.roll
}
})
// 强制更新
leftViewer.value.scene.requestRender()
leftViewer.value.camera.changed.raiseEvent()
console.log('[useDualMapCompare] 相机位置同步完成')
} catch (error) {
console.error('[useDualMapCompare] 相机同步失败:', error)
}
}
try {
const tileset = await load3DTileset(leftViewer.value, 'before', false)
if (tileset) {
@ -321,9 +350,24 @@ export function useDualMapCompare() {
throw error
}
// 立即同步右侧相机的当前位置到左侧
// 等待左侧 viewer 完全初始化
await new Promise(resolve => setTimeout(resolve, 100))
// 立即同步右侧相机的当前位置到左侧(关键修复)
console.log('[useDualMapCompare] 同步初始相机位置...')
const rightCamera = rightViewerInstance.camera
// 记录当前右侧相机状态
console.log('[useDualMapCompare] 右侧相机位置:', {
position: rightCamera.position,
heading: Cesium.Math.toDegrees(rightCamera.heading),
pitch: Cesium.Math.toDegrees(rightCamera.pitch),
roll: Cesium.Math.toDegrees(rightCamera.roll)
})
// 强制同步相机位置(使用多种方式确保成功)
try {
// 方式1: 使用 setView (推荐)
leftViewerInstance.camera.setView({
destination: rightCamera.position.clone(),
orientation: {
@ -333,6 +377,30 @@ export function useDualMapCompare() {
}
})
// 方式2: 直接设置相机属性 (备份方案)
leftViewerInstance.camera.position = rightCamera.position.clone()
leftViewerInstance.camera.direction = rightCamera.direction.clone()
leftViewerInstance.camera.up = rightCamera.up.clone()
leftViewerInstance.camera.right = rightCamera.right.clone()
console.log('[useDualMapCompare] 初始相机同步成功')
// 验证同步结果
const leftCamera = leftViewerInstance.camera
console.log('[useDualMapCompare] 左侧相机位置:', {
position: leftCamera.position,
heading: Cesium.Math.toDegrees(leftCamera.heading),
pitch: Cesium.Math.toDegrees(leftCamera.pitch),
roll: Cesium.Math.toDegrees(leftCamera.roll)
})
} catch (error) {
console.error('[useDualMapCompare] 初始相机同步失败:', error)
}
// 强制渲染左侧场景
leftViewerInstance.scene.requestRender()
leftViewerInstance.camera.changed.raiseEvent()
// 设置相机同步(单向:右→左)
setupCameraSync(rightViewerInstance, leftViewerInstance)
@ -396,6 +464,13 @@ export function useDualMapCompare() {
console.log('[useDualMapCompare] 左侧Viewer已销毁')
}
// 清理容器上的 viewer 引用
const leftContainer = document.getElementById('leftCesiumContainer')
if (leftContainer && leftContainer._cesiumViewer) {
delete leftContainer._cesiumViewer
console.log('[useDualMapCompare] 已清理容器上的 viewer 引用')
}
// 触发右侧viewer resize恢复全屏
if (rightViewer.value && !rightViewer.value.isDestroyed()) {
setTimeout(() => {

View File

@ -0,0 +1,121 @@
/**
* 灾后坍塌边界Cartesian3坐标数据
* 用于绘制蓝色实线边界
*/
export const collapseBoundaryData = [
{
x: -1706347.5312101133,
y: 5248165.078710412,
z: 3187394.127873529
},
{
x: -1706350.6376697333,
y: 5248165.028669288,
z: 3187394.2121145395
},
{
x: -1706352.8364036318,
y: 5248165.537099652,
z: 3187392.3620141866
},
{
x: -1706354.4533845682,
y: 5248165.6287691165,
z: 3187390.116655845
},
{
x: -1706355.2233232812,
y: 5248165.6021544365,
z: 3187387.277126487
},
{
x: -1706354.7432117031,
y: 5248164.178150934,
z: 3187384.824506141
},
{
x: -1706352.4841280153,
y: 5248163.731050433,
z: 3187383.6322611645
},
{
x: -1706351.712005713,
y: 5248162.186013809,
z: 3187381.647760827
},
{
x: -1706350.324299354,
y: 5248162.767355746,
z: 3187380.3936922415
},
{
x: -1706348.0854380748,
y: 5248165.163894288,
z: 3187379.1822132985
},
{
x: -1706346.0893322548,
y: 5248165.439709174,
z: 3187377.6419475204
},
{
x: -1706343.2634703086,
y: 5248165.840835863,
z: 3187378.9524312993
},
{
x: -1706341.9044425671,
y: 5248167.062865315,
z: 3187377.660352469
},
{
x: -1706339.8929941722,
y: 5248167.694347973,
z: 3187378.3585356465
},
{
x: -1706338.307401523,
y: 5248167.350063955,
z: 3187379.80788505
},
{
x: -1706338.0093617737,
y: 5248167.209763139,
z: 3187381.8907824843
},
{
x: -1706339.5459232442,
y: 5248166.094283297,
z: 3187382.861357757
},
{
x: -1706339.2268297782,
y: 5248165.94099689,
z: 3187385.2711631004
},
{
x: -1706340.8980982322,
y: 5248162.72254414,
z: 3187386.811143851
},
{
x: -1706342.4054570391,
y: 5248163.2478962615,
z: 3187387.4919083416
},
{
x: -1706342.8948843458,
y: 5248163.095840862,
z: 3187390.2277356824
},
{
x: -1706344.3551372418,
y: 5248163.910523915,
z: 3187392.2502082493
},
{
x: -1706346.0369703155,
y: 5248164.550006937,
z: 3187393.6480220454
}
]

View File

@ -284,6 +284,9 @@ import soldierIcon from './assets/images/SketchPngfbec927027ff9e49207749ebaafd22
import deviceIcon from './assets/images/SketchPng860d54f2a31f5f441fc6a88081224f1e98534bf6d5ca1246e420983bdf690380.png'
import emergencyBaseIcon from './assets/images/应急基地.png'
import reserveCenterIcon from './assets/images/储备中心.png'
import cityEmergencyIcon from './assets/images/市应急点.png'
import districtEmergencyIcon from './assets/images/区县应急点.png'
import otherEmergencyIcon from './assets/images/其他应急点.png'
import mediaIcon from './assets/images/media.png'
import collapseLeftArrow from './assets/images/折叠面板左箭头.png'
import collapseRightArrow from './assets/images/折叠面板右箭头.png'
@ -329,6 +332,11 @@ const {
markerEntities,
reserveCenterEntities,
emergencyResourceEntities,
drawCollapseBoundary,
drawCollapseBoundaryLeft,
clearCollapseBoundary,
showCollapseBoundary,
hideCollapseBoundary,
} = useMapMarkers()
// 3D Tiles
@ -366,6 +374,9 @@ const mapClickHandler = useMapClickHandler({
emergencyCenterIcon,
emergencyBaseIcon,
reserveCenterIcon,
cityEmergencyIcon,
districtEmergencyIcon,
otherEmergencyIcon,
},
rangeCircleEntity,
})
@ -631,8 +642,8 @@ const handleForcePresetToggle = async () => {
console.log('[index.vue] 已关闭地图对比模式')
}
// 2.
showAllMarkersAndRange()
// 2.
await showAllMarkersAndRange()
// 3.
flyToBestViewForMarkers()
@ -705,7 +716,7 @@ const handleDistanceChange = async (newDistance) => {
/**
* 显示所有标记点和范围圈
*/
const showAllMarkersAndRange = () => {
const showAllMarkersAndRange = async () => {
const viewer = mapStore.viewer
if (!viewer) return
@ -730,15 +741,37 @@ const showAllMarkersAndRange = () => {
console.log('[index.vue] 已清除其他标记')
// 2. /
reserveCenterEntities.value.forEach((entity) => {
if (entity) {
entity.show = true
}
})
console.log(`[index.vue] 显示 ${reserveCenterEntities.value.length} 个储备中心/预置点标记`)
// 2. /
console.log('[index.vue] 加载全部储备中心/预置点到地图...')
await loadReserveCentersAndPresets(DISASTER_CENTER.lon, DISASTER_CENTER.lat, true)
// 3.
// 3. /
console.log('[index.vue] 加载范围内储备中心/预置点数据(用于面板显示)...')
const rangeResponse = await request({
url: `/snow-ops-platform/yhYjll/list`,
method: 'GET',
params: {
longitude: DISASTER_CENTER.lon,
latitude: DISASTER_CENTER.lat,
maxDistance: disasterData.forcePreset.value.searchRadius,
},
})
// stations
if (rangeResponse?.data && Array.isArray(rangeResponse.data)) {
const transformedStations = disasterData.transformReserveDataToStations(
rangeResponse.data,
{ longitude: DISASTER_CENTER.lon, latitude: DISASTER_CENTER.lat }
)
if (transformedStations.length > 0) {
disasterData.forcePreset.value.stations = transformedStations
console.log('[index.vue] 已更新面板 stations范围内:', transformedStations.length, '个')
}
}
console.log(`[index.vue] 显示全部储备中心/预置点标记`)
// 4.
const offset = 0.0002 // 22
//
@ -800,7 +833,7 @@ const showAllMarkersAndRange = () => {
//
viewer.scene.requestRender()
// 4.
// 5.
showRangeCircle()
console.log('[index.vue] 快速响应标记显示完成')
@ -947,35 +980,45 @@ const loadEmergencyResources = async (longitude, latitude) => {
/**
* 加载储备中心和预置点数据
* @param {number} longitude - 经度可选不传则查询全部
* @param {number} latitude - 纬度可选不传则查询全部
* @param {boolean} loadAllForMap - 是否加载全部点位到地图true时不限制距离
*/
const loadReserveCentersAndPresets = async (longitude, latitude) => {
const loadReserveCentersAndPresets = async (longitude, latitude, loadAllForMap = false) => {
try {
//
const params = {}
if (!loadAllForMap && longitude !== undefined && latitude !== undefined) {
//
params.longitude = longitude
params.latitude = latitude
params.maxDistance = disasterData.forcePreset.value.searchRadius
}
// loadAllForMap true
const response = await request({
url: `/snow-ops-platform/yhYjll/list`,
method: 'GET',
params: {
longitude,
latitude,
maxDistance: disasterData.forcePreset.value.searchRadius,
},
params,
})
if (response?.data && Array.isArray(response.data)) {
console.log('[index.vue] 储备中心和预置点数据加载成功:', response.data)
console.log('[index.vue] 储备中心和预置点数据加载成功:', response.data.length, '个点位')
// 1. stations forcePreset
// 1. stations
const transformedStations = disasterData.transformReserveDataToStations(
response.data,
{ longitude, latitude }
)
if (transformedStations.length > 0) {
// forcePreset.stations ( station-list )
// 2. "" forcePreset.stations station-list
if (!loadAllForMap && transformedStations.length > 0) {
disasterData.forcePreset.value.stations = transformedStations
console.log('[index.vue] 已更新 forcePreset.stations:', transformedStations)
console.log('[index.vue] 已更新 forcePreset.stations:', transformedStations.length, '个')
}
// 2.
// 3.
if (mapStore.viewer) {
console.log('[index.vue] 添加储备中心和预置点地图标记...')
clearReserveCenterMarkers(mapStore.viewer)
@ -1166,6 +1209,11 @@ const initializeScene = async () => {
},
})
console.log('[index.vue] 中心点标记已添加')
// 线
console.log('[index.vue] 绘制坍塌边界蓝色实线...')
drawCollapseBoundary(viewer)
console.log('[index.vue] 坍塌边界蓝色实线已添加')
}
} catch (error) {
console.error('[index.vue] 3D模型加载失败:', error)
@ -1176,6 +1224,14 @@ const initializeScene = async () => {
console.log('[index.vue] 开始加载左侧灾前模型...')
await toggleCompareMode(true, viewer, { loadLeftModel: true })
console.log('[index.vue] 左侧灾前模型加载完成')
// 线
const leftContainer = document.getElementById('leftCesiumContainer')
if (leftContainer) {
console.log('[index.vue] 绘制左侧坍塌边界蓝色虚线...')
drawCollapseBoundaryLeft(leftContainer)
console.log('[index.vue] 左侧坍塌边界蓝色虚线已添加')
}
} catch (error) {
console.error('[index.vue] 左侧模型加载失败:', error)
}
@ -1289,10 +1345,13 @@ onUnmounted(() => {
console.log('[index.vue] 组件卸载,开始清理资源...')
const viewer = mapStore.viewer
const leftContainer = document.getElementById('leftCesiumContainer')
//
if (viewer) {
clearRangeCircle(viewer)
// 线
clearCollapseBoundary(viewer, leftContainer)
}
//