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>
62 lines
2.2 KiB
TypeScript
62 lines
2.2 KiB
TypeScript
// Synthetic road for the probe, so the page runs without importing OSM or
|
|
// touching the workbench API. Geometry helpers come from the real edit modules
|
|
// on purpose: the probe has to exercise the production conversion, not a copy.
|
|
|
|
import { offsetCoordinate, polylineLengthMeters, type Coordinate } from '../../src/edit/meters';
|
|
import type { EditHandle } from '../../src/edit/types';
|
|
|
|
/** The road heads due north, so its normal — the drag axis — points due east. */
|
|
export const TANGENT_AZIMUTH = 0;
|
|
export const AXIS_AZIMUTH = 90;
|
|
export const WIDTH_METERS = 12;
|
|
export const LANE_COUNT = 4;
|
|
|
|
export const CENTERLINE: Coordinate[] = [
|
|
[116.397, 39.905],
|
|
[116.397, 39.915],
|
|
];
|
|
|
|
export const ROAD_LENGTH_METERS = polylineLengthMeters(CENTERLINE);
|
|
|
|
const MIDPOINT: Coordinate = [116.397, 39.91];
|
|
|
|
/** Outer ring of the baseline road surface, EPSG:4326. */
|
|
export function roadSurfaceRing(): Coordinate[] {
|
|
const half = WIDTH_METERS / 2;
|
|
const left = CENTERLINE.map((point) => offsetCoordinate(point, TANGENT_AZIMUTH + 90, half));
|
|
const right = CENTERLINE.map((point) => offsetCoordinate(point, TANGENT_AZIMUTH - 90, half));
|
|
return [...left, ...right.reverse(), left[0]];
|
|
}
|
|
|
|
/** Lane divider lines, to make it obvious if the baseline ever gets rewritten. */
|
|
export function laneDividerLines(): Coordinate[][] {
|
|
const laneWidth = WIDTH_METERS / LANE_COUNT;
|
|
const lines: Coordinate[][] = [];
|
|
for (let index = 1; index < LANE_COUNT; index += 1) {
|
|
const lateral = -WIDTH_METERS / 2 + laneWidth * index;
|
|
lines.push(CENTERLINE.map((point) => offsetCoordinate(point, TANGENT_AZIMUTH + 90, lateral)));
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
/**
|
|
* A left edge-offset handle shaped exactly like the one `makeRoadHandles()`
|
|
* emits, so `projectHandleValue()` sees production input.
|
|
*/
|
|
export const PROBE_HANDLE: EditHandle = {
|
|
handleId: 'probe:road-edge-offset:left',
|
|
kind: 'road-edge-offset',
|
|
anchor: {
|
|
type: 'road-interval',
|
|
roadId: 'probe:road/1:forward',
|
|
startStation: 0.15,
|
|
endStation: 0.85,
|
|
side: 'left',
|
|
},
|
|
position: offsetCoordinate(MIDPOINT, AXIS_AZIMUTH, WIDTH_METERS / 2),
|
|
axisAzimuth: AXIS_AZIMUTH,
|
|
value: { current: 0, min: -WIDTH_METERS * 0.45, max: WIDTH_METERS * 0.45, unit: 'meter' },
|
|
affects: [],
|
|
editable: true,
|
|
};
|