第05章 全景与地图双向联动机制
5.1 联动概述
map-360-demo 的核心亮点是全景图与地图的双向联动。所谓”双向”指的是:
- 全景 → 地图:点击全景中的标记热点,地图上对应的热点会高亮
- 地图 → 全景:点击地图上的热点,全景会自动旋转到对应位置
这种联动通过 Photo Sphere Viewer 的 PlanPlugin 实现,其核心机制是标记的 data.plan 字段。
5.2 联动核心:data.plan 字段
5.2.1 标记配置转换
在 src/data/scenes.ts 中,toPsvMarkerConfig 函数将业务标记数据转换为 PSV 标记配置:
// scenes.ts:117-140
export function toPsvMarkerConfig(m: MarkerData): PsvMarkerConfig {
const isLink = m.type === 'link'
const safeName = escapeHtml(m.name)
const safeDesc = escapeHtml(m.description)
const tooltip = isLink ? `${safeName}(跳转)` : safeName
return {
id: m.id,
tooltip,
content: `<div class="psv-marker-content"><h3>${safeName}</h3><p>${safeDesc}</p></div>`,
position: m.position, // 全景球形坐标
image: isLink ? LINK_ICON : BASE_URL + 'pictos/pin-blue.png',
size: { width: 32, height: 32 },
anchor: 'bottom center',
className: isLink ? 'psv-marker--link' : '',
data: {
plan: { // PlanPlugin 联动字段
coordinates: m.coordinates, // GPS 经纬度
size: 28,
image: isLink ? LINK_ICON : BASE_URL + 'pictos/pin-blue.png',
},
},
}
}
5.2.2 联动原理
data.plan 字段是 PlanPlugin 读取地图热点的约定:
position(球形坐标 yaw/pitch):决定标记在全景中的位置data.plan.coordinates(GPS 经纬度):决定标记在地图上的位置
两者通过同一个 marker id 关联。PlanPlugin 会自动:
- 在全景中渲染标记(根据
position) - 在地图上渲染热点(根据
data.plan.coordinates) - 建立两者的关联关系
5.3 点击事件分发
5.3.1 统一事件出口
在 PsvContainer.vue 中,全景标记点击和地图热点点击都 emit 同一个 marker-click 事件:
// PsvContainer.vue:123-131
markersPlugin?.addEventListener('select-marker', ({ marker }) => {
emit('marker-click', marker.id)
})
planPlugin?.addEventListener('select-hotspot', ({ hotspotId }) => {
emit('marker-click', hotspotId)
})
5.3.2 App 统一分发
在 App.vue 中,onMarkerClick 根据标记类型决定行为:
// App.vue:146-164
function onMarkerClick(id: string) {
const marker = currentMarkers.value.find(m => m.id === id)
if (!marker) return
if (marker.type === 'link' && marker.targetSceneId) {
// link 标记 → 切换场景
if (marker.targetSceneId === currentScene.value.id) {
show('已在该场景中', 'info'); return
}
flow.exitAdding()
const target = scenes.value.find(s => s.id === marker.targetSceneId)
switchScene(marker.targetSceneId!)
if (target) show(`已跳转至「${target.name}」`, 'info')
} else {
// info 标记 → 旋转视角
psvRef.value?.gotoMarker(id)
}
}
核心逻辑:
- link 标记:切换场景(调用
switchScene) - info 标记:旋转视角(调用
gotoMarker)
5.4 全景旋转:gotoMarker
5.4.1 暴露接口
PsvContainer.vue 通过 defineExpose 暴露 gotoMarker:
// PsvContainer.vue:210-218
defineExpose({ gotoMarker, refreshMarkers })
5.4.2 内部实现
function gotoMarker(id: string) {
markersPlugin?.gotoMarker(id)
}
gotoMarker 内部调用 MarkersPlugin 的 gotoMarker 方法,该方法会:
- 将全景视角旋转到标记所在位置
- 高亮该标记
这就是点击标记触发全景旋转的入口。
5.5 场景切换时的联动更新
当切换场景时,PlanPlugin 需要同步更新地图配置:
// PsvContainer.vue:175-187
watch(() => props.scene.id, (id) => {
if (!viewer || viewerSceneId === id) return
viewerSceneId = id
planPlugin?.setOptions({ bearing: props.scene.bearing })
planPlugin?.setCoordinates(props.scene.coordinates)
planPlugin?.setZoom(props.scene.defaultZoom)
viewer.setPanorama(props.scene.panorama, {
caption: props.scene.name,
showLoader: true,
})
})
切换场景时:
- 更新地图方位角(
setOptions) - 更新地图中心坐标(
setCoordinates) - 更新地图缩放级别(
setZoom) - 更新全景图(
setPanorama)
5.6 联动时序图
用户点击全景标记
│
▼
PsvContainer: select-marker 事件
│
▼
emit('marker-click', id)
│
▼
App.vue: onMarkerClick(id)
│
├── link 标记 → switchScene(targetSceneId)
│ │
│ └── PsvContainer watcher 检测 scene.id 变化
│ └── 更新地图 + 全景
│
└── info 标记 → psvRef.gotoMarker(id)
│
└── markersPlugin.gotoMarker(id)
└── 全景旋转到标记位置
5.7 联动机制总结
| 方向 | 触发方式 | 处理逻辑 |
|---|---|---|
| 全景 → 地图 | 点击全景标记 | PlanPlugin 自动高亮地图热点 |
| 地图 → 全景 | 点击地图热点 | emit marker-click → gotoMarker 旋转视角 |
| 场景切换 | 切换场景 | 更新地图坐标/方位角/缩放 + 更新全景图 |
关键设计:
- 统一事件出口:全景和地图的点击都 emit
marker-click,由 App 统一分发 - data.plan 字段:通过标记的
data.plan字段自动关联全景与地图位置 - 复用 viewer:场景切换复用 viewer,保留 WebGL 上下文和瓦片缓存
5.8 下一步
理解了联动机制后,进入 第06章 标记管理功能详解 了解标记的完整管理流程。