第11章 二次开发指南

11.1 二次开发概述

map-360-demo 是一个结构清晰、扩展性良好的演示项目。本章介绍如何在此基础上进行二次开发,包括新增场景、扩展标记类型、自定义样式、接入真实数据等。

11.2 新增场景

11.2.1 场景数据结构

场景定义在 src/data/scenes.ts

// scenes.ts:11-30
export const SCENES: Scene[] = [
  {
    id: 'mercantour',
    name: '法国默康图尔国家公园',
    description: '...',
    panorama: BASE_URL + 'sphere.jpg',
    coordinates: [6.78677, 44.58241],
    bearing: '120deg',
    defaultZoom: 14,
  },
  {
    id: 'key-biscayne',
    name: '美国比斯坎湾灯塔',
    description: '...',
    panorama: BASE_URL + 'tour/key-biscayne-1.jpg',
    coordinates: [-80.1556, 25.6742],
    bearing: '0deg',
    defaultZoom: 15,
  },
]

11.2.2 新增场景步骤

  1. 准备全景图:将全景图片放到 public/ 或使用 CDN 地址
  2. 添加场景对象:在 SCENES 数组中添加新场景
  3. 配置参数
    • id:唯一标识
    • name:场景名称
    • panorama:全景图地址
    • coordinates:拍摄点经纬度 [lng, lat]
    • bearing:方位角(全景正北方向对应的地理方位角)
    • defaultZoom:地图默认缩放级别

11.2.3 添加默认标记

DEFAULT_MARKERS 数组中添加新场景的默认标记:

// scenes.ts:32-105
{
  id: 'm8',
  sceneId: 'mercantour',
  name: '新标记',
  description: '标记描述',
  position: { yaw: 0.5, pitch: 0.1 },  // 全景球形坐标
  coordinates: [6.78677, 44.58241],    // GPS 经纬度
  createdAt: Date.now(),
  type: 'info',
}

11.3 扩展标记类型

11.3.1 修改类型定义

src/types/index.ts 中扩展 MarkerType

type MarkerType = 'info' | 'link' | 'video'  // 新增 video 类型

11.3.2 更新转换逻辑

toPsvMarkerConfig 中处理新类型:

// scenes.ts:117-140
export function toPsvMarkerConfig(m: MarkerData): PsvMarkerConfig {
  const isLink = m.type === 'link'
  const isVideo = m.type === 'video'
  // 根据类型选择图标和样式
  return {
    // ...
    image: isLink ? LINK_ICON : isVideo ? VIDEO_ICON : BASE_URL + 'pictos/pin-blue.png',
    className: isLink ? 'psv-marker--link' : isVideo ? 'psv-marker--video' : '',
    // ...
  }
}

11.3.3 更新点击分发逻辑

App.vueonMarkerClick 中处理新类型:

function onMarkerClick(id: string) {
  const marker = currentMarkers.value.find(m => m.id === id)
  if (!marker) return
  if (marker.type === 'link' && marker.targetSceneId) {
    // 切换场景
  } else if (marker.type === 'video') {
    // 播放视频
  } else {
    psvRef.value?.gotoMarker(id)  // 旋转视角
  }
}

11.4 自定义样式

11.4.1 修改 PSV 样式

src/styles/psv.css 中覆盖 PSV 默认样式:

/* 修改标记 tooltip 样式 */
.psv-marker-content {
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

11.4.2 新增标记样式

为不同类型的标记添加样式:

.psv-marker--video {
  /* 视频标记样式 */
}

11.5 接入真实数据

11.5.1 替换静态场景

SCENES 改为从 API 获取:

// 在 useAppState.ts 中
async function loadScenes() {
  const res = await fetch('/api/scenes')
  scenes.value = await res.json()
}

11.5.2 替换 localStorage 持久化

将 localStorage 持久化改为后端存储:

// 在 useAppState.ts 中
watch(markers, async (val) => {
  await fetch('/api/markers', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(val),
  })
}, { deep: true })

11.6 常见扩展场景

11.6.1 添加更多插件

Photo Sphere Viewer 提供丰富的插件,如:

  • GalleryPlugin:全景图画廊
  • VirtualTourPlugin:虚拟漫游
  • GyroscopePlugin:陀螺仪控制
  • AutorotatePlugin:自动旋转
import { GalleryPlugin } from '@photo-sphere-viewer/gallery-plugin'

viewer = new Viewer({
  // ...
  plugins: [
    GalleryPlugin.withConfig({ /* 配置 */ }),
    // 其他插件
  ],
})

11.6.2 添加自定义 UI

App.vue 中添加新的 UI 组件,通过 composable 管理状态。

11.6.3 接入真实地图服务

将 PlanPlugin 的默认 OSM 底图替换为其他地图服务(如天地图、高德地图),需要配置 configureLeaflet 或自定义瓦片源。

11.7 开发注意事项

11.7.1 不要使用 configureLeaflet

重要:不要使用 configureLeaflet 选项。它的语义是”完全接管 Leaflet 配置”,传入后默认 OSM 底图不会添加,地图会变成灰色空面板。点击监听改为创建后通过 getLeaflet() 挂载。

11.7.2 场景切换复用 viewer

场景切换时复用 viewer 而非销毁重建,保留 WebGL 上下文和瓦片缓存,切换更快且带过渡动画。

11.7.3 预览 pin 避免重叠

编辑模式下若位置与原有标记完全相同则不显示预览 pin,避免与蓝 pin 重叠。

11.7.4 坐标换算注意 cos 因子

经度/纬度每度地面距离不同(相差 cos(纬度) 因子),必须先在”米”空间按方位角分解再换算回度数,否则标记落在地图上的实际方向会偏转(中纬度可达 10°)。

11.7.5 HTML 转义防注入

标记名称/描述在渲染到 PSV tooltip 前经过 HTML 转义,防止 XSS 注入。

11.8 项目扩展建议

  1. 引入 Pinia:如果状态管理变得复杂,可引入 Pinia 替代模块级单例
  2. 接入后端:将场景和标记数据迁移到后端,支持多用户协作
  3. 添加更多场景:扩展场景库,支持更多地理位置
  4. 自定义标记图标:支持用户上传自定义图标
  5. 移动端适配:优化移动端触控体验

11.9 总结

map-360-demo 是一个结构清晰、扩展性良好的全景地图联动演示项目。通过本章的学习,你应该能够:

  1. 理解项目的整体架构与数据流
  2. 掌握 Photo Sphere Viewer 的插件体系
  3. 理解全景与地图联动的实现原理
  4. 掌握坐标换算的核心算法
  5. 能够在此基础上进行二次开发

至此,map-360-demo 的完整教程结束。祝你开发愉快!