# Codex 指令:把 NavSea `pickup` 的“当前点击解读”迁移到 React + MapLibre 最后更新:`2026-04-11` 项目根:`/root/sourceserver/pbf` ## 1. 这份文档的用途 这是一份给 `Codex` 的中文任务指令。 目标不是讨论方案,而是让 `Codex` 在目标前端项目里,直接把当前 NavSea 测试页上的 `pickup / 当前点击解读` 逻辑迁移到 `React + MapLibre`。 这份指令要求实现结果尽量与当前测试页一致,而不是重新发明一套新逻辑。 ## 2. 你要实现的目标 请在目标前端项目中实现一套 NavSea 海图点击能力,点击后返回并展示“当前点击解读”。 这套逻辑必须满足: 1. 如果点击命中海面上或露出水面的可见对象: - 直接返回这个对象是什么 - 例如: - 灯塔 - 灯标 - 浮标 - 建筑物 - 地标 - 防波堤 - 栈桥 - 陆地 - 桥梁 - 危险物 2. 如果点击更适合按海底信息解释: - 返回当前水深 - 返回物体名(有的话) - 返回一句简单、普通钓鱼人能看懂的水底情况说明 - 返回“判断理由” 3. 不要让用户看见一堆内部调试术语。 4. 不要默认优先海底解释。 - 只要用户明确点到了看得见的物体,就优先按物体解释。 ## 3. 当前实现的唯一参考来源 迁移时,请以本仓库下面这些文件为准,不要自己脑补一套新规则: - 测试页主逻辑: - [src/pbf/navsea-pickup-test-karatsu-20nm.html](/root/sourceserver/pbf/src/pbf/navsea-pickup-test-karatsu-20nm.html) - pickup 解释器: - [src/pbf/navsea-pickup-interpreter.ts](/root/sourceserver/pbf/src/pbf/navsea-pickup-interpreter.ts) - [src/pbf/navsea-pickup-interpreter.js](/root/sourceserver/pbf/src/pbf/navsea-pickup-interpreter.js) - pickup 规则字典: - [src/pbf/navsea-pickup-rules.v1.json](/root/sourceserver/pbf/src/pbf/navsea-pickup-rules.v1.json) - 当前前端实现说明: - [NavSea_Native_Pickup_ReactNative_MapLibre_实现说明.md](/root/sourceserver/pbf/NavSea_Native_Pickup_ReactNative_MapLibre_实现说明.md) 当前测试页里真正要迁移的核心行为,重点看: - `handlePickup` - `buildDepthContext` - `collectTerrainSamples` - `buildTerrainInterpretation` - `buildFriendlyClickInterpretation` - `shouldPreferBBoxPrimary` ## 4. 当前业务口径 迁移时请严格保持下面这些口径。 ### 4.1 主键口径 - 主对象 id 使用: - `feature.id` - 当前前端可把它视为: - `fid` ### 4.2 图层口径 - 不允许前端手写一套新的 layer 名单 - 统一从: - `navsea-pickup-rules.v1.json` 读取: - `render_layer_groups.primary` - `render_layer_groups.area` - `render_layer_groups.info` - `render_layer_groups.ignore` ### 4.3 点击优先级口径 - 用户点到可见对象时: - 优先按对象解释 - 用户没点到明确对象时: - 再回退到海底信息解释 ### 4.4 “附近”口径 - 当前业务定义: - `附近 = 100 米内` - 不能再直接用固定 `px` 当业务口径 - 正确做法: 1. 先把 `100m` 换成当前 zoom 下对应的屏幕半径 2. 用这个半径做 `queryRenderedFeatures` 3. 再用真实 `distance_m` 二次过滤 4. 只有 `distance_m <= 100` 的对象,才算“附近” ### 4.5 用户文案口径 - 要用普通用户能看懂的话 - 不要把下面这些词直接当最终用户文案: - 坡折 - 脊 - 槽 - bathymetry - contour evidence - 可以保留在内部调试结构里 - 但正式展示应尽量写成: - `这里水底起伏比较明显,像一道坎。` - `这里像水底一条沟的边上。` - `这里像水底一个小包或小凸起边上。` - `这里水底看起来比较平。` - `这附近像礁边,鱼容易沿边活动。` - `当前位置只有少量海底辅助线证据,暂不判断明显坎沟。` ## 5. 需要迁移的功能拆分 请把实现拆成下面几层,不要把所有逻辑塞进一个组件里。 ### A. `pickup rules loader` 负责: - 加载 `navsea-pickup-rules.v1.json` - 对外提供: - `primary / area / info / ignore` - `display_name_overrides` - `layer_rules` ### B. `rendered feature interpreter` 优先复用或移植: - [src/pbf/navsea-pickup-interpreter.ts](/root/sourceserver/pbf/src/pbf/navsea-pickup-interpreter.ts) 负责: - 把 `queryRenderedFeatures` 返回的 feature 转成统一解释结构 - 对每个 feature 输出: - `fid` - `source_layer_std` - `object_domain` - `object_class` - `canonical_object_type` - `title` - `details` - `render` - `score` - `distance_m` ### C. `pickup query adapter` 负责把 React + MapLibre 的 map 实例封装成统一查询接口。 至少提供: 1. `queryExactFeatures(point)` 2. `queryBBoxFeatures(point, radiusPx)` 3. `queryRawFeatures(point, radiusPx)` 要求: - `exact` 查询层: - `area + primary + info` - `bbox` 查询层: - `primary + info` ### D. `depth context service` 负责: - 在对象未命中时,返回最近深度上下文 必须保持与测试页同样口径: - 当前采用: - `100 米` 口径 - 返回: - `nearest_depth_m` - `nearest_label` - `nearest_render_layer` - `nearest_distance_m` - `candidate_count` - `candidates` ### E. `terrain interpretation service` 负责: - 采样 `100 米` 内的海底证据 - 做规则判读 当前只看下面这些强相关层: - 深度/海底线: - `depth-contours` - `depth-contours-major` - `depth-contour-labels` - `depth-contours-major-labels` - `bathymetry-support` - `bathymetry-depth-labels` - 底质: - `bottom-material-labels` - 结构: - `submerged-structures` - `fishery-areas` - `hazard-polygons` - `navigation-hazard-polygons` 返回: - `local_depth_range_m` - `nearest_depth_m` - `contour_density` - `seabed_material` - `nearby_structures` - `hints` ### F. `friendly click interpretation service` 这是最重要的一层。 负责把内部 pickup / depth / terrain 结果,变成前端真正展示的“当前点击解读”。 输出必须分两种模式: 1. `海面/露出物` 2. `海底信息` 并返回: - `mode` - `headline` - `depth_text` - `object_name` - `shape_text` - `reason_text` - `extra_text` ## 6. 必须保留的关键逻辑 ### 6.1 `exact` 与 `bbox` 的双阶段查询 不要只做一个 `queryRenderedFeatures`。 必须保留: 1. `exact point query` 2. `bbox query` 理由: - 海图小符号太小 - 单点容易漏 - bbox 用来补小灯标/小浮标/小地标 ### 6.2 `nearby primary` 抢回规则 当前逻辑里,如果: - `exact` 命中的是 broad area / 次级对象 - `bbox` 命中了 nearby `primary` 则允许 nearby `primary` 抢回。 但下列层不能被轻易抢走: - `land_area` - `onshore_structure_area` - `onshore_structure_line` - `bridge_structure` 也就是: - 陆地、建筑物、桥这种可见对象 - 一旦已经被命中 - 不应再被附近别的点对象夺走 ### 6.3 可见建筑/地标点必须纳入主入口 当前规则里,这些层已经属于正式 pickup 主入口,迁移时必须保留: - `facility-points` - `landmark-points` - `landmark-point-fallback` 不能只顾海底,不关注建筑/地标。 ### 6.4 海底解释不要黑箱 “当前点击解读”里必须展示: - `判断理由` 当前要求至少能显示: - 当前水深取最近可见深度,约多少米 - `100米` 内样本深度范围与跨度 - 附近等深线数量 - 深度标签数量 - 海底辅助线数量 - 附近结构或底质 ## 7. React 侧建议数据结构 请实现一个统一返回结构,例如: ```ts export type NavSeaClickInterpretation = { resultType: "surface_object" | "seabed_context" | "empty"; primary: null | { fid: string | null; title: string; sourceLayerStd: string; objectDomain: string; objectClass: string; canonicalObjectType: string | null; distanceM: number | null; details: Array<{ field: string; label: string; value: unknown }>; }; currentDisplay: { mode: "海面/露出物" | "海底信息" | "未命中"; headline: string; depthText: string; objectName: string; shapeText: string; reasonText: string; extraText: string; }; depthContext: unknown; terrainInterpretation: unknown; candidates: unknown[]; debug?: { exactFeatures: unknown[]; bboxFeatures: unknown[]; rawFeatures: unknown[]; }; }; ``` ## 8. React 组件层要求 请在目标前端项目里落一个正式的点击结果 panel。 至少显示下面这些字段: - `结果类型` - `主显示` - `当前水深` - `物体名` - `水底情况` - `判断理由` - `补充提示` 要求: - 样式简洁,不需要复刻测试页调试风格 - 但字段含义必须保留 - JSON 调试块可以放到开发模式或 debug 开关下 ## 9. 不要做的事 请不要做下面这些事: 1. 不要另造一套新的对象分类体系 2. 不要手写一套新的 layer 白名单 3. 不要把 `附近` 再改回固定像素半径 4. 不要把所有用户都按“海底信息”解释 5. 不要为了“更聪明”擅自引入复杂插值算法 6. 不要删掉当前 `判断理由` 7. 不要把内部调试术语直接暴露给普通用户 ## 10. 你应该产出的内容 请在目标前端项目里至少完成: 1. 一个可复用的 `pickup` service / hook 2. 一个可复用的 `current click interpretation` 解释器 3. 一个前端 panel 组件 4. 把 map 点击事件接到这套逻辑上 5. 必要的类型定义 6. 一份简短说明,写清: - 主要新增了哪些文件 - 如何接入 - 当前有哪些限制 ## 11. 验收标准 实现完成后,至少满足下面这些验收条件: 1. 点到灯塔、灯标、浮标、建筑物、地标、防波堤、栈桥、桥梁时: - 优先返回具体物体 2. 点到空白水域时: - 不直接返回空 - 返回海底信息 3. “附近”口径稳定为: - `100 米内` 4. “当前水深”和“判断理由”使用同一套证据口径 5. 海底解释文案对普通钓鱼人基本可理解 6. 不会出现: - 明明点到建筑物,却解释成海底 7. 不会出现: - 当前水深和判断理由互相打架 ## 12. 建议给 Codex 的直接执行口令 如果你把这份文档直接发给另一个 Codex,可以在前面加上这段: ```md 请在当前 React + MapLibre 项目中,严格按照这份文档实现 NavSea 的 pickup “当前点击解读”。 要求: 1. 先阅读文档中列出的参考文件。 2. 不要另造规则,尽量复用现有 pickup 规则字典和解释器结构。 3. 直接在项目里完成实现,不要只给方案。 4. 完成后给出: - 改了哪些文件 - 如何接入 - 还剩哪些限制 ``` ## 13. 当前一句话总结 迁移时请记住唯一重点: - **可见对象优先按对象解释** - **海底信息只作为未命中明确对象时的回退解释** - **“附近”统一按 100 米** - **“当前点击解读”必须带判断理由**