cesium

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

目录

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 地理可视化项目。

cesium maximumScreenSpaceError

A tile’s screen space error is roughly equivalent to the number of pixels wide that would be drawn if a sphere with a radius equal to the tile’s geometric error were rendered at the tile’s position. If this value exceeds maximumScreenSpaceError the tile refines to its descendants.

瓦片的屏幕空间误差大致相当于如果以与瓦片几何误差相等的半径绘制一个球体,并在瓦片位置渲染时,该球体所绘制的像素宽度。如果此值超过 maximumScreenSpaceError ,则瓦片将细化到其子代。

Depending on the tileset, maximumScreenSpaceError may need to be tweaked to achieve the right balance. Higher values provide better performance but lower visual quality.

根据瓦片集的不同,可能需要调整 maximumScreenSpaceError 以实现正确的平衡。更高的值提供更好的性能,但视觉质量较低。

cesium 用帧缓存实现了什么