znlgis 博客

GIS开发与技术分享 — GDAL · GeoServer · PostGIS · QGIS · OpenLayers · Cesium · FreeCAD · NPOI

第09章:地图集成与辅助功能

前面章节的系统学习,你已经掌握了 Viewer 核心操作、标记系统、图集管理和视频全景。从本章开始,我们把视野扩展到更丰富的辅助功能——地图集成、指南针、自动旋转、设置面板等。这些插件并非必须,但它们能让你的全景应用从”能用”跃升到”好用”,在房地产看房、景区导览、虚拟展厅等场景中尤其关键。

Photo-Sphere-Viewer 提供了两种地图插件:MapPlugin(自定义静态地图)和 PlanPlugin(基于 Leaflet 的真实地理地图),二者定位不同,各有适用场景。本章还将一并介绍 CompassPlugin(指南针)、AutorotatePlugin(自动旋转)、SettingsPlugin / ResolutionPlugin(设置面板与分辨率切换)、VisibleRangePlugin(可见范围限制)以及 OverlaysPlugin(叠加层),并讨论多插件组合的最佳实践。

9.1 MapPlugin 详解(自定义地图)

9.1.1 功能定位

MapPlugin 在 Viewer 界面上叠加一个自定义地图图片——它不是一个 GIS 地图,而是一张静态图片(PNG / JPG / SVG),你可以在上面放置热点标记。它的典型应用场景包括:

  • 室内平面图:在 VR 看房中,将楼层平面图作为地图,标记客厅、卧室、厨房等位置
  • 景区导览图:使用手绘地图或景区示意图,标记观景台、休息区等点位
  • 展馆布局图:为虚拟展厅配置展区分布图,观众可以快速跳转到目标区域
  • 工厂/仓库示意图:标记不同工位或货架在全景中的对应位置

MapPlugin 的地图底图是一张自定义图片,因此你可以使用任何设计工具(Photoshop、Figma、甚至手绘图扫描件)来制作。地图支持缩放、拖拽平移,并且地图上的中心指针会随全景视角实时旋转,直观显示当前朝向。

9.1.2 安装与导入

npm install @photo-sphere-viewer/map-plugin

导入代码:

// JS 模块
import { MapPlugin } from '@photo-sphere-viewer/map-plugin';

// CSS(必须)
import '@photo-sphere-viewer/map-plugin/index.css';

CDN 方式则需要在 Import Map 中添加映射,并在 HTML 中引入 CSS:

<link rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@photo-sphere-viewer/map-plugin@5/index.css" />

<script type="importmap">
{
  "imports": {
    "@photo-sphere-viewer/map-plugin": "https://cdn.jsdelivr.net/npm/@photo-sphere-viewer/map-plugin@5/index.module.js"
  }
}
</script>

9.1.3 核心配置选项

MapPlugin 的配置项非常丰富,按功能分类如下。

必填配置:

选项 类型 说明
imageUrl string 地图图片的 URL,必须指定
center { x: number, y: number } 全景图在地图上的坐标位置(单位:像素),必填

外观控制:

选项 类型 默认值 说明
size string '200px' 地图容器尺寸,支持 pxremvh 等单位
position string 'bottom left' 地图在 Viewer 中的位置,可选 top / bottom 组合 left / right
shape 'round' \| 'square' 'round' 地图形状:圆形或方形
rotation number \| string 0 地图旋转角度,使地图方向与全景图一致,如 '45deg'
static boolean false 若为 true,地图不旋转,只有中心指针旋转以指示朝向

缩放与可视性:

选项 类型 默认值 说明
defaultZoom number 100 默认缩放级别(百分比)
minZoom number 20 最小缩放级别
maxZoom number 200 最大缩放级别
visibleOnLoad boolean true 初始是否显示地图
minimizeOnHotspotClick boolean true 点击热点后是否自动最小化地图

视觉元素:

选项 类型 默认值 说明
pinImage string 默认 SVG 中心指针图片(SVG 或图片 URL)
pinSize number 35 中心指针的大小
coneColor string '#1E78E6' 视场锥形区域颜色,设为 null 禁用
coneSize number 40 视场锥形区域大小
overlayImage string 默认 SVG 地图上层叠图片(如指南针刻度),设为 null 禁用

热点配置(hotspots):

热点是地图上的可点击标记点。每个热点有两种定位方式:

  • 角度 + 距离yaw(偏航角度)+ distance(距中心点的像素距离)
  • 绝对坐标x + y(地图图片上的像素坐标)
hotspots: [
  {
    id: 'kitchen',
    yaw: '45deg',
    distance: 120,
    tooltip: '厨房',
  },
  {
    id: 'bedroom',
    x: 400,
    y: 150,
    tooltip: '主卧',
  },
]

每个热点还支持 style 属性来覆盖全局的 spotStyle

9.1.4 方法

方法 说明
setImage(url, center?, rotation?) 更换地图图片,可同时更新中心点和旋转角度
setCenter(center, resetView=true) 更新全景图在地图上的位置,resetView=false 时不移动地图视角
setHotspots(hotspots) 设置热点数组
clearHotspots() 移除所有热点
setZoom(level) 设置缩放级别(介于 minZoommaxZoom 之间)
open() / close() 展开/折叠地图
maximize() / minimize() 最大化/最小化地图(仅在展开状态有效)

9.1.5 事件

事件名 参数 说明
select-hotspot { hotspotId: string } 用户点击地图上的热点
view-changed view: 'maximized' \| 'normal' \| 'closed' 地图视图状态变更

9.2 MapPlugin 完整示例

下面是一个完整的室内看房场景示例,使用楼层平面图作为地图,标记客厅、主卧、次卧和阳台在全景图中的对应位置。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>室内看房 - MapPlugin 示例</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    html, body { width: 100%; height: 100%; overflow: hidden; }
    #viewer { width: 100%; height: 100%; }
  </style>
  <link rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/@photo-sphere-viewer/core@5/index.css" />
  <link rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/@photo-sphere-viewer/map-plugin@5/index.css" />
</head>
<body>
  <div id="viewer"></div>

  <script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js",
      "@photo-sphere-viewer/core": "https://cdn.jsdelivr.net/npm/@photo-sphere-viewer/core@5/index.module.js",
      "@photo-sphere-viewer/map-plugin": "https://cdn.jsdelivr.net/npm/@photo-sphere-viewer/map-plugin@5/index.module.js"
    }
  }
  </script>

  <script type="module">
    import { Viewer } from '@photo-sphere-viewer/core';
    import { MapPlugin } from '@photo-sphere-viewer/map-plugin';

    const viewer = new Viewer({
      container: document.querySelector('#viewer'),
      panorama: 'https://photo-sphere-viewer-data.netlify.app/pano/living-room.jpg',
      caption: '三室两厅 · 客厅视角',
      navbar: ['zoom', 'fullscreen', 'caption'],
      plugins: [
        [MapPlugin, {
          imageUrl: 'https://photo-sphere-viewer-data.netlify.app/maps/floor-plan.png',
          center: { x: 500, y: 300 },
          rotation: '0deg',
          size: '280px',
          position: 'bottom left',
          shape: 'square',
          defaultZoom: 80,
          coneColor: 'rgba(30, 120, 230, 0.25)',
          hotspots: [
            {
              id: 'kitchen',
              yaw: '45deg',
              distance: 145,
              tooltip: '厨房',
            },
            {
              id: 'master-bedroom',
              yaw: '180deg',
              distance: 110,
              tooltip: '主卧',
            },
            {
              id: 'guest-bedroom',
              x: 620,
              y: 180,
              tooltip: '次卧',
            },
            {
              id: 'balcony',
              yaw: '270deg',
              distance: 160,
              tooltip: '阳台',
            },
          ],
          visibleOnLoad: true,
        }],
      ],
    });

    // 获取 MapPlugin 实例
    const mapPlugin = viewer.getPlugin(MapPlugin);

    // 监听热点点击事件
    mapPlugin.addEventListener('select-hotspot', ({ hotspotId }) => {
      const targets = {
        'kitchen':       { yaw: '45deg',  pitch: '0deg' },
        'master-bedroom': { yaw: '180deg', pitch: '0deg' },
        'guest-bedroom': { yaw: '190deg', pitch: '0deg' },
        'balcony':       { yaw: '270deg', pitch: '-10deg' },
      };

      const target = targets[hotspotId];
      if (target) {
        viewer.rotate({
          yaw: target.yaw,
          pitch: target.pitch,
        });
        console.log(`切换到: ${hotspotId}`);
      }
    });

    // 页面卸载时销毁
    window.addEventListener('beforeunload', () => {
      viewer.destroy();
    });
  </script>
</body>
</html>

这个示例演示了 MapPlugin 最核心的能力:一张自定义平面图叠加在 Viewer 上,点击热点瞬间切换全景视角。在真实项目中,你只需要替换 imageUrlpanorama 为实际路径,并根据平面图调整 center 和各热点的坐标即可。

9.3 PlanPlugin 详解(Leaflet 真实地图)

9.3.1 功能定位

PlanPlugin 与 MapPlugin 的最大区别在于:它使用真实的 GIS 地图。PlanPlugin 基于 Leaflet 构建,默认使用 OpenStreetMap 瓦片图层,显示全景图在地球上的真实地理位置。适合以下场景:

  • 户外全景:旅游景点、街景、自然风光,需要展示真实经纬度
  • 多点位导览:在城市地图上标记多个全景拍摄点,支持快速跳转
  • 地理信息系统集成:与已有 Leaflet 地图应用融合

9.3.2 安装与依赖

PlanPlugin 依赖 Leaflet,需要同时加载 Leaflet 的 JS 和 CSS。PSV 的 PlanPlugin 包本身不包含 Leaflet。

npm install @photo-sphere-viewer/plan-plugin leaflet
npm install --save-dev @types/leaflet

导入代码:

import 'leaflet/dist/leaflet.css';
import { PlanPlugin } from '@photo-sphere-viewer/plan-plugin';
import '@photo-sphere-viewer/plan-plugin/index.css';

CDN 方式需要额外引入 Leaflet:

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@photo-sphere-viewer/plan-plugin@5/index.css" />

<script type="importmap">
{
  "imports": {
    "leaflet": "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet-src.esm.js",
    "@photo-sphere-viewer/plan-plugin": "https://cdn.jsdelivr.net/npm/@photo-sphere-viewer/plan-plugin@5/index.module.js"
  }
}
</script>

Leaflet 需要单独引入。如果使用 npm 方式,import 'leaflet/dist/leaflet.css' 是必须的;如果使用 CDN,需要在 HTML 中通过 <link> 标签引入 Leaflet CSS。

9.3.3 核心配置选项

必填配置:

选项 类型 说明
coordinates [number, number] 全景图的 GPS 坐标,格式为 [longitude, latitude](经度在前,纬度在后)

外观与行为:

选项 类型 默认值 说明
size { width: string, height: string } { width: '300px', height: '200px' } 地图组件的尺寸
position string 'bottom left' 地图在 Viewer 中的位置
bearing number \| string 0 方向角偏移,使指针方向与全景朝向匹配
defaultZoom number 15 默认 Leaflet 缩放级别
pinImage string 默认 SVG 中心位置指针图片
pinSize number 35 中心指针大小
visibleOnLoad boolean true 初始是否显示
minimizeOnHotspotClick boolean true 点击热点后是否自动最小化地图

热点配置(hotspots):

PlanPlugin 的热点使用 GPS 坐标定位——这是与 MapPlugin 最本质的区别:

hotspots: [
  {
    id: 'tiananmen',
    coordinates: [116.3975, 39.9087],
    tooltip: '天安门',
  },
  {
    id: 'gugong',
    coordinates: [116.4039, 39.9158],
    tooltip: '故宫博物院',
  },
]

每个热点同样支持 style 属性覆盖 spotStyle

自定义图层(layers):

默认使用 OpenStreetMap 瓦片,你可以指定多个图层供用户切换:

layers: [
  {
    name: 'OpenStreetMap',
    urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
    attribution: '&copy; OpenStreetMap contributors',
  },
  {
    name: '卫星图',
    urlTemplate: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
    attribution: '&copy; Esri',
  },
]

每个图层对象包含 nameurlTemplate(或直接传 layer——任意 Leaflet 图层实例)和 attribution。如果定义了多个图层,地图上会出现一个图层切换按钮。

高级自定义:

通过 configureLeaflet(map) 回调,你可以在 Leaflet 地图实例创建后对其进行完全自定义——例如添加 GeoJSON 数据、自定义控件、设置地图边界等:

configureLeaflet(map) {
  // map 是 Leaflet 的 L.Map 实例
  L.control.scale().addTo(map);
  map.setMaxBounds([[39.8, 116.2], [40.0, 116.6]]);
}

一旦使用了 configureLeafletlayers 选项将被忽略——你需要自己管理瓦片图层。

9.3.4 方法与事件

方法 说明
setCoordinates(coordinates) 更新全景图在地图上的 GPS 坐标
setHotspots(hotspots) 设置热点数组
clearHotspots() 移除所有热点
setZoom(level) 设置 Leaflet 缩放级别
open() / close() 展开/折叠地图
maximize() / minimize() 最大化/最小化地图
getLeaflet() 获取底层 Leaflet 地图实例,用于高级自定义

事件与 MapPlugin 一致:select-hotspot(hotspotId)view-changed(view)

9.3.5 完整示例

import 'leaflet/dist/leaflet.css';
import { Viewer } from '@photo-sphere-viewer/core';
import { PlanPlugin } from '@photo-sphere-viewer/plan-plugin';
import '@photo-sphere-viewer/core/index.css';
import '@photo-sphere-viewer/plan-plugin/index.css';

const viewer = new Viewer({
  container: document.querySelector('#viewer'),
  panorama: 'https://photo-sphere-viewer-data.netlify.app/pano/street.jpg',
  caption: '北京 · 长安街',
  plugins: [
    [PlanPlugin, {
      coordinates: [116.3975, 39.9087],
      bearing: '15deg',
      defaultZoom: 16,
      size: { width: '320px', height: '240px' },
      position: 'bottom left',
      hotspots: [
        {
          id: 'tiananmen',
          coordinates: [116.3975, 39.9087],
          tooltip: '天安门广场',
        },
        {
          id: 'gugong',
          coordinates: [116.4039, 39.9158],
          tooltip: '故宫博物院',
        },
        {
          id: 'jingshan',
          coordinates: [116.4023, 39.9226],
          tooltip: '景山公园',
        },
      ],
      visibleOnLoad: true,
    }],
  ],
});

const planPlugin = viewer.getPlugin(PlanPlugin);

planPlugin.addEventListener('select-hotspot', ({ hotspotId }) => {
  // 此处可以配合 VirtualTourPlugin 实现场景切换
  console.log(`选中景点: ${hotspotId}`);
});

9.4 CompassPlugin 详解(指南针)

9.4.1 功能定位

CompassPlugin 在 Viewer 角落显示一个指南针组件,实时指示当前视野方向。用户可以直观地感知自己”面朝哪个方向”,也可以点击指南针上的方向快速导航。它特别适合:

  • 户外全景:配合真实方向,让用户知道南北朝向
  • 室内看房:标注房间方位,辅助理解户型
  • 导航类应用:作为方向的视觉参考

Photo-Sphere-Viewer 默认将 yaw=0(正前方)视为”北”。如果需要调整北方的定义,可以使用 sphereCorrection.panpanoData.poseHeading 进行全局校正。

9.4.2 安装与导入

npm install @photo-sphere-viewer/compass-plugin
import { CompassPlugin } from '@photo-sphere-viewer/compass-plugin';
import '@photo-sphere-viewer/compass-plugin/index.css';

9.4.3 配置选项

选项 类型 默认值 说明
size string '120px' 指南针尺寸,支持 pxremvh
position string 'top left' 指南针在 Viewer 中的位置,支持 top/bottom + left/center/right 组合
navigation boolean true 是否可点击指南针进行导航
resetPitch boolean true 点击导航时是否将俯仰角重置为 defaultPitch
hotspots CompassHotspot[] null 指南针上的标记点
backgroundSvg string 默认 SVG 自定义指南针背景 SVG(必须是正方形)
coneColor string 'rgba(255, 255, 255, 0.2)' 视野锥形区域颜色
navigationColor string 'rgba(255, 0, 0, 0.2)' 导航点击时的锥形指示颜色
hotspotColor string 'rgba(0, 0, 0, 0.5)' 热点默认颜色
className string 添加到指南针元素上的 CSS 类名

9.4.4 配置指南针热点

热点配置非常简洁,每个热点只需指定一个 yaw 角度(即指南针上的方位)和可选的 color

const viewer = new Viewer({
  container: document.querySelector('#viewer'),
  panorama: './pano.jpg',
  plugins: [
    [CompassPlugin, {
      size: '130px',
      position: 'top right',
      navigation: true,
      resetPitch: true,
      hotspots: [
        { yaw: '0deg',   color: '#e74c3c' },   // 北 — 红色
        { yaw: '90deg',  color: '#3498db' },   // 东 — 蓝色
        { yaw: '180deg', color: '#2ecc71' },   // 南 — 绿色
        { yaw: '270deg', color: '#f39c12' },   // 西 — 橙色
      ],
    }],
  ],
});

指南针热点还可以与 Markers 联动——在 Marker 配置中设置 compass: truecompass: '#ff0000',该 Marker 就会自动显示在指南针上。

9.4.5 方法与事件

方法 说明
setHotspots(hotspots) 更新指南针热点
clearHotspots() 移除所有热点

指南针没有自己专属的事件(热点交互通过可视化反馈完成,无需事件监听)。如果需要监听与指南针相关的 Markers 点击,使用 MarkersPlugin 的 select-marker 事件即可。

9.5 AutorotatePlugin 详解(自动旋转)

9.5.1 功能定位

AutorotatePlugin 让全景图自动旋转,常用于以下场景:

  • 展厅展示:无人操作时自动旋转展示全景内容,吸引观众注意力
  • 产品 360° 展示:用户无需拖拽即可看到产品的各个角度
  • 关键路径演示:按预设路径依次浏览多个关键视角,每个视角停留一段时间

9.5.2 安装与导入

npm install @photo-sphere-viewer/autorotate-plugin
import { AutorotatePlugin } from '@photo-sphere-viewer/autorotate-plugin';
import '@photo-sphere-viewer/autorotate-plugin/index.css';

插件会在导航栏自动添加一个”自动旋转”按钮,用户可以手动开关。

9.5.3 配置选项

选项 类型 默认值 说明
autostartDelay number 2000 用户停止交互后延迟多少毫秒开始自动旋转
autostartOnIdle boolean true 用户闲置后是否自动重新开始旋转(手动点击按钮关闭后不会自动重启)
autorotateSpeed string '2rpm' 旋转速度,支持 rpm(每分钟转数),设负值可反转方向
autorotatePitch number \| string defaultPitch 自动旋转时的俯仰角,设为 null 保持当前俯仰角
autorotateZoomLvl number null 自动旋转时的缩放级别,设为 null 保持当前缩放
keypoints AutorotateKeypoint[] 关键路径点数组,定义按序访问的位置
startFromClosest boolean true 是否从最近的关键点开始(而非数组第一个)

9.5.4 关键路径点(Keypoints)

当配置了 keypoints,自动旋转不再是匀速绕圈,而是按预设路径在关键点之间平滑移动,并在每个关键点停留一段时间:

keypoints: [
  {
    position: { yaw: 0,            pitch: '5deg' },
    pauseTime: 3000,   // 在此停留 3 秒
  },
  {
    position: { yaw: '90deg',  pitch: '-10deg' },
    pauseTime: 2000,
  },
  {
    position: { yaw: '180deg', pitch: '0deg' },
    pauseTime: 4000,
  },
  {
    position: { yaw: '270deg', pitch: '10deg' },
    pauseTime: 2000,
  },
]

如果未配置 keypoints,则使用匀速旋转模式(由 autorotateSpeed 控制速度)。

9.5.5 方法与事件

方法 说明
start() 开始自动旋转
stop() 停止自动旋转
toggle() 切换开关状态
setKeypoints(keypoints) 设置或更新关键路径点

事件:autorotate(enabled) — 自动旋转状态变更时触发,参数为布尔值表示当前状态。

9.5.6 完整示例

import { Viewer } from '@photo-sphere-viewer/core';
import { AutorotatePlugin } from '@photo-sphere-viewer/autorotate-plugin';
import '@photo-sphere-viewer/core/index.css';
import '@photo-sphere-viewer/autorotate-plugin/index.css';

const viewer = new Viewer({
  container: document.querySelector('#viewer'),
  panorama: './exhibition-hall.jpg',
  caption: '数字展厅 · 自动导览',
  navbar: ['autorotate', 'zoom', 'fullscreen', 'caption'],
  plugins: [
    [AutorotatePlugin, {
      autostartDelay: 3000,
      autostartOnIdle: true,
      autorotateSpeed: '1.5rpm',
      autorotatePitch: '-5deg',
      autorotateZoomLvl: 60,
      keypoints: [
        { position: { yaw: 0,            pitch: '0deg' },  pauseTime: 3000 },
        { position: { yaw: '60deg',  pitch: '-5deg' }, pauseTime: 2000 },
        { position: { yaw: '120deg', pitch: '0deg' },  pauseTime: 3000 },
        { position: { yaw: '180deg', pitch: '5deg' },  pauseTime: 2000 },
        { position: { yaw: '240deg', pitch: '0deg' },  pauseTime: 3000 },
        { position: { yaw: '300deg', pitch: '-5deg' }, pauseTime: 2000 },
      ],
      startFromClosest: true,
    }],
  ],
});

const autorotatePlugin = viewer.getPlugin(AutorotatePlugin);

// 监听状态变化
autorotatePlugin.addEventListener('autorotate', (enabled) => {
  console.log(`自动旋转: ${enabled ? '开启' : '关闭'}`);
});

// 页面卸载时销毁
window.addEventListener('beforeunload', () => {
  viewer.destroy();
});

9.6 SettingsPlugin 与 ResolutionPlugin

9.6.1 SettingsPlugin(设置面板)

SettingsPlugin 本身不做任何事情——它是一个基础设施插件,在导航栏添加一个”设置”按钮,点击后展开设置面板。其他插件(如 ResolutionPlugin)通过调用 addSetting() 向面板中注册具体的设置项。

npm install @photo-sphere-viewer/settings-plugin
import { SettingsPlugin } from '@photo-sphere-viewer/settings-plugin';
import '@photo-sphere-viewer/settings-plugin/index.css';

SettingsPlugin 支持两种设置类型:

Toggle 开关设置 — 只有 true / false 两种状态:

settingsPlugin.addSetting({
  id: 'night-mode',
  label: '夜间模式',
  type: 'toggle',
  active: () => nightMode,
  toggle: () => { nightMode = !nightMode; },
});

Options 选项设置 — 多个可选项之间切换:

settingsPlugin.addSetting({
  id: 'quality',
  label: '画质',
  type: 'options',
  current: () => quality,
  options: () => [
    { id: 'low',  label: '流畅' },
    { id: 'mid',  label: '标准' },
    { id: 'high', label: '高清' },
  ],
  apply: (option) => { quality = option; },
});

持久化存储: 通过 persist: true 开启设置持久化,默认使用 localStorage 存放在 psvSettings 键下。你也可以通过 storage 选项自定义存储方案(如 LocalForage、后端 API 等):

[SettingsPlugin, {
  persist: true,
  storage: {
    get(settingId) {
      return myCustomStore.get(`psv_${settingId}`);
    },
    set(settingId, value) {
      myCustomStore.set(`psv_${settingId}`, value);
    },
  },
}]

事件:setting-changed(settingId, settingValue) — 任何设置项变更时触发。

9.6.2 ResolutionPlugin(多分辨率切换)

ResolutionPlugin 必须配合 SettingsPlugin 使用,它在设置面板中添加一个”画质”选项,让用户在不同分辨率的全景图之间切换。这对于性能优化非常有用——默认加载低分辨率图片确保快速打开,用户可按需切换到高清版本。

npm install @photo-sphere-viewer/resolution-plugin
import { SettingsPlugin } from '@photo-sphere-viewer/settings-plugin';
import { ResolutionPlugin } from '@photo-sphere-viewer/resolution-plugin';
import '@photo-sphere-viewer/settings-plugin/index.css';
import '@photo-sphere-viewer/resolution-plugin/index.css';

const viewer = new Viewer({
  container: document.querySelector('#viewer'),
  // 注意:若在 Viewer 上直接设置了 panorama,defaultResolution 将被忽略
  plugins: [
    SettingsPlugin,
    [ResolutionPlugin, {
      defaultResolution: 'HD',
      showBadge: true,
      resolutions: [
        {
          id: 'SD',
          label: '标清',
          panorama: 'https://example.com/pano-sd.jpg',
        },
        {
          id: 'HD',
          label: '高清',
          panorama: 'https://example.com/pano-hd.jpg',
        },
        {
          id: '4K',
          label: '超清',
          panorama: 'https://example.com/pano-4k.jpg',
        },
      ],
    }],
  ],
});

每个 resolution 对象包含 id(唯一标识)、label(显示名称)、panorama(对应的全景图 URL),以及可选的 panoData(为该分辨率的图片提供裁剪信息)。

重要兼容性提示: ResolutionPlugin 与 GalleryPlugin 不兼容——图集模式已自带场景管理,使用 ResolutionPlugin 会导致冲突。

事件:resolution-changed(resolutionId) — 分辨率切换时触发。

9.7 VisibleRangePlugin(可见范围限制)

9.7.1 功能定位

VisibleRangePlugin 锁定全景图的水平和垂直旋转范围,阻止用户旋转到范围之外的区域。它影响手动拖拽和自动旋转,但不限制 API 调用。典型场景:

  • 部分全景图:当全景图本身只覆盖了 180° 或更窄的视角时,限制可见范围避免用户看到黑边
  • 引导式体验:在虚拟展厅中限制用户只能看前方展品,避免不必要的视口移动
  • 安全教育:限制儿童可浏览的范围

9.7.2 安装与导入

npm install @photo-sphere-viewer/visible-range-plugin
import { VisibleRangePlugin } from '@photo-sphere-viewer/visible-range-plugin';
import '@photo-sphere-viewer/visible-range-plugin/index.css';

9.7.3 配置与使用

方式一:手动指定范围

[VisibleRangePlugin, {
  horizontalRange: ['-90deg', '90deg'],  // 只能看正前方 180° 范围
  verticalRange:   ['-45deg', '45deg'],   // 上下各 45°
}]

方式二:基于裁剪数据自动计算

如果全景图使用了 panoData 配置了裁剪信息(croppedXcroppedYcroppedWidthcroppedHeight),可以启用 usePanoData: true 自动将可见范围限制为裁剪后的有效区域:

const viewer = new Viewer({
  panorama: './partial-pano.jpg',
  panoData: {
    fullWidth: 8192,
    fullHeight: 4096,
    croppedX: 1024,
    croppedY: 512,
    croppedWidth: 6144,
    croppedHeight: 3072,
  },
  plugins: [
    [VisibleRangePlugin, {
      usePanoData: true,
    }],
  ],
});

9.7.4 方法

方法 说明
setHorizontalRange(range) 设置水平可见范围,传 null 取消限制
setVerticalRange(range) 设置垂直可见范围,传 null 取消限制
setRangesFromPanoData() 从当前全景的裁剪数据重新计算可见范围

9.8 OverlaysPlugin(叠加层)

9.8.1 功能定位

OverlaysPlugin 在全景球面上叠加额外的图像——与 MarkersPlugin 不同,叠加层是嵌入 3D 场景中的,而非浮在画面上层的 DOM 元素。这意味着叠加层会随全景旋转而自然变换视角,具有真实的立体感。典型用途:

  • 虚拟装修:在空房间全景上叠加家具图片,展示装修效果
  • 特效叠加:叠加光照效果、文字标注、水印等
  • AR 预览:在现实场景全景上叠加虚拟物体

9.8.2 安装与导入

npm install @photo-sphere-viewer/overlays-plugin
import { OverlaysPlugin } from '@photo-sphere-viewer/overlays-plugin';
import '@photo-sphere-viewer/overlays-plugin/index.css';

9.8.3 叠加层类型

OverlaysPlugin 支持两种几何体类型:

球形叠加层(Spherical) — 使用等距柱状投影图片:

overlays: [
  {
    id: 'furniture',
    path: 'https://example.com/furniture-overlay.png',
    opacity: 0.9,
    zIndex: 1,
  },
]

球形叠加层也支持 panoData 裁剪,用于只覆盖球面的一部分(例如只在一面墙上叠加):

{
  id: 'wall-art',
  path: 'painting.png',
  panoData: {
    fullWidth: 4096,
    fullHeight: 2048,
    croppedX: 0,
    croppedY: 500,
    croppedWidth: 4096,
    croppedHeight: 1048,
  },
}

立方体叠加层(Cubemap) — 使用 6 张独立的面图片:

{
  id: 'cube-overlay',
  path: {
    left:   'cubemap/overlay-left.jpg',
    front:  'cubemap/overlay-front.jpg',
    right:  'cubemap/overlay-right.jpg',
    back:   'cubemap/overlay-back.jpg',
    top:    'cubemap/overlay-top.jpg',
    bottom: 'cubemap/overlay-bottom.jpg',
  },
  opacity: 0.7,
  zIndex: 2,
}

9.8.4 配置选项

选项 类型 默认值 说明
overlays OverlayConfig[] 初始叠加层配置数组
autoclear boolean true 切换全景图时是否自动清除所有叠加层
inheritSphereCorrection boolean true 是否继承全局 sphereCorrection 设置

每个叠加层在配置中还可以设置 sphereCorrection,用于覆盖全局的球体校正。叠加层支持 opacity(透明度)和 zIndex(层级顺序),zIndex 越大的叠加层越靠上。

9.8.5 方法与事件

方法 说明
addOverlay(config) 动态添加一个叠加层
removeOverlay(id) 通过 id 移除指定叠加层
clearOverlays() 移除所有叠加层

事件:overlay-click(overlayId) — 用户点击叠加层时触发。

9.9 多插件组合最佳实践

随着学习的深入,你已经接触了 PSV 生态中的大量插件。在实际项目中,通常需要组合多个插件来实现完整功能。以下是经过验证的插件兼容性说明和推荐组合方案。

9.9.1 插件兼容性矩阵

大多数插件可以自由组合,但以下组合需要特别注意:

插件 A 插件 B 兼容性 说明
GalleryPlugin ResolutionPlugin 不兼容 图集模式自带切换逻辑,与分辨率切换冲突
MapPlugin PlanPlugin 可共存 两者定位不同,但通常不同时使用
ResolutionPlugin SettingsPlugin 强依赖 ResolutionPlugin 必须在 SettingsPlugin 之后加载
VideoPlugin ResolutionPlugin 兼容 支持切换不同分辨率的视频源
MarkersPlugin MapPlugin 兼容 Markers 可通过 map 属性直接显示在地图上
MarkersPlugin CompassPlugin 兼容 Markers 可通过 compass 属性直接显示在指南针上
MarkersPlugin PlanPlugin 兼容 Markers 可通过 plan 属性直接显示在 Leaflet 地图上

9.9.2 按场景推荐的插件组合

场景一:房地产 VR 看房

这是最经典的 PSV 应用场景,核心需求是房间之间的快速切换、平面图导航和空间定位。

import { Viewer } from '@photo-sphere-viewer/core';
import { VirtualTourPlugin } from '@photo-sphere-viewer/virtual-tour-plugin';
import { MarkersPlugin } from '@photo-sphere-viewer/markers-plugin';
import { MapPlugin } from '@photo-sphere-viewer/map-plugin';
import { CompassPlugin } from '@photo-sphere-viewer/compass-plugin';

const viewer = new Viewer({
  container: document.querySelector('#viewer'),
  panorama: 'living-room.jpg',
  plugins: [
    [VirtualTourPlugin, { /* 节点配置 */ }],
    [MarkersPlugin, { /* 标记配置 */ }],
    [MapPlugin, {
      imageUrl: 'floor-plan.png',
      center: { x: 500, y: 300 },
      hotspots: [ /* 平面图热点 */ ],
    }],
    [CompassPlugin, {
      size: '100px',
      position: 'top right',
    }],
  ],
});

组合要点:

  • VirtualTourPlugin 管理多个房间节点的切换
  • MapPlugin 提供直观的平面图导航
  • CompassPlugin 辅助方向感知
  • MarkersPlugin 在各个房间中标记细节(如橱柜材质、地板品牌等)

场景二:360° 视频展示

import { VideoPlugin } from '@photo-sphere-viewer/video-plugin';
import { ResolutionPlugin } from '@photo-sphere-viewer/resolution-plugin';
import { SettingsPlugin } from '@photo-sphere-viewer/settings-plugin';
import { MarkersPlugin } from '@photo-sphere-viewer/markers-plugin';
import { AutorotatePlugin } from '@photo-sphere-viewer/autorotate-plugin';

const viewer = new Viewer({
  container: document.querySelector('#viewer'),
  panorama: { source: 'tour-360.mp4' },
  plugins: [
    SettingsPlugin,
    [VideoPlugin, { /* 视频配置 */ }],
    [ResolutionPlugin, {
      resolutions: [
        { id: '720p', label: '720p', panorama: { source: 'tour-720p.mp4' } },
        { id: '1080p', label: '1080p', panorama: { source: 'tour-1080p.mp4' } },
      ],
    }],
    [AutorotatePlugin, { autorotateSpeed: '0.5rpm' }],
    [MarkersPlugin, { /* 在视频中标记关键时间点 */ }],
  ],
});

场景三:移动端 VR 体验

import { GyroscopePlugin } from '@photo-sphere-viewer/gyroscope-plugin';
import { StereoPlugin } from '@photo-sphere-viewer/stereo-plugin';

const viewer = new Viewer({
  container: document.querySelector('#viewer'),
  panorama: 'vr-scene.jpg',
  mousewheel: false,
  navbar: ['zoom', 'fullscreen', 'gyroscope', 'stereo'],
  plugins: [
    [GyroscopePlugin, {
      touchMode: true,
      absolutePosition: true,
    }],
    [StereoPlugin, {
      stereoOverlay: 0.5,
    }],
  ],
});

场景四:展厅自动展示

import { AutorotatePlugin } from '@photo-sphere-viewer/autorotate-plugin';
import { MarkersPlugin } from '@photo-sphere-viewer/markers-plugin';
import { OverlaysPlugin } from '@photo-sphere-viewer/overlays-plugin';

const viewer = new Viewer({
  container: document.querySelector('#viewer'),
  panorama: 'exhibition.jpg',
  navbar: ['autorotate', 'zoom', 'fullscreen'],
  plugins: [
    [AutorotatePlugin, {
      autostartDelay: 5000,
      autorotateSpeed: '1rpm',
      autorotatePitch: '-3deg',
      keypoints: [ /* 按展品顺序排列的关键路径点 */ ],
    }],
    [MarkersPlugin, {
      markers: [ /* 展品信息标记 */ ],
    }],
    [OverlaysPlugin, {
      overlays: [ /* 特效叠加层 */ ],
    }],
  ],
});

9.9.3 插件加载顺序与最佳实践

  1. SettingsPlugin 必须最先加载——ResolutionPlugin 等依赖它注册设置面板
  2. VideoPlugin 必须在分辨率切换插件之前加载——视频源需要先初始化
  3. MapPlugin / PlanPlugin 的 CSS 导入不可遗漏——否则地图按钮和样式丢失
  4. 合理控制插件数量——每个插件都会增加初始加载体积。PSV 的 tree-shaking 支持良好,如果你使用 npm + 打包工具(如 Vite / Webpack),未引用的插件不会被打包进去
  5. 性能敏感场景——如果全景图体积大且网络慢,避免同时初始化过多插件,可以在 ready 事件后按需动态添加
viewer.addEventListener('ready', () => {
  // 全景图加载完成后,再创建地图和标记插件(非官方 API,仅示意思路)
  // 实际中应使用插件系统的动态注册方式
}, { once: true });
  1. 测试兼容性——在开发阶段,每添加一个新插件后立即在全景图上测试一轮基本交互(拖拽、缩放、热点点击),确保没有插件冲突

9.10 本章小结

本章全面介绍了 Photo-Sphere-Viewer 的地图集成方案与辅助功能插件。这些插件虽不直接控制全景图的渲染,却大幅提升了应用的用户体验和实用价值。

知识要点回顾:

插件 一句话总结 核心依赖
MapPlugin 自定义静态地图,适合平面图/示意图
PlanPlugin 基于 Leaflet 的真实 GIS 地图,适合地理全景 Leaflet
CompassPlugin 角落指南针,指示当前视野方向
AutorotatePlugin 自动旋转或按关键路径顺序导览
SettingsPlugin 统一设置面板基础设施,其他插件注册设置项
ResolutionPlugin 多分辨率切换,必须配合 SettingsPlugin SettingsPlugin
VisibleRangePlugin 锁定水平和垂直旋转范围
OverlaysPlugin 在 3D 球面上叠加额外图像

关键区分:

  • MapPlugin vs PlanPlugin:前者是自定义静态图片(室内平面图),后者是真实地理地图(经纬度 + OSM)。选哪个取决于你的全景图是否需要关联真实地理位置。
  • MapPlugin 热点使用像素坐标(x/y)或角度+距离(yaw+distance);PlanPlugin 热点使用 GPS 坐标(coordinates)。
  • SettingsPlugin 是基础设施,本身不做任何事;ResolutionPlugin 依赖它来展示画质切换按钮。
  • ResolutionPlugin 不能与 GalleryPlugin 同时使用。

下一章将进入实战整合——我们将把这些插件组合到真实项目框架(React / Vue / Angular)中,并讨论生产环境的打包优化与部署策略。


← 上一章 下一章 →