Files
road-compiler/workbench/client/probe/ol-ext-transform
que01 bc4b9a9717 feat: add direct edit handles behind directEdit flag
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>
2026-08-27 14:59:11 +08:00
..

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 针对的是拖拽与重渲染。

请回报给我

  1. 三条门禁分别通过/未通过。
  2. 未通过的,面板上的具体数字或现象。
  3. 主观手感:拖拽是否顺滑,手柄命中是否可靠。

已知的、探针之外的结论

这些我已经查过,不用你验证,但会计入最终取舍:

  • ol-ext 4.0.38BSD-3-ClausepeerDependencies: ol >= 5.3.0,无运行时依赖。
  • 不带 TypeScript 类型,也没有 @types/ol-ext 本目录的 ol-ext.d.ts 是我为探针手写的最小声明; 真要把 ol-ext 提为正式依赖,就要长期自己维护一份声明文件。
  • Transformtranslate 分支直接对传入 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 全部保留。