# Earth Mobile Center Country Highlight Plan ## Goal 移动端打开 Earth 国界图层后,用屏幕中心,也就是当前镜头正对的地球表面位置,自动识别所在国家,并高亮该国家国界。 桌面端仍保持现有 hover 行为。移动端不引入新的国界渲染体系,而是复用已有 `country-boundaries.js` 的 GeoJSON 命中和 hover 高亮能力。 ## Criteria for success 1. 移动端 `layout-mode-mobile` 下,国界图层开启后,屏幕中心所在国家会自动高亮。 2. 移动端旋转、缩放、巡航或自动旋转地球时,高亮会跟随镜头中心更新。 3. 屏幕中心落在海洋或没有命中地球时,国家高亮会清除。 4. 国界图层关闭时,不执行中心国家识别,也不显示残留高亮。 5. 桌面端 pointer hover 行为保持不变。 6. 移动端抽屉、搜索、设置、媒体、详情等前景 UI 打开时,不因为用户操作 UI 产生明显误高亮或抖动。 7. 中心识别有节流或状态缓存,不把 GeoJSON point-in-polygon 检测放到无条件每帧高频执行。 8. 实现后能通过本地静态检查或前端构建,并用移动端 viewport 手动或 Playwright 验证核心场景。 ## Existing pieces 当前项目已经具备大部分基础能力: - [frontend/public/earth/js/country-boundaries.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/country-boundaries.js) - `updateCountryBoundaryHover(coords)`:根据 `{ lat, lon }` 命中国家并更新高亮线。 - `clearCountryBoundaryHover()`:清除当前 hover 高亮。 - `getShowCountryBoundaries()`:判断国界线图层是否可见。 - [frontend/public/earth/js/utils.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/utils.js) - `screenToEarthCoords(clientX, clientY, camera, earth, domElement)`:屏幕坐标 raycast 到地球表面。 - `vector3ToLatLon(vector)`:地球本地坐标转经纬度。 - [frontend/public/earth/js/constants.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/constants.js) - `COUNTRY_BOUNDARY_CONFIG` 已定义普通国界线和 hover 国界线样式。 - 移动端布局状态已经通过 `layout-mode-mobile` body class 区分。 因此本需求的核心不是新增图层,而是补一个移动端中心取点控制器。 ## Non-goals - 不改变桌面端 hover 交互。 - 不引入旧低精度国界兜底;移动端中心国家能力必须复用生产 PMTiles/MVT 国界源。 - 不新增后端 API。 - 不把国家面填充做成新的 selected country 面状 shader。 - 不为移动端增加永久准星 UI,除非后续产品明确需要视觉准星。 ## Implementation plan ### 1. Add a small mobile center hover controller 新增一个轻量函数,建议放在现有主循环附近或单独模块,例如: ```text frontend/public/earth/js/mobile-center-country-highlight.js ``` 建议导出: ```js updateMobileCenterCountryHighlight({ camera, earth, renderer, now, isBlocked, }); clearMobileCenterCountryHighlight(); ``` 职责: 1. 判断是否处于移动端。 2. 判断国界图层是否开启。 3. 判断当前是否被移动端前景 UI 阻塞。 4. 对 renderer canvas 中心点做 raycast。 5. 命中地球后转经纬度。 6. 调用 `updateCountryBoundaryHover({ lat, lon })`。 7. 无命中或禁用时调用 `clearCountryBoundaryHover()`。 ### 2. Use canvas center, not window center 中心点应基于 renderer canvas rect 计算: ```js const rect = renderer.domElement.getBoundingClientRect(); const clientX = rect.left + rect.width / 2; const clientY = rect.top + rect.height / 2; ``` 这样在移动端安全区、地址栏变化、viewport resize 或 canvas 非全屏时仍然准确。 ### 3. Convert center point into country hover coords 复用已有工具: ```js const point = screenToEarthCoords(clientX, clientY, camera, earth, renderer.domElement); if (!point) { clearCountryBoundaryHover(); return; } const coords = vector3ToLatLon(point); updateCountryBoundaryHover(coords); ``` 注意:`screenToEarthCoords` 返回的是 earth local point,符合 `vector3ToLatLon` 的输入语义。 ### 4. Gate updates by mobile and foreground UI state 建议新增一个本地判断函数: ```js function isMobileCenterCountryHighlightBlocked() { return ( !document.body.classList.contains("layout-mode-mobile") || document.body.classList.contains("earth-search-open") || document.body.classList.contains("earth-settings-open") || document.body.classList.contains("earth-media-open") || document.body.classList.contains("earth-info-open") ); } ``` 如果移动端抽屉只是半收起、且没有覆盖中心视野,可以继续允许中心高亮。若实际体验里抽屉展开会遮挡中心点,再把 drawer open 状态纳入阻塞条件。 ### 5. Throttle and cache center updates GeoJSON polygon 命中不应该无条件每帧执行。 第一版建议: - `throttleMs = 120` - 缓存上次经纬度,中心点变化小于 `0.05` 度时跳过。 - 禁用、切回桌面、图层关闭、UI 阻塞时立即清除一次高亮。 伪代码: ```js if (now - lastUpdateAt < 120) return; if (Math.abs(coords.lat - lastLat) < 0.05 && Math.abs(coords.lon - lastLon) < 0.05) return; ``` ### 6. Wire into the Earth animation loop 在 [frontend/public/earth/js/main.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/main.js) 的动画循环中调用: ```js updateMobileCenterCountryHighlight({ camera, earth, renderer, now: performance.now(), isBlocked: isMobileCenterCountryHighlightBlocked(), }); ``` 这样自动旋转、手势旋转、缩放和巡航都会自然更新。 ### 7. Keep desktop hover unchanged 桌面 pointer hover 仍然走当前逻辑。 移动端中心高亮只在 `layout-mode-mobile` 下生效,不应该监听 pointer move,也不应该抢占 desktop hover 状态。 ### 8. Optional visual tuning 第一版复用: - `COUNTRY_BOUNDARY_CONFIG.hoverLineColor` - `COUNTRY_BOUNDARY_CONFIG.hoverLineOpacity` - `COUNTRY_BOUNDARY_CONFIG.hoverGlowOpacity` 如果移动端体验太强,可以后续加独立配置: ```js mobileCenterHoverLineOpacity mobileCenterHoverGlowOpacity ``` 但第一版不建议过早分叉样式。 ## Verification ### Static checks 1. `npm` 前端构建或现有 lint/typecheck 命令通过。 2. `rg` 确认新增函数只在移动端路径调用,不影响桌面 pointer hover。 3. `git diff --stat` 和目标文件 diff 确认改动范围集中。 ### Manual mobile checks 使用移动端 viewport,例如 390x844: 1. 打开 Earth。 2. 开启国界图层。 3. 转动地球到中国、美国、澳大利亚等大块陆地区域,确认中心国家国界高亮。 4. 转动到太平洋或印度洋,确认高亮消失。 5. 缩放地球,确认高亮仍跟随中心点。 6. 打开移动端搜索、设置、媒体或详情面板,确认没有明显误高亮或抖动。 7. 切回桌面 viewport,确认 hover 仍由鼠标位置控制。 ### Playwright smoke check 如果已有 Playwright 流程,建议补一个移动端 smoke: 1. 设置 viewport 为手机尺寸。 2. 打开 Earth 页面。 3. 开启国界图层。 4. 等待国界数据加载。 5. 截图确认中心附近国家边界有 hover 高亮线。 这个 smoke 不必断言具体国家名称,因为当前功能核心是视觉高亮;更稳定的自动化可以后续通过暴露 debug state 实现。 ## Risks and mitigations ### Polygon hit cost too高 风险:移动端设备上频繁 `featureContains` 可能带来卡顿。 缓解: - 使用 `120ms` 节流。 - 经纬度变化小于阈值时跳过。 - 后续如仍慢,再为 GeoJSON features 预计算 bbox,先 bbox 粗筛再 point-in-polygon。 ### UI blocking state 不完整 风险:某些移动端前景 UI 没有对应 body class,中心点被遮挡但高亮仍更新。 缓解: - 第一版覆盖现有主要 class。 - 验证时记录遗漏项,补充到 `isMobileCenterCountryHighlightBlocked()`。 ### Desktop hover 被移动端状态污染 风险:移动端中心高亮和桌面 hover 共用 `_hoveredFeature` 状态。 缓解: - 只在 `layout-mode-mobile` 下运行中心高亮。 - 切出 mobile 或图层关闭时调用一次 `clearCountryBoundaryHover()`。 - 不改 `updateCountryBoundaryHover()` 的语义。 ## Milestones 1. 设计落地:完成本 plan,明确目标和验收标准。 2. 最小实现:新增移动端中心取点 controller,并接入 animation loop。 3. 性能保护:加入节流、经纬度阈值和禁用态清理。 4. 验证:本地构建通过,移动端 viewport 手动检查通过。 5. 调优:根据截图或真机体验微调阻塞条件和节流阈值。