Steps 1-3 of the main map road interval editor. EditSession keeps the command stack, undo/redo and previewSeq arbitration as pure logic with no OpenLayers reference, so all of it is unit-tested in node. Pointer displacement converts to meters through EPSG:4326 and spherical distance: treating a 3857 delta as meters desyncs the geometry from the cursor by 1/cos(latitude). Handle drags project onto the axis the manifest declares and clamp to its range, so the client never writes a coordinate into a road polygon. All of it sits behind a directEdit flag that defaults to off. With the flag off the workbench requests no manifest, creates no extra source and registers no interaction, so behaviour matches main. The ol-ext probe passed its three gates but is not adopted for road handles. Transform translates by the raw pointer delta, so a handle detaches from its clamped constraint value: a drag reading -24.1 m produced a draft of -5.4 m. Production needs the handle position derived from the constraint instead, which means owning the position update, so native OL PointerInteraction will carry the drag. ol-ext stays out of package.json; the probe is kept as a manual harness. Reserve handles are unreachable with the current solver, recorded in research/ rather than worked around. Also names the dead backend when an API response is empty, instead of surfacing "Unexpected end of JSON input" from response.json(). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3.6 KiB
ol-ext Transform 限时探针
对应 .trellis/tasks/08-26-direct-edit-map-editor/implement.md 第 1 步。
这个探针只回答一个问题:能否复用 ol-ext 通用 handle 的命中、pointer 生命周期与视觉反馈。
它不回答“道路语义怎么建模”——那部分由 src/edit/ 的 EditSession / projection 负责,且已经独立单测通过。
怎么跑
npm install ol-ext --no-save # 门禁通过前不进依赖清单
npm run dev
然后打开 http://localhost:5173/probe/ol-ext-transform/。
不需要导入 OSM,也不需要后端:页面用 fixture.ts 里的合成道路。
你要做的事
拖动地图上那个蓝色圆点手柄,来回拖几次,快慢都试,然后看右侧面板。
三条门禁(必须同时成立)
| # | 门禁 | 面板怎么读 |
|---|---|---|
| 1 | OL Map 未被重建 |
“实例 #N,累计构造 N 次”两个数字必须相等。再点几次“强制 React 重渲染”,数字仍要相等。 |
| 2 | 基线 source 未被写入 | “写入 0 次,几何未改变”。拖拽过程中这行任何变化都算失败。 |
| 3 | proxy 拖拽稳定且事件能转成 draft 值 | 看**「原始位移」**那行跟手连续变化、松手不跳;手柄不粘滞、不丢命中。 |
门禁 3 要看的是「原始位移」,不是「约束值」。约束值带生产钳位 ±5.4 米,
而 5.4 米在 zoom 20 只有约 47 像素,稍微拖远就会顶到边界显示成常量——那是正常的,不代表跟踪有问题。
「原始位移」不钳位,所以它才反映跟手质量。
两个数都是用生产代码算的(signedMetersAlongAxis() / projectHandleValue()),
所以门禁 3 同时验证了“事件 → 约束值”这条链路。
另外面板会显示收到了多少个 translating 事件。这个数字和 React 渲染次数的比例是个有用的信号:
探针已经把逐帧读数用 requestAnimationFrame 合并了,生产实现还要更进一步——
ghost 直接写自己的 OL source,绝不为每次指针移动重渲染 React 树。
注意:卸载/重新挂载地图会构造新的 Map,这是预期行为,不算门禁 1 失败。门禁 1 针对的是拖拽与重渲染。
请回报给我
- 三条门禁分别通过/未通过。
- 未通过的,面板上的具体数字或现象。
- 主观手感:拖拽是否顺滑,手柄命中是否可靠。
已知的、探针之外的结论
这些我已经查过,不用你验证,但会计入最终取舍:
- ol-ext 4.0.38,BSD-3-Clause,
peerDependencies: ol >= 5.3.0,无运行时依赖。 - 不带 TypeScript 类型,也没有
@types/ol-ext。 本目录的ol-ext.d.ts是我为探针手写的最小声明; 真要把 ol-ext 提为正式依赖,就要长期自己维护一份声明文件。 Transform的translate分支直接对传入 feature 调geometry.translate()。 所以它只能绑一次性 proxy,绝不能绑编译产物图层——这也是门禁 2 存在的原因。- 它的手柄模型是 bounding box 的 scale/rotate/stretch,跟道路法线偏移、区间范围、路口 cutback 不是一回事。
即使门禁全过,它最多承担“通用手柄的命中与拖拽生命周期”,语义投影仍然是我们自己的
projection.ts。
如果门禁没过
按 design.md 的回退优先级,删掉本目录,改用回退方案 1:
原生 OL Snap + 小型 PointerInteraction adapter。
HandleManifest → RoadEditOperation → RoadConstraint → preview solver 的数据合约不变,
所以这一步失败只换输入层,第 2 步已交付的 EditSession / projection / meters 全部保留。