SuperMap iClient3D for Cesium 如何限制相机位置在边界内

SuperMap iClient3D for Cesium 如何限制相机位置在边界内

功能介绍

Cesium 中,操作相机时,如果操作不当惠导致相机乱飞,找不到预设的场景,而且Cesium 中也没有可以限制相机的接口,本篇文章通过每帧检测相机位置,实现了将相机限制在了矩形边界内。

效果展示

实现思路

主要实现思路是通过viewer.scene.postUpdate方法注册了一个检测相机位置的方法,每当相机超出了设置的位置就会被强制拉回范围内

设置限制位置

首先设置一个变量确定好相机可以移动的边界位置。

javascript 复制代码
 // 定义矩形区域 (经度, 纬度)
  const boundary = {
       west: -120,   // 西边界经度
       south: 30,    // 南边界纬度
       east: -100,   // 东边界经度
       north: 50     // 北边界纬度
   };

获取相机位置并且限制经度在边界内

首先通过viewer.camera.position获取相机位置,然后和boundary 进行比较,看是否超出了范围,如果超出了范围就通过viewer.camera.setView将相机拉回范围内。

javascript 复制代码
  // 相机限制函数
 function constrainCameraToBoundary() {
      const position = viewer.camera.position;
      const cartographicPosition = Cesium.Cartographic.fromCartesian(position);
      
      // 获取当前相机位置的经纬度
      let longitude = Cesium.Math.toDegrees(cartographicPosition.longitude);
      let latitude = Cesium.Math.toDegrees(cartographicPosition.latitude);
      let height = cartographicPosition.height;
      let heading = viewer.camera.heading;
      let pitch = viewer.camera.pitch;
      let roll = viewer.camera.roll;

      // 限制经度在边界内
      if (longitude < boundary.west) {
          longitude = boundary.west;
      } else if (longitude > boundary.east) {
          longitude = boundary.east;
      }
      
      // 限制纬度在边界内
      if (latitude < boundary.south) {
          latitude = boundary.south;
      } else if (latitude > boundary.north) {
          latitude = boundary.north;
      }
      // 如果相机超出了边界,将其拉回边界内
      if (longitude !== Cesium.Math.toDegrees(cartographicPosition.longitude) || 
          latitude !== Cesium.Math.toDegrees(cartographicPosition.latitude)) {
          
          // 保持相机高度不变,只调整水平位置
          const newPosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);
          viewer.camera.setView({
              destination: newPosition,
              orientation:{
                    heading:0,
                 		 roll :  6.283185307179586,
                     pitch : viewer.camera.pitch,
              }
            }
          )
      }
  }

注册监听事件

将检测相机位置事件注册到场景每帧更新事件中。只要相机超出范围就被拉回之前的位置。

javascript 复制代码
 // 注册每帧更新事件
viewer.scene.postUpdate.addEventListener(constrainCameraToBoundary);

完整代码

javascript 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Cesium 相机矩形范围限制</title>
   <script src="http://support.supermap.com.cn:8090/webgl/Cesium/Build/Cesium/Cesium.js"> </script>
 <link href="http://support.supermap.com.cn:8090/webgl/Cesium/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
 <style>
     body {
         margin: 0;
         padding: 0;
         overflow: hidden;
         font-family: Arial, sans-serif;
     }
     #cesiumContainer {
         position: absolute;
         width: 100%;
         height: 100%;
     }
     #infoPanel {
         position: absolute;
         top: 10px;
         left: 10px;
         background: rgba(0, 0, 0, 0.7);
         color: white;
         padding: 15px;
         border-radius: 5px;
         z-index: 100;
         max-width: 300px;
     }
     h1 {
         font-size: 18px;
         margin-top: 0;
     }
     .boundary-line {
         position: absolute;
         pointer-events: none;
     }
     button {
         background: #4CAF50;
         border: none;
         color: white;
         padding: 8px 15px;
         text-align: center;
         text-decoration: none;
         display: inline-block;
         font-size: 14px;
         margin: 4px 2px;
         cursor: pointer;
         border-radius: 4px;
     }
 </style>
</head>
<body>
 <div id="cesiumContainer"></div>
 <div id="infoPanel">
     <h1>相机矩形范围限制演示</h1>
     <p>相机被限制在蓝色矩形区域内移动。</p>
     <p>尝试拖动地图,观察相机如何被限制在边界内。</p>
     <div id="coordinates">
         <p>经度: <span id="longitude">--</span></p>
         <p>纬度: <span id="latitude">--</span></p>
         <p>高度: <span id="height">--</span> 米</p>
         <p>方位角: <span id="heading">--</span></p>
         <p>倾斜角: <span id="pitch">--</span></p>
         <p>旋转角: <span id="roll">--</span> 米</p>
     </div>
     <button id="resetView">重置视图</button>
 </div>

 <script>
     // 初始化Cesium视图
     const viewer = new Cesium.Viewer('cesiumContainer', {
         terrainProvider: Cesium.createWorldTerrain()
     });

     // 禁用默认的相机控制限制
     viewer.scene.screenSpaceCameraController.enableTranslate = true;
     viewer.scene.screenSpaceCameraController.enableLook = true;
     viewer.scene.screenSpaceCameraController.enableTilt = true;
     viewer.scene.screenSpaceCameraController.enableZoom = true;

     // 定义矩形区域 (经度, 纬度)
     const boundary = {
         west: -120,   // 西边界经度
         south: 30,    // 南边界纬度
         east: -100,   // 东边界经度
         north: 50     // 北边界纬度
     };

     // 计算矩形中心点
     const centerLongitude = (boundary.west + boundary.east) / 2;
     const centerLatitude = (boundary.south + boundary.north) / 2;

     // 设置初始相机位置到矩形中心
     viewer.camera.setView({
         destination: Cesium.Cartesian3.fromDegrees(centerLongitude, centerLatitude, 1500000) // 高度设为1500公里
     });

     // 绘制矩形边界
     const boundaryPolygon = viewer.entities.add({
         polygon: {
             hierarchy: [
                 new Cesium.Cartesian3.fromDegrees(boundary.west, boundary.south),
                 new Cesium.Cartesian3.fromDegrees(boundary.east, boundary.south),
                 new Cesium.Cartesian3.fromDegrees(boundary.east, boundary.north),
                 new Cesium.Cartesian3.fromDegrees(boundary.west, boundary.north)
             ],
             material: Cesium.Color.BLUE.withAlpha(0.2),
             outline: true,
             outlineColor: Cesium.Color.BLUE
         }
     });

     // 相机限制函数
     function constrainCameraToBoundary() {
         const position = viewer.camera.position;
         const cartographicPosition = Cesium.Cartographic.fromCartesian(position);
         
         // 获取当前相机位置的经纬度
         let longitude = Cesium.Math.toDegrees(cartographicPosition.longitude);
         let latitude = Cesium.Math.toDegrees(cartographicPosition.latitude);
         let height = cartographicPosition.height;
         let heading = viewer.camera.heading;
         let pitch = viewer.camera.pitch;
         let roll = viewer.camera.roll;
         
         // 更新UI显示
         document.getElementById('longitude').textContent = longitude.toFixed(4);
         document.getElementById('latitude').textContent = latitude.toFixed(4);
         document.getElementById('height').textContent = Math.round(height);
          document.getElementById('heading').textContent = heading.toFixed(4);
         document.getElementById('pitch').textContent = pitch.toFixed(4);
         document.getElementById('roll').textContent = Math.round(roll);
         // 限制经度在边界内
         if (longitude < boundary.west) {
             longitude = boundary.west;
         } else if (longitude > boundary.east) {
             longitude = boundary.east;
         }
         
         // 限制纬度在边界内
         if (latitude < boundary.south) {
             latitude = boundary.south;
         } else if (latitude > boundary.north) {
             latitude = boundary.north;
         }
         // 如果相机超出了边界,将其拉回边界内
         if (longitude !== Cesium.Math.toDegrees(cartographicPosition.longitude) || 
             latitude !== Cesium.Math.toDegrees(cartographicPosition.latitude)) {
             
             // 保持相机高度不变,只调整水平位置
             const newPosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);
             // viewer.camera.position = newPosition;
             // console.log(viewer.camera)
             // 保持相机方向不变
             // viewer.camera.direction = viewer.camera.direction.clone();
             // viewer.camera.up = viewer.camera.up.clone();
             // viewer.camera.heading = 0
             // viewer.camera.roll = 0
             viewer.camera.setView({
                 destination: newPosition,
                 orientation:{
                       heading:0,
                     roll :  6.283185307179586,
                        pitch : viewer.camera.pitch,
                 }
               
             }
                 
             )
         }
     }

     // 注册每帧更新事件
     viewer.scene.postUpdate.addEventListener(constrainCameraToBoundary);

     // 重置视图按钮
     document.getElementById('resetView').addEventListener('click', function() {
         viewer.camera.setView({
             destination: Cesium.Cartesian3.fromDegrees(centerLongitude, centerLatitude, 1500000)
         });
     });

     // 添加一些示例标记以增强可视化
     const markers = [
         {lon: boundary.west, lat: boundary.south, name: "西南角"},
         {lon: boundary.east, lat: boundary.south, name: "东南角"},
         {lon: boundary.east, lat: boundary.north, name: "东北角"},
         {lon: boundary.west, lat: boundary.north, name: "西北角"}
     ];

     markers.forEach(marker => {
         viewer.entities.add({
             position: Cesium.Cartesian3.fromDegrees(marker.lon, marker.lat),
             point: {
                 pixelSize: 10,
                 color: Cesium.Color.RED
             },
             label: {
                 text: marker.name,
                 font: '14px Helvetica',
                 fillColor: Cesium.Color.WHITE,
                 horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
                 verticalOrigin: Cesium.VerticalOrigin.BOTTOM
             }
         });
     });
 </script>
</body>
</html>
相关推荐
格林威1 天前
多相机微秒级对齐:硬件触发 vs PTP(IEEE 1588)方案实战对比
开发语言·人工智能·数码相机·机器学习·计算机视觉·视觉检测·机器视觉
云飞云共享云桌面1 天前
广东精密机械设备工厂8-10人共享一台SolidWorks设计服务器
运维·服务器·3d·自动化·汽车·制造
xcLeigh1 天前
Unity基础:Camera相机组件详解——透视投影与正交投影的区别
数码相机·unity·游戏引擎
在世修行1 天前
从零打造 C# 工业视觉检测系统(四):海康威视 MVS SDK 入门与相机枚举实战
数码相机·c#·视觉检测
AI视觉网奇1 天前
bambu-studio-ai 踩坑实战笔记
人工智能·3d
m0_749492222 天前
2026平价运动相机选择指南:科美锐(Komery)PX3与另外两款相机的功能取舍拆解
数码相机
沐梯子2 天前
AI视频工具大战升级!PixPix首发接入MiniMaxH3大战Seedance
数码相机
c#上位机2 天前
线扫相机的帧触发
数码相机
MagicUrban2 天前
经纬管网MagicPipe3D V3.8.1发布,参数化自主可控三维管线建模工具
3d·gis·智慧城市·bim·地下管网
LONGZETECH2 天前
传统实训 vs 仿真实训:从技术架构拆解新能源汽车教学的范式之变
c语言·3d·unity·架构·汽车