/** * 视角预设与进入引导(票 30):纯函数、不 import cesium、不读 DOM。 * * 目的:维护者「完全找不到目标位置,无法判断 DEM 与精细影像是否部署成功」。 * 给出一个**命名视角**(home)与进入引导,让系统一进来就把镜头带到沅陵县城。 * * 坐标口径:维护者给的 `110.39028164, 28.44367870` 是 **CGCS2000 · 天地图**坐标; * CGCS2000 与 WGS-84 在本项目的精度要求下视为一致,直接喂 `Cartesian3.fromDegrees`。 */ /** 进入引导的默认文案与时长。 */ export const INTRO_DEFAULTS = { enabled: true, title: '点击进入系统', subtitle: '', /** 星球自转一圈的秒数(0 = 不自转)。 */ spinSeconds: 48, } /** * 默认「家」视角:沅陵县城上空,俯角 -55°、距离 12 km —— 这个距离下能同时看到 * 县城轮廓与周边地形起伏(实测 4.6 km 是街区级、12 km 是县域级)。 */ export const HOME_DEFAULTS = { lon: 110.39028164, lat: 28.4436787, height: 0, heading: 0, pitch: -55, range: 12000, } function readCoord(value, fallback) { const num = Number(value) return Number.isFinite(num) ? num : fallback } /** * 取「家」视角(配置优先,缺项用默认)。 * @param {object} mapConfig 归一化后的 `config.map` */ export function planHome(mapConfig = {}) { const home = (mapConfig && mapConfig.home) || {} return { lon: readCoord(home.lon, HOME_DEFAULTS.lon), lat: readCoord(home.lat, HOME_DEFAULTS.lat), height: readCoord(home.height, HOME_DEFAULTS.height), heading: readCoord(home.heading, HOME_DEFAULTS.heading), pitch: readCoord(home.pitch, HOME_DEFAULTS.pitch), range: readCoord(home.range, HOME_DEFAULTS.range), label: typeof home.label === 'string' && home.label ? home.label : '目标区域', } } /** * 取进入引导配置。 * @param {object} mapConfig 归一化后的 `config.map` */ export function planIntro(mapConfig = {}) { const intro = (mapConfig && mapConfig.intro) || {} return { enabled: intro.enabled !== false, title: typeof intro.title === 'string' && intro.title ? intro.title : INTRO_DEFAULTS.title, subtitle: typeof intro.subtitle === 'string' ? intro.subtitle : INTRO_DEFAULTS.subtitle, spinSeconds: Math.max(0, readCoord(intro.spinSeconds, INTRO_DEFAULTS.spinSeconds)), } } /** 「归位」按钮与引导文案里用的短标签。 */ export function homeLabel(home) { const current = home || HOME_DEFAULTS const lon = Number(current.lon).toFixed(4) const lat = Number(current.lat).toFixed(4) return `${current.label || '目标区域'}(${lon}, ${lat})` } /** * 视角是否已经落在预设上(容差内)—— 归位按钮据此决定是否禁用/高亮, * 走查也用它断言「点一下真的回去了」。 * @param {object} state `cameraControl.getState()` * @param {object} home `planHome()` 的结果 * @param {object} [tolerance] 各分量容差 */ export function isAtHome(state, home, tolerance = { lon: 1e-4, lat: 1e-4, range: 0.02, angle: 0.5 }) { if (!state || !state.pivot || !state.hpr) return false return ( Math.abs(state.pivot.lon - home.lon) <= tolerance.lon && Math.abs(state.pivot.lat - home.lat) <= tolerance.lat && // 距离用相对容差(2%):弹簧臂是连续量,绝对容差在不同量级下没意义。 Math.abs(state.hpr.range - home.range) <= Math.max(1, home.range * tolerance.range) && Math.abs(((state.hpr.heading - home.heading + 540) % 360) - 180) <= tolerance.angle && Math.abs(state.hpr.pitch - home.pitch) <= tolerance.angle ) }