viewPresets.js 3.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. /**
  2. * 视角预设与进入引导(票 30):纯函数、不 import cesium、不读 DOM。
  3. *
  4. * 目的:维护者「完全找不到目标位置,无法判断 DEM 与精细影像是否部署成功」。
  5. * 给出一个**命名视角**(home)与进入引导,让系统一进来就把镜头带到沅陵县城。
  6. *
  7. * 坐标口径:维护者给的 `110.39028164, 28.44367870` 是 **CGCS2000 · 天地图**坐标;
  8. * CGCS2000 与 WGS-84 在本项目的精度要求下视为一致,直接喂 `Cartesian3.fromDegrees`。
  9. */
  10. /** 进入引导的默认文案与时长。 */
  11. export const INTRO_DEFAULTS = {
  12. enabled: true,
  13. title: '点击进入系统',
  14. subtitle: '',
  15. /** 星球自转一圈的秒数(0 = 不自转)。 */
  16. spinSeconds: 48,
  17. }
  18. /**
  19. * 默认「家」视角:沅陵县城上空,俯角 -55°、距离 12 km —— 这个距离下能同时看到
  20. * 县城轮廓与周边地形起伏(实测 4.6 km 是街区级、12 km 是县域级)。
  21. */
  22. export const HOME_DEFAULTS = {
  23. lon: 110.39028164,
  24. lat: 28.4436787,
  25. height: 0,
  26. heading: 0,
  27. pitch: -55,
  28. range: 12000,
  29. }
  30. function readCoord(value, fallback) {
  31. const num = Number(value)
  32. return Number.isFinite(num) ? num : fallback
  33. }
  34. /**
  35. * 取「家」视角(配置优先,缺项用默认)。
  36. * @param {object} mapConfig 归一化后的 `config.map`
  37. */
  38. export function planHome(mapConfig = {}) {
  39. const home = (mapConfig && mapConfig.home) || {}
  40. return {
  41. lon: readCoord(home.lon, HOME_DEFAULTS.lon),
  42. lat: readCoord(home.lat, HOME_DEFAULTS.lat),
  43. height: readCoord(home.height, HOME_DEFAULTS.height),
  44. heading: readCoord(home.heading, HOME_DEFAULTS.heading),
  45. pitch: readCoord(home.pitch, HOME_DEFAULTS.pitch),
  46. range: readCoord(home.range, HOME_DEFAULTS.range),
  47. label: typeof home.label === 'string' && home.label ? home.label : '目标区域',
  48. }
  49. }
  50. /**
  51. * 取进入引导配置。
  52. * @param {object} mapConfig 归一化后的 `config.map`
  53. */
  54. export function planIntro(mapConfig = {}) {
  55. const intro = (mapConfig && mapConfig.intro) || {}
  56. return {
  57. enabled: intro.enabled !== false,
  58. title: typeof intro.title === 'string' && intro.title ? intro.title : INTRO_DEFAULTS.title,
  59. subtitle: typeof intro.subtitle === 'string' ? intro.subtitle : INTRO_DEFAULTS.subtitle,
  60. spinSeconds: Math.max(0, readCoord(intro.spinSeconds, INTRO_DEFAULTS.spinSeconds)),
  61. }
  62. }
  63. /** 「归位」按钮与引导文案里用的短标签。 */
  64. export function homeLabel(home) {
  65. const current = home || HOME_DEFAULTS
  66. const lon = Number(current.lon).toFixed(4)
  67. const lat = Number(current.lat).toFixed(4)
  68. return `${current.label || '目标区域'}(${lon}, ${lat})`
  69. }
  70. /**
  71. * 视角是否已经落在预设上(容差内)—— 归位按钮据此决定是否禁用/高亮,
  72. * 走查也用它断言「点一下真的回去了」。
  73. * @param {object} state `cameraControl.getState()`
  74. * @param {object} home `planHome()` 的结果
  75. * @param {object} [tolerance] 各分量容差
  76. */
  77. export function isAtHome(state, home, tolerance = { lon: 1e-4, lat: 1e-4, range: 0.02, angle: 0.5 }) {
  78. if (!state || !state.pivot || !state.hpr) return false
  79. return (
  80. Math.abs(state.pivot.lon - home.lon) <= tolerance.lon &&
  81. Math.abs(state.pivot.lat - home.lat) <= tolerance.lat &&
  82. // 距离用相对容差(2%):弹簧臂是连续量,绝对容差在不同量级下没意义。
  83. Math.abs(state.hpr.range - home.range) <= Math.max(1, home.range * tolerance.range) &&
  84. Math.abs(((state.hpr.heading - home.heading + 540) % 360) - 180) <= tolerance.angle &&
  85. Math.abs(state.hpr.pitch - home.pitch) <= tolerance.angle
  86. )
  87. }