当前位置: 首页 > news >正文

Cesium 导航模块设计

Cesium 导航模块设计

js/cesium/navigation/ 是 3D 地图 iframe 内的 路线预览 + 实时导航 子系统。对外通过 createCesiumNavigation(viewer, options) 创建实例,由 js/cesium/index.js 在路线查询、换层、GPS 推送等场景下调用。


目录

  • 设计目标
  • 架构概览
  • 两种工作模式
  • 文件说明
  • 对外 API 摘要
  • 与上层集成
  • 扩展与修改指引
  • 目录结构
  • 深度章节
    • 1. 路径几何与显示路径构建
    • 2. past / feature 路线切割
    • 3. GPS 匹配与平滑插值
    • 4. 路线材质与 flow 流动箭头
    • 5. 导航跟车相机
    • 6. 导航进度与多段续播
    • 7. 路线数据预处理

设计目标

  1. 预览态:查询路线后,在当前楼层绘制多条备选路线(半透明灰/高亮蓝),自动 flyTo 到合适视角。
  2. 导航态:开始实时导航后,GPS 点沿路线平滑插值移动,路线按进度切割为 已走过(past) / 未走过(feature),相机跟车。
  3. 多段路线:一条 routeNo 可含多个 segment(跨楼层);单段到达后通过 positionUpdate 通知父页面续播下一段(transitionSegment)。
  4. 可配置:图标、颜色、流动箭头、相机视角等通过 options / updateConfig 调整,无需改业务逻辑。

架构概览

采用 工厂 + 共享 state/config 装配,模块间通过 createXxx({ viewer, state, ... }) 注入依赖。

                    createCesiumNavigation(viewer, options)│┌─────────────────────────┼─────────────────────────┐│                         │                         │constants.js              state.js                  events.js(默认 config +              (运行时可变                (on/off/emit)SMOOTH_CONFIG)               状态容器)│                         │└────────────┬────────────┘│index.js(装配顺序)│┌──────────────────┼──────────────────┐│                  │                  │
route-materials    route-display    smooth-motion(材质/flow动画)    (显示路径/切割)   (GPS插值)│                  │                  │└────────┬─────────┴────────┬─────────┘│                  │route-handlers      nav-camera(预览绘制/flyTo)      (跟车相机)│                  │route-progress    navigation-controller(进度/positionUpdate)  (启停/GPS入口)│                  │└────────┬─────────┘│api.js

数据分层

层级 文件 内容 生命周期
静态配置 constants.js 默认 options、SMOOTH_CONFIG 创建时 merge;updateConfig 可改
运行时状态 state.js 路线、实体、state.smooth init → 导航 → destroy
视角配置 nav-view-conf.js 第一/第三人称、2D 俯视参数 作为 config 一部分

易混淆概念

名称 位置 含义
routeStyle.flow config 路线 材质箭头流动(视觉)
state.smooth state GPS 位置插值 运行时状态
SMOOTH_CONFIG constants 插值/几何 算法常量
routeDisplay.positions state 带圆角的 显示路径 采样点
routeData.points state 当前 segment 原始节点 坐标

两种工作模式

预览态(isNavigating === false

  • 触发:api.init({ routes, waypoints, currentFlId, routeNo, segmentIndex })
  • 绘制当前楼层预览线、起终点,并 flyToCurrentFloorRoutes 取景
  • updatePosition 仅更新箭头 entity

详见 2. past / feature 路线切割 中的预览分支。

导航态(isNavigating === true

  • 触发:api.startRealTimeNavigation()
  • 清除预览线,启用 past/feature 双色线与跟车相机
  • updatePosition → GPS 匹配与平滑插值 → 路线切割与进度上报

详见 3. GPS 匹配与平滑插值、5. 导航跟车相机。


文件说明

文件 职责
index.js 装配入口;refs 解决 navigationrouteProgress 循环依赖
api.js 对外 API;applySegmentContext 统一写入 state
constants.js 默认 options、SMOOTH_CONFIG、到达阈值
state.js createNavState() 运行时容器
nav-view-conf.js 相机三种视角默认参数
floor-height.js 从 store 读楼层高度
route-preprocess.js 路线预处理 → §7
route-path-geometry.js 路径纯几何 → §1
route-display.js 显示路径、切割、距离 → §1、§2
route-materials.js 材质与 flow → §4
route-handlers.js 预览绘制、flyToinitRoutePrimitives
smooth-motion.js GPS 插值 → §3
nav-camera.js 跟车相机 → §5
navigation-controller.js 启停、handleNewPosition → §3
route-progress.js 进度与续播 → §6
entities.js 定位点、途径点、起终点、实点
events.js 轻量事件总线

对外 API 摘要

方法 说明
init(payload) 销毁旧态并进入预览
transitionSegment(payload) 多段续播换层,保持 isNavigating
startRealTimeNavigation() 进入导航态
stopNavigation() 停止导航,回到预览
updatePosition([lon, lat, alt]) 预览更新箭头 / 导航推送 GPS
updateRealPoint(...) 可选 GPS 实点红点
updateConfig(partialOptions) 热更新样式、flow、viewType
destroy() 释放资源
on / off 订阅 positionUpdate 等事件

positionUpdate 字段与续播逻辑见 §6。


与上层集成

js/cesium/index.js

  • resetPosition / changFloorshowRouteDetailinittransitionSegment
  • postMessage updatePositionnavSystem.updatePosition
  • navSystem.on('positionUpdate', handleRoutePositionUpdate)postMessage 父页面

换层续播时模型 autoFly: false,避免与跟车相机冲突;预览取景由 flyToCurrentFloorRoutes 负责。


扩展与修改指引

需求 建议修改
线宽、到达阈值、插值速度 constants.js
默认颜色、箭头流动 constants.jsupdateConfig
相机跟车 nav-camera.jsnav-view-conf.js → §5
GPS 匹配 / 插值 navigation-controller.jssmooth-motion.js → §3
切割 / 预览绘制 route-display.jsroute-handlers.js → §2
进度 / route-over route-progress.js → §6

新增模块时在 index.js 注册工厂;双向依赖用 refs 延迟引用。


目录结构

navigation/
├── README.md
├── index.js
├── api.js
├── constants.js
├── state.js
├── events.js
├── floor-height.js
├── nav-view-conf.js
├── route-preprocess.js
├── route-path-geometry.js
├── route-display.js
├── route-materials.js
├── route-handlers.js
├── smooth-motion.js
├── nav-camera.js
├── navigation-controller.js
├── route-progress.js
└── entities.js

深度章节

以下章节说明模块中最复杂、最常改动的逻辑。


1. 路径几何与显示路径构建

涉及文件route-path-geometry.jsroute-display.jsrebuildRouteDisplayPositionsnodesToDisplayPositions

导航中有两套「路径」概念,不要混用:

名称 存储位置 来源 用途
控制点 / 节点路径 routeData.pointssmooth.routeCartesian 接口节点 nodes[].pos GPS 路段匹配、passDistance 累加
显示路径 routeDisplay.positions 控制点 + 几何加工 绘制折线、past/feature 切割、插值沿路线走

1.1 控制点 → 显示路径

routeData.points(经纬度节点)→ rebuildRouteCartesian() → smooth.routeCartesian(Cartesian3)→ buildRouteDisplayWithLocalCorners(controlPoints)→ routeDisplay.positions(高密度采样点)

预览态 addBackgroundRoutePrimitive 也走 nodesToDisplayPositionsbuildRouteDisplayWithLocalCorners,保证预览线与导航线几何一致。

1.2 buildRouteDisplayWithLocalCorners 策略

route-path-geometry.js 中的核心算法:

  1. 直线段:按 STRAIGHT_SAMPLE_STEP_M(默认 1m)线性插值,保持折线感。
  2. 折点(内角 < 170°):在顶点两侧各取 CORNER_BLEND_M(默认 1m)切点 A、B,用三次贝塞尔连接 A→B,绕过尖角。
  3. 近直线(内角 ≥ 170°)skipCornerCut,直接连线,避免无意义圆角。

相关常量均在 SMOOTH_CONFIG 中。

1.3 其他几何工具

函数 作用
pointToSegmentInfoCartesian 点到线段垂足、参数 t、距离
buildSmoothPathFromControlPoints Catmull-Rom 全曲线(插值 fallback)
buildArcLengthTable / samplePathByNormalizedT 弧长均匀采样,插值按路径长度而非直线 t
pushUniqueCartesian 去重相邻重合点(阈值 0.01m)

1.4 进度在显示路径上的表示

findClosestOnRouteDisplay(point)routeDisplay.positions 上找最近线段,返回:

  • index:采样段下标
  • t:段内参数 [0,1]
  • 逻辑进度 = index + t(用于切割与节流)

这与 routeData.points 上的 currentSegmentIndex(GPS 匹配用)是不同坐标系。


2. past / feature 路线切割

涉及文件route-display.jsroute-handlers.jsroute-materials.js

导航态下路线拆成四条 primitive(描边 + 主线 × past/feature):

routePrimitives.pastBorder / past      ← 已走过(灰色)
routePrimitives.featureBorder / feature ← 未走过(高亮色 + flow 箭头)

2.1 切割流程

GPS 插值点 cartesian→ findClosestOnRouteDisplay(cartesian)→ buildSplitFromRouteSample(index, t)past:  positions[0..index] + 垂足future: 垂足 + positions[index+1..end]→ applyRouteStylePrimitives(past, future)→ 重建四条 Polyline Primitive

updateRouteProgressFromPoint 负责上述链路,并由 syncRouteStyleFromCartesian(smooth-motion)在插值每帧调用。

防抖动

  • lastAppliedProgress:进度变化 < 0.002 时不重建 primitive
  • syncRouteStyleFromCartesian:120ms 内进度变化 < 0.003 时跳过(force 可打破)

后退allowBackward === true 时允许 past 回缩(needsRouteStyleResync 场景)。

2.2 预览态(无切割)

route-handlers.drawAllRoutesOnFloor

  • 遍历 state.routes,仅 segment.flId === currentFlId
  • 每段 addBackgroundRoutePrimitive → 写入 backgroundRoutes
  • 当前 routeNo 用 feature 材质高亮;备选线 BACKGROUND_ROUTE_INACTIVE_ALPHA 半透明
  • 激活路线起终点 entity;flyToCurrentFloorRoutes 框选当前层节点包围球

导航开始时 clearBackgroundRoutePrimitives,预览线全部移除。

2.3 导航启动初始化

route-handlers.initRoutePrimitives

rebuildRouteCartesian
→ rebuildRouteDisplayPositions
→ applyRouteStylePrimitives([], 全长 positions)   // past 空,feature 为整段

3. GPS 匹配与平滑插值

涉及文件navigation-controller.jssmooth-motion.jsconstants.jsSMOOTH_CONFIG

3.1 总览

GPS 不直接驱动 entity 位置,而是:

updatePosition→ handleNewPosition(匹配路线、算目标垂足)→ startSmoothMove(oldPoint, footOnRoute, waypoints)→ CallbackPositionProperty 每帧沿曲线移动→ syncRouteStyleFromCartesian(切割 past/feature)→ emitPositionUpdate(进度)

箭头 entity 与相机均绑定 getNavPositionProperty()

3.2 GPS 路段匹配(handleNewPosition

smooth.routeCartesian(节点折线)上:

  1. 对每个线段计算 GPS 到线段的距离与垂足 foot
  2. 优先取距离 ≤ SEGMENT_MATCH_THRESHOLD(100m)的段;若无则取最近段。
  3. 路口消歧t ≥ 0.999 时若下一段也更近,则 adjustedSegmentIndex + 1t ≤ 0.001 时类似回退。
  4. 更新 currentSegmentIndex;若后退则 needsRouteStyleResync = true
  5. 若当前插值点与垂足距离 < 0.25m,忽略(防抖动)。
  6. 前进跨段:在 waypoints 中插入跳过的节点,保证插值经过拐点。
  7. startSmoothMove(oldPoint, foot, waypoints, { pauseStyleUpdate: isReverseMatch })

3.3 插值曲线选取(startSmoothMove

条件 曲线来源
前进且已有显示路径 extractRouteDisplayPathBetween(from, to) 沿显示路径截取
后退或 pauseStyleUpdate buildSmoothPathFromControlPoints(Catmull-Rom)

弧长表 smoothCurveArc + samplePathByNormalizedT 保证匀速沿路径运动。

时长pathLength / NAV_MOVE_SPEED,夹在 [NAV_SMOOTH_DURATION_MIN, NAV_SMOOTH_DURATION_MAX]

3.4 state.smooth 在插值帧中的职责

getNavPositionProperty 的 Callback 每帧:

  1. t = elapsed / totalDuration 采样位置 → currentPoint
  2. smoothRouteSegments 上更新 currentSegmentIndex(顶点吸附 VERTEX_SNAP_EPS)。
  3. 前进:正常 syncRouteStyleFromCartesian
  4. 后退:smoothPauseRouteStyleUpdate,只 emitPositionUpdate,不切割路线。
  5. t >= 1:落到终点,强制同步样式,resetSmoothMotionState()

3.5 flow vs smooth(再次区分)

  • routeStyle.flow:线上箭头纹理动画(route-materials),与 GPS 位移无关。
  • state.smooth:标记点沿路线的运动状态(本章节)。
  • SMOOTH_CONFIG:匹配阈值、插值速度、曲线采样等算法参数。

4. 路线材质与 flow 流动箭头

涉及文件route-materials.jsconstants.jsrouteStyle.flow

4.1 材质类型

材质集 用途 缓存变量
getRouteMaterials() 导航 past/feature 双色线 routeMaterials
getInactivePreviewMaterials() 预览备选半透明线 inactivePreviewMaterials
getPreviewRouteMaterials(isActive) 预览当前/备选 组合上述

每条线 = border(描边纯色)+ line(chevron 箭头材质)。

4.2 flow 配置项

config.routeStyle.flow(默认见 constants.js):

字段 含义
animated feature 线是否流动
arrowSpeed 流动速度(0 = 静止图案)
arrowRepeat 箭头重复密度
arrowArmWidth / arrowAngle / arrowStroke 箭头形状
pastAnimated past 线是否单独动画
pastArrowRepeat past 线密度

updateConfig({ routeStyle: { flow } })applyFlowUniformsToRouteMaterials,无需重建全部材质。

4.3 动画驱动

ensureRouteAnimListener 注册 postRender

mats.feature.uniforms.time = (performance.now() - routeAnimStartMs) * 0.001;

仅当 flow.animated 且存在 feature primitive 时更新。预览激活线与导航 feature 线共用该监听器。


5. 导航跟车相机

涉及文件nav-camera.jsnav-view-conf.js

5.1 启用时机

navigation-controller.startRealTimeenableNavigationCamera

  • 清除 trackedEntitylookAtTransform(IDENTITY)
  • 注册 postRender 监听器 + 滚轮缩放
  • state.camera.isNavigationCameraMode = true

stop / destroydisableNavigationCamera

5.2 每帧跟车逻辑

positionProperty.getValue(currentTime)     → 当前位置
positionProperty.getValue(time + 0.4s)   → lookahead 位置(NAV_CAMERA_LOOKAHEAD_SEC)→ computeTravelHeading → targetHeading→ lerpAngle(smoothedHeading, targetHeading, 0.22)→ applyNavCameraForViewType(position, heading)

applyNavCameraForViewType 根据 resolveNavViewConf 计算相机偏移:

viewType 模式 row 含义 滚轮缩放
1 第一人称 固定 row 偏移
2 第三人称 navCameraRange(默认来自 row 缩放 range
3 2D 俯视 column × navCameraColumnScale 缩放 column

lookAtOffsetDown:注视点沿法线向下偏移,避免盯头顶。

5.3 与预览 flyTo 的关系

场景 相机行为
预览 flyToCurrentFloorRoutes flyToBoundingSphere + getRouteFlyToHpr()
导航中 updateConfig 改 viewType 立即 applyNavCameraForViewTypeflyToRouteDisplay
换层续播 transitionSegment 上层 changFloorautoFly: false,跟车相机接管

getRouteFlyToHpr:全局 2D 视图俯仰 -90°;否则用当前 viewType 的 pitch。


6. 导航进度与多段续播

涉及文件route-progress.jsroute-display.jsapi.jsindex.js

6.1 positionUpdate 数据字段

route-progress.emitPositionUpdate 构造并派发(导航中节流 200ms,段到达立即):

字段 含义
routeNo / flId / segmentIndex 当前路线与段
position [lon, lat, alt]
passDistance 沿节点折线累计已走距离(米)
endDistance 距段终点剩余距离;到达时为 0
nextPointDistance 到下一节点距离
polylineLength 段总长度
currentIndex / currentPointInfo / nextPointInfo 节点索引与信息
hasNextSegment 是否还有下一段
nextRouteInfo { index, flId } 下一段信息(到达时)

6.2 段到达判定

isRouteNavigationComplete 需同时满足:

  1. 距目的地节点 < NAV_ROUTE_END_THRESHOLD_M(1m)
  2. routeData.points 最后一段上且 t ≥ 0.85

6.3 防重复 route-over

机制 位置 作用
segmentTransitionLock state 到达有下一段时设为当前 segmentIndex,续播完成前忽略重复到达
emitPositionUpdate.cancel lodash throttle 到达时取消 pending 节流,立即派发
handleRoutePositionUpdate index.js 忽略迟到/重复的 endDistance === 0 事件

6.4 续播时序

段到达 → positionUpdate(endDistance: 0, hasNextSegment: true)→ index.js sendMessageToParent('route-over')→ 父页面换层 / 更新 routeData.currentRouteInfo→ changFloor({ changeFloor: true })  // autoFly: false→ showRouteDetail → navSystem.transitionSegmentapplySegmentContext(新 segmentIndex / flId)reset smooth 中间态navigation.startRealTime()   // 保持 isNavigating

最后一段到达 → onRouteCompletenavigation.stop() → 回到预览态并重绘楼层路线。

6.5 一层多段轨迹的预览取景

同楼层多个 segment 时,flyToCurrentFloorRoutes 收集当前路线在该层 所有 segment 的节点 做包围球,而非只框第一段。详见 route-handlers.js


7. 路线数据预处理

涉及文件route-preprocess.jsfloor-height.js

api.init / transitionSegment 在写入 state.routes 前调用 preprocessRoutes(routes, getHeight)

步骤 说明
补 Z pos.length === 2push(getFloorHeight(flId) + 0.2)
枚举 turnTurnTypetransitTransitType
POI 展开 node.posList 插入虚拟节点 nodeId_poi_{i}
去重 相邻节点经纬度重合时,优先去掉 _poi_ 节点
目的地文案 type == 1 且无 address 时填「目的地」

预处理后的结构:

routes[].segments[].nodes[].pos  // [lon, lat, alt]

segmentIndexcurrentFlIdapi 入参决定,不在预处理中修改。

http://www.gsyq.cn/news/1500998.html

相关文章:

  • 2026年近期河北钻裂一体机生产商可靠选择指南 - 品牌鉴赏官2026
  • 数据的加密与解密(01:50)
  • 2026年Q2四川制冷服务对接推荐:四川冰雪人等企业解析 - 优质品牌商家
  • 018华夏之光永存,助力国家科技破局:先进制程(7nm及以下)全流程EDA工具链专项
  • 【Agent Harness实战】我给 Agent 装了一套“神经系统”,它现在比我还敏感
  • 学生可用的步态识别课程设计全套材料:Python源码+预训练模型+详细PDF文档
  • 广州 GEO 服务商深度测评:2026 年五大优质品牌与全意图 GEO 核心价值 - GEO优化
  • 非公度量子系统的谱分析方法与高维嵌入技术
  • 2026年 表面瑕疵检测最新推荐榜单:薄膜/无纺布/带钢/铜箔/碳纤维/纸张/铝箔/板卷材/印刷专用检测系统与源头厂家精选 - 品牌发掘
  • HDC 2026 跨平台框架专题:HarmonyOS 生态下的跨端技术全景
  • 静态住宅ip哪家好?2026年静态住宅ip测评
  • 智能小区安防系统的设计(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_可以扫码或者私信
  • 手把手复现蓝桥杯‘缺失的数据’:用Python OpenCV和PyWavelets搞定数字水印提取
  • 动手搭一个可调直流电源:用Arduino+晶闸管实现AC-DC可控整流(附代码和波形分析)
  • 别再只看K线了!用Python复刻同花顺里的VR、VMA等10个量价指标(附完整代码)
  • 神经网络场论与弦论路径积分的融合研究
  • 别再只看K线了!用Python复刻同花顺的VR和VSTD指标,量化你的风险感知力
  • 工厂照明节能改造:成本控制、分区设计与零碳工厂照明指标
  • 告别混乱!用Quicker+Zotero6打造你的五星级文献管理系统(附详细配置脚本)
  • OpenGL实战:用中点Bresenham算法手搓一个椭圆(附完整C++代码)
  • MC9S12XE Flash模块实战:从底层驱动到安全解锁与EEE仿真
  • 如何快速提升戴森球计划工厂效率:3000+专业蓝图库完整指南
  • YOLOv5 6.0轻量手势数字检测包:1908张清洗图+4MB终版权重+完整训练可视化
  • 2026年 PLC控制柜实力厂家推荐榜:技术硬核与稳定可靠之选,plc控制柜源头厂家、自动化控制系统厂家专业榜单解析 - 品牌发掘
  • 如何用VDesk实现3倍工作效率提升:Windows虚拟桌面的智能管理实践
  • SAP BOM查询实战:CS11到CS15,哪个事务码才是你的菜?(附BAPI调用避坑点)
  • 怎样快速掌握AI全自动短视频制作:Pixelle-Video新手完整指南
  • CPT Markets:聚焦细节,看看外汇领域风控思路的关键路径
  • Android步行/驾车路线规划Demo:百度地图SDK集成即用工程
  • 还在人工剪视频?2026年五大跨境电商AI视频生成工具推荐