cesium

Posted on:January 22, 2025 at 03:56 PM
预计阅读时长:5 min read 字数:854

目录

basic

代码
src/components/react/cesium/template.tsx
1import { useEffect, useRef, useReducer } from "react"; 2import { Viewer, Ion, IonWorldImageryStyle, ImageryLayer } from "cesium"; 3import "cesium/Build/Cesium/Widgets/widgets.css"; 4Ion.defaultAccessToken = 5 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIwOWRkMzFlYS0yMDVhLTRkNzYtYWJmMC1hMmE1NjljN2MyNjMiLCJpZCI6NzMzNDQsImlhdCI6MTYzNjgxNDEzNX0.Q2MfD_lkQgsJ-R3NPfYjS9QA9q_j4Py8DktYKsPmZNg"; 6 7// 定义状态类型 8type State = { 9 viewer: Viewer | null; 10 scene: any | null; 11}; 12 13// 定义action类型 14type Action = 15 | { type: "INIT_VIEWER"; payload: { viewer: Viewer; scene: any } } 16 | { type: "DESTROY_VIEWER" }; 17 18// 创建reducer函数 19function cesiumReducer(state: State, action: Action): State { 20 switch (action.type) { 21 case "INIT_VIEWER": 22 return { 23 ...state, 24 viewer: action.payload.viewer, 25 scene: action.payload.scene, 26 }; 27 case "DESTROY_VIEWER": 28 return { 29 ...state, 30 viewer: null, 31 scene: null, 32 }; 33 default: 34 return state; 35 } 36} 37 38export default function Control() { 39 const cesiumRef = useRef(null); 40 const [state, dispatch] = useReducer(cesiumReducer, { 41 viewer: null, 42 scene: null, 43 }); 44 45 useEffect(() => { 46 if (cesiumRef.current && !state.viewer) { 47 const viewer = new Viewer(cesiumRef.current, { 48 terrainProvider: undefined, 49 baseLayerPicker: false, 50 geocoder: false, 51 homeButton: false, 52 sceneModePicker: false, 53 navigationHelpButton: false, 54 animation: false, 55 timeline: false, 56 fullscreenButton: false, 57 baseLayer: ImageryLayer.fromWorldImagery({}), 58 }); 59 60 //@ts-ignore 61 viewer._cesiumWidget._creditContainer.parentNode.removeChild( 62 //@ts-ignore 63 viewer._cesiumWidget._creditContainer 64 ); 65 66 const scene = viewer.scene; 67 const canvas = viewer.canvas; 68 canvas.setAttribute("tabindex", "0"); 69 canvas.onclick = () => canvas.focus(); 70 71 dispatch({ type: "INIT_VIEWER", payload: { viewer, scene } }); 72 73 const handleWheel = (event: WheelEvent) => { 74 event.stopPropagation(); 75 }; 76 cesiumRef.current.addEventListener("wheel", handleWheel); 77 78 return () => { 79 viewer.destroy(); 80 dispatch({ type: "DESTROY_VIEWER" }); 81 }; 82 } 83 }, []); 84 85 return ( 86 <div> 87 <div 88 onDoubleClick={() => { 89 if (cesiumRef.current) { 90 if (document.fullscreenElement) { 91 document.exitFullscreen(); 92 } else { 93 cesiumRef.current.requestFullscreen(); 94 } 95 } 96 }} 97 ref={cesiumRef} 98 style={{ width: "100%", height: "500px", userSelect: "none" }} 99 /> 100 </div> 101 ); 102} 103

tips

  • cesium 鼠标左键旋转右键平移
let controller = viewer.scene.screenSpaceCameraController;
controller.tiltEventTypes = [Cesium.CameraEventType.RIGHT_DRAG];
controller.rotateEventTypes = [Cesium.CameraEventType.LEFT_DRAG];

google earth demo

components

实时跟踪

当前位置定位:刷新页面,查看当前位置
当前坐标: 39.00000, 116.00000
代码
src/components/react/cesium/work/trackline.tsx
1import { Cartesian3, Color, Entity, PointGraphics } from "cesium"; 2import { useCesium } from "../hooks/useCesium"; 3import { useCallback, useEffect, useState } from "react"; 4import "cesium/Build/Cesium/Widgets/widgets.css"; 5import { Button } from "@components/react/shadcn/ui/button"; 6 7export default () => { 8 const { cesiumContainerRef, viewer } = useCesium(); 9 const [currentEntity, setCurrentEntity] = useState<Entity>(); // 存储位置实体 10 const [watchId, setWatchId] = useState<number>(null); 11 12 type CurrentLocation = { 13 latitude?: number; 14 longitude?: number; 15 }; 16 17 const [currentLocation, setCurrentLocation] = useState<CurrentLocation>({ 18 latitude: 39, 19 longitude: 116, 20 }); 21 22 // 初始化时检查地理位置权限 23 useEffect(() => { 24 if (!navigator.geolocation) { 25 alert("浏览器不支持地理位置功能"); 26 return; 27 } 28 29 navigator.permissions 30 .query({ name: "geolocation" }) 31 .then(permissionStatus => { 32 if (permissionStatus.state === "denied") { 33 alert("请允许地理位置权限以使用此功能"); 34 } 35 }); 36 }, []); 37 38 // 更新地图上的位置标记 39 const updatePositionMarker = useCallback( 40 (longitude: number, latitude: number) => { 41 if (!viewer.current) return; 42 43 // 移除旧实体 44 if (currentEntity) { 45 viewer.current.entities.remove(currentEntity); 46 } 47 48 // 创建新实体 49 const newEntity = viewer.current.entities.add({ 50 position: Cartesian3.fromDegrees(longitude, latitude), 51 point: new PointGraphics({ 52 color: Color.RED, 53 pixelSize: 10, 54 outlineColor: Color.WHITE, 55 outlineWidth: 2, 56 }), 57 model: { 58 uri: "/assets/models/gltf/Cesium_Man.glb", 59 scale: 2, 60 }, 61 }); 62 63 setCurrentEntity(newEntity); 64 65 // 移动视角到当前位置 66 viewer.current.camera.flyTo({ 67 destination: Cartesian3.fromDegrees(longitude, latitude, 1000), 68 orientation: { 69 heading: 0, 70 roll: 0, 71 }, 72 }); 73 }, 74 [viewer.current, currentEntity] 75 ); 76 77 // 监听位置变化 78 const watchCurrentLocation = useCallback(() => { 79 const id = navigator.geolocation.watchPosition( 80 position => { 81 const { latitude, longitude } = position.coords; 82 setCurrentLocation({ latitude, longitude }); 83 updatePositionMarker(longitude, latitude); 84 }, 85 error => { 86 switch (error.code) { 87 case error.PERMISSION_DENIED: 88 alert("用户拒绝了地理位置请求"); 89 break; 90 case error.POSITION_UNAVAILABLE: 91 alert("无法获取位置信息"); 92 break; 93 case error.TIMEOUT: 94 alert("请求超时"); 95 break; 96 default: 97 alert("未知错误"); 98 } 99 }, 100 { 101 enableHighAccuracy: true, 102 timeout: 10000, 103 maximumAge: 0, 104 } 105 ); 106 setWatchId(id); 107 }, [updatePositionMarker]); 108 109 // 清理 110 useEffect(() => { 111 return () => { 112 if (watchId) navigator.geolocation.clearWatch(watchId); 113 if (currentEntity && viewer.current) { 114 viewer.current.entities.remove(currentEntity); 115 } 116 }; 117 }, [watchId, currentEntity]); 118 119 return ( 120 <> 121 <div 122 ref={cesiumContainerRef} 123 style={{ 124 width: "100%", 125 height: "500px", 126 userSelect: "none", 127 position: "relative", 128 zIndex: 0, 129 }} 130 /> 131 <Button onClick={watchCurrentLocation}>开始实时定位</Button> 132 133 <div className="position-info"> 134 当前坐标: {currentLocation.latitude?.toFixed(5)},{" "} 135 {currentLocation.longitude?.toFixed(5)} 136 </div> 137 </> 138 ); 139}; 140

历史轨迹

第一版:实现人员跟踪

漫思

vite-plugin-cesium的原理

vite-plugin-cesium

vite-plugin-cesium 是一个用于简化在 Vite 项目中集成 CesiumJS 的插件。它的核心原理是通过自动化配置和资源处理,解决 CesiumJS 在 Vite 中常见的静态资源加载、路径配置、模块兼容性等问题。以下是其工作原理的详细分析:


1. 自动依赖管理与路径别名

  • Cesium 安装:插件会自动安装 cesium npm 包,或确保用户已正确安装。
  • 别名配置:在 Vite 配置中设置路径别名,将 cesium 指向 node_modules/cesium,简化模块导入:
    // vite.config.js
    export default {
      resolve: {
        alias: {
          cesium: path.resolve(__dirname, "node_modules/cesium"),
        },
      },
    };
    

2. 静态资源处理

CesiumJS 依赖大量静态资源(如 WebAssembly、图片、CSS 等),这些资源需要被正确加载。

  • 资源复制:插件在开发阶段将 node_modules/cesium/Build/Cesium 下的静态资源复制到 Vite 项目的 public 目录(或指定路径),确保开发服务器能访问它们。
  • 生产构建处理:在构建阶段,资源会被打包到输出目录(如 dist),并通过 CESIUM_BASE_URL 配置基础路径。

3. Web Workers 与 WASM 配置

CesiumJS 使用 Web Workers 和 WebAssembly 提升性能,但浏览器安全策略要求这些文件必须通过正确的 MIME 类型和路径加载。

  • Worker 文件处理:通过 viteworker 插件配置,将 .js 文件标记为 Web Workers,确保其被正确隔离和加载。
  • WASM 配置:配置 .wasm 文件作为异步模块加载,并设置正确的 MIME 类型(如 application/wasm)。

4. 环境变量注入

  • CESIUM_BASE_URL:插件自动设置环境变量 CESIUM_BASE_URL,指向静态资源的基础路径(如开发环境的 /public 或生产环境的相对路径)。CesiumJS 通过此变量加载资源:
    window.CESIUM_BASE_URL = "/";
    

5. Vite 构建配置调整

  • 资源包含规则:扩展 assetsInclude 配置,让 Vite 识别 Cesium 相关文件(如 .glb, .czml 等)。
  • CommonJS 转换:CesiumJS 使用 CommonJS 模块,插件通过 @rollup/plugin-commonjs 将其转换为 ESM,确保与 Vite 的 ESM 环境兼容。
  • 依赖优化:在 optimizeDeps 中排除 Cesium,避免 Vite 预构建时处理其复杂依赖。

6. 开发服务器中间件配置

  • CORS 头设置:为开发服务器添加中间件,设置 Cross-Origin-Opener-PolicyCross-Origin-Embedder-Policy 头,解决 Web Worker 的跨域限制。
  • 静态资源代理:确保对 Cesium 资源的请求被正确代理到本地文件或 CDN。

7. 生产优化

  • 代码拆分:配置 Rollup 避免拆分关键 Cesium 模块,保持运行时完整性。
  • 资源压缩:启用 build.minify 压缩代码,同时确保 WASM 等二进制文件不被破坏。

总结

vite-plugin-cesium 通过以下方式简化开发:

  1. 自动化配置:隐藏复杂的路径、资源处理和模块转换逻辑。
  2. 环境适配:统一开发和生产环境下的静态资源加载行为。
  3. 兼容性处理:解决 CJS/ESM 模块差异和浏览器安全策略问题。

开发者只需安装插件并导入 Cesium,无需手动处理上述细节,即可快速启动 3D 地理可视化项目。