// selection -> which handles the main map may show. // // Pure manifest filtering, deliberately free of any OpenLayers import so it can // be unit-tested in node. `handle-layer.ts` is the OL adapter that renders the // result. import { isRoadKind, type EditHandle, type HandleManifest, type JunctionReserve } from './types'; /** * Road kinds only, and only for the selected road's segment. * * Junction kinds stay out even though the manifest carries them: reserve interiors * belong to JunctionTools, and the main map must not offer a second way to edit * them. Reserve-disabled road handles are *kept* rather than dropped, because the * user has to be able to see why that stretch refuses to move. * * Handle anchors carry a directional `roadId`, while `reserves` are keyed by * segment id, so callers pass a resolver instead of parsing ids apart. */ export function handlesForSegment( manifest: HandleManifest | null, segmentId: string | null, segmentOf: (roadId: string) => string | undefined, ): EditHandle[] { if (!manifest || !segmentId) return []; return manifest.handles.filter((handle) => { if (!isRoadKind(handle.kind)) return false; const roadId = 'roadId' in handle.anchor ? handle.anchor.roadId : undefined; return typeof roadId === 'string' && segmentOf(roadId) === segmentId; }); } /** Reserves that apply to one segment, for the range window and the tooltip copy. */ export function reservesForSegment(manifest: HandleManifest | null, segmentId: string | null): JunctionReserve[] { if (!manifest || !segmentId) return []; return manifest.reserves.filter((reserve) => reserve.roadId === segmentId); } /** Why a handle refuses to move. Falls back to a generic reason so the UI never says nothing. */ export function disabledReasonOf(handle: EditHandle): string | undefined { if (handle.editable) return undefined; return handle.disabledReason || '该手柄位于路口保留区,请进入 JunctionTools 编辑。'; }