feat: parameterize complex junction geometry with Gaode reference
- 高德 GeoJSON 参考流程: `scripts/lib/gaode-junction-reference.js` 与 `scripts/inspect-junction-reference.js` 将 GCJ-02 参考转换为 WGS84, 按 node id/最近距离关联 OSM, 支持普通路口面和 `complex-cluster` 两种匹配。 - 复合路口模板 `complex-junction-v1`: `scripts/lib/complex-junction.js` 用参考 几何校准 core 半径, 生成路口面、进口路面、斑马线、停止线、角部圆角与安全岛; 拓扑/信号/连接全部沿用 OSM/native。 - 车道中心线控制要素避让: `compileLaneCenterlines` 现接收模板已产出的斑马线/停止线, 新增 `trimLaneOutsideControls` 按到路口中心的半径定向裁剪; 标线源几何同步裁剪, 不再 越过斑马线继续画到核心区。拓扑几何不变, connector 集合前后一致。 - 复合路口人行道转角: `buildComplexJunctionGeometry` 沿已定义的路缘生成 2m 宽转角带, 复用圆角曲线, 通过 `islands` 通道并入 `sidewalk_surface`; 自交或坐标非有限时报 `complex-junction-sidewalk-corner-fallback` 并跳过。 - 新增诊断: `complex-junction-configured-radius-ignored`、 `lane-centerline-fully-inside-control`、`complex-junction-sidewalk-corner-fallback`。 - 死码清理: 移除未被调用的 `clusterApproachRing`。 - spec 更新: `.trellis/spec/pipeline/cli-and-stages.md` 复合路口小节补充控制要素 避让顺序、人行道转角契约、Validation 矩阵三行; 索引新增导航。 - 任务产物 `08-19-gaode-junction-reference`: 8 条验收标准全部实测记录, Scope Drift / Verification Log / Known Gaps 三节沉淀本次工作。 Regression: test:native-road / test:road-workbench / test:preflight / test:native-preview-traffic / test:package-contract / test:traffic-signals / test:gaode-junction-reference 全绿; road:check ok=true, errors=[]。
This commit is contained in:
@@ -61,6 +61,7 @@ controlsToggle.innerHTML = '<input data-layer="controls" type="checkbox" checked
|
||||
const signalsToggle = document.createElement("label");
|
||||
signalsToggle.innerHTML = '<input data-layer="signals" type="checkbox" checked> 红绿灯设施';
|
||||
document.querySelector('[data-layer="lanes"]').closest("label").after(directionArrowsToggle, markingsToggle, centerLinesToggle, edgeLinesToggle, controlsToggle, signalsToggle);
|
||||
const gaodeReferenceColors = { 1: "#2563eb", 2: "#0f766e", 3: "#7c3aed", 4: "#ea580c", 5: "#64748b" };
|
||||
|
||||
let state;
|
||||
let selectedRoad = null;
|
||||
@@ -83,6 +84,7 @@ const signalPicker = signalPanel.querySelector('[name="signal-picker"]');
|
||||
const source = () => new VectorSource();
|
||||
const layers = {
|
||||
reference: new VectorLayer({ source: source(), visible: false, style: new Style({ fill: new Fill({ color: "rgba(123, 140, 148, .28)" }), stroke: new Stroke({ color: "#8999a0", width: 1 }) }) }),
|
||||
gaodeReference: new VectorLayer({ source: source(), visible: true, zIndex: 1, style: (feature) => { const color = gaodeReferenceColors[feature.get("type")] || "#475569"; return new Style({ fill: new Fill({ color: `${color}26` }), stroke: new Stroke({ color, width: 1.5 }) }); } }),
|
||||
native: new VectorLayer({ source: source(), style: nativeSurfaceStyle }),
|
||||
edgeLines: new VectorLayer({ source: source(), visible: false, style: markingStyle }),
|
||||
sidewalks: new VectorLayer({ source: source(), style: sidewalkSurfaceStyle }),
|
||||
@@ -99,7 +101,7 @@ const layers = {
|
||||
selectedRoad: new VectorLayer({ source: source(), style: new Style({ stroke: new Stroke({ color: "#00a5cf", width: 8 }) }), zIndex: 10 }),
|
||||
selectedMovement: new VectorLayer({ source: source(), style: new Style({ stroke: new Stroke({ color: "#f0b323", width: 6 }) }), zIndex: 12 }),
|
||||
};
|
||||
const map = new Map({ target: "map", layers: [layers.reference, layers.native, layers.edgeLines, layers.sidewalks, layers.osm, layers.lanes, layers.directionArrows, layers.markings, layers.centerLines, layers.controls, layers.signals, layers.connectors, layers.diagnostics, layers.selectedRoad, layers.osmDirection, layers.selectedMovement], view: new View({ center: [0, 0], zoom: 2 }) });
|
||||
const map = new Map({ target: "map", layers: [layers.gaodeReference, layers.reference, layers.native, layers.edgeLines, layers.sidewalks, layers.osm, layers.lanes, layers.directionArrows, layers.markings, layers.centerLines, layers.controls, layers.signals, layers.connectors, layers.diagnostics, layers.selectedRoad, layers.osmDirection, layers.selectedMovement], view: new View({ center: [0, 0], zoom: 2 }) });
|
||||
const select = new Select({ condition: click, layers: (layer) => manualFromEndpoint ? layer === layers.osm : [layers.osm, layers.lanes, layers.directionArrows, layers.markings, layers.centerLines, layers.edgeLines, layers.controls, layers.signals, layers.connectors, layers.native, layers.diagnostics].includes(layer), hitTolerance: 12, style: null });
|
||||
map.addInteraction(select);
|
||||
select.on("select", ({ selected }) => {
|
||||
@@ -155,7 +157,7 @@ function osmDirectionLabel(road) { return road?.direction === "forward" ? "沿 O
|
||||
function roadIdFromLane(laneId) { return typeof laneId === "string" ? laneId.slice(5, laneId.lastIndexOf(":")) : ""; }
|
||||
function laneIndex(laneId) { return Number(String(laneId).split(":").at(-1)); }
|
||||
function lanePositionLabel(road, index) { return road?.laneCount === 1 ? "唯一车道" : `左起第 ${index} 车道`; }
|
||||
function laneStyle(feature) { const selected = feature.get("road_id") === selectedRoad?.id; return new Style({ stroke: new Stroke({ color: selected ? "#006e91" : "#f5f6ee", width: selected ? 3 : 1.3, lineDash: [5, 4] }) }); }
|
||||
function laneStyle(feature) { const roadId = feature.get("road_id"); const selected = Boolean(roadId && selectedRoad && roadId === selectedRoad.id); const composite = feature.get("cluster_preview"); return new Style({ stroke: new Stroke({ color: selected ? "#006e91" : "#f5f6ee", width: selected ? 3 : composite ? 1.6 : 1.3, lineDash: composite ? [7, 5] : [5, 4] }) }); }
|
||||
function markingStyle(feature) { const yellow = feature?.get("color") === "yellow"; return new Style({ fill: new Fill({ color: yellow ? "#f5be2a" : "#f5f6ee" }), stroke: new Stroke({ color: yellow ? "#d29d16" : "#d9dacf", width: 1 }) }); }
|
||||
function signalAssemblyStyle(feature) { const component = feature.get("signal_component"); if (component === "mast") return [new Style({ stroke: new Stroke({ color: "#fff", width: 9 }) }), new Style({ stroke: new Stroke({ color: "#007f99", width: 5 }) })]; if (component === "face") return [new Style({ stroke: new Stroke({ color: "#fff", width: 7 }) }), new Style({ stroke: new Stroke({ color: "#df2435", width: 3 }) })]; if (component === "head") { const heading = Number(feature.get("face_heading_deg")) || 0; return new Style({ image: new RegularShape({ points: 3, radius: 8, rotation: heading * Math.PI / 180, fill: new Fill({ color: "#df2435" }), stroke: new Stroke({ color: "#fff", width: 2 }) }) }); } return new Style({ image: new RegularShape({ points: 4, radius: 6, angle: Math.PI / 4, fill: new Fill({ color: "#263630" }), stroke: new Stroke({ color: "#fff", width: 2 }) }) }); }
|
||||
function centerLineStyle(feature) { const white = feature.get("color") === "white"; const color = white ? "#faf9ee" : "#f5be2a"; return new Style({ fill: new Fill({ color }), stroke: new Stroke({ color: feature.get("pattern") === "solid" ? color : white ? "#aeb0aa" : "#d29d16", width: feature.get("pattern") === "solid" ? .25 : .8 }) }); }
|
||||
@@ -163,6 +165,9 @@ function nativeSurfaceStyle(feature) {
|
||||
// Split road features meet at OSM junction nodes. Their per-feature outlines
|
||||
// are editing aids, not physical seams, so scene mode must render fills only.
|
||||
if (scenePreview) return new Style({ fill: new Fill({ color: "#3f4b50" }) });
|
||||
if (feature.get("cluster_id") && feature.get("complex_part")) return new Style({ fill: new Fill({ color: "#6f948a" }) });
|
||||
if (feature.get("kind") === "cluster") return new Style({ fill: new Fill({ color: "rgba(20, 132, 112, .5)" }), stroke: new Stroke({ color: "#075e4f", width: 4, lineDash: [10, 5] }) });
|
||||
if (feature.get("template")) return new Style({ fill: new Fill({ color: "rgba(20, 132, 112, .46)" }), stroke: new Stroke({ color: "#087c67", width: 3, lineDash: [7, 4] }) });
|
||||
return feature.get("native_id")?.startsWith("junction:")
|
||||
? new Style({ fill: new Fill({ color: "rgba(12, 116, 91, .38)" }), stroke: new Stroke({ color: "#0e785f", width: 1.5 }) })
|
||||
: new Style({ fill: new Fill({ color: "rgba(40, 105, 86, .35)" }), stroke: new Stroke({ color: "#296956", width: 1 }) });
|
||||
@@ -180,24 +185,25 @@ function roadForFeature(feature) {
|
||||
const roadId = properties.road_id || properties.subjectId || roadIdFromLane(properties.from_lane_id) || properties.directional_road_ids?.split(",")[0];
|
||||
return state.compiled.model.roads.find((road) => road.id === roadId) || null;
|
||||
}
|
||||
function junctionForFeature(feature) { const id = feature.get("native_id"); return id?.startsWith("junction:") ? layers.native.getSource().getFeatures().find((item) => item.get("native_id") === id) : null; }
|
||||
function junctionForFeature(feature) { const id = feature.get("native_id"); return id?.startsWith("junction:") || id?.startsWith("junction-cluster:") ? layers.native.getSource().getFeatures().find((item) => item.get("native_id") === id) : null; }
|
||||
function endpointFor(road, side) { return state.compiled.model.endpoints.find((endpoint) => endpoint.roadId === road?.id && endpoint.side === side) || null; }
|
||||
function endpointsCompatible(from, to) { if (!from || !to || from.roadId === to.roadId || from.side !== "end" || to.side !== "start") return false; if (from.nodeId === to.nodeId) return true; const dx = (from.coordinate[0] - to.coordinate[0]) * 111320 * Math.cos(from.coordinate[1] * Math.PI / 180); const dy = (from.coordinate[1] - to.coordinate[1]) * 111320; return Math.hypot(dx, dy) <= 35; }
|
||||
function readFeatures(collection) { return geojson.readFeatures(collection || { type: "FeatureCollection", features: [] }, { dataProjection: "EPSG:4326", featureProjection: "EPSG:3857" }); }
|
||||
function readFeatures(collection, predicate = null) { const source = collection || { type: "FeatureCollection", features: [] }; const filtered = predicate ? { ...source, features: (source.features || []).filter(predicate) } : source; return geojson.readFeatures(filtered, { dataProjection: "EPSG:4326", featureProjection: "EPSG:3857" }); }
|
||||
function rawRoadFeatures() { return state.compiled.model.roads.map((road) => new Feature({ geometry: new LineString(road.centerline).transform("EPSG:4326", "EPSG:3857"), road_id: road.id })); }
|
||||
function updateSources() {
|
||||
layers.reference.getSource().clear(); layers.reference.getSource().addFeatures(readFeatures(state.layers.osm2streetsRoadSurface));
|
||||
layers.gaodeReference.getSource().clear(); layers.gaodeReference.getSource().addFeatures(readFeatures(state.junctionReference?.converted));
|
||||
layers.native.getSource().clear(); layers.native.getSource().addFeatures([...readFeatures(state.layers.nativeRoadSurface), ...readFeatures(state.layers.nativeIntersectionSurface)]);
|
||||
layers.sidewalks.getSource().clear(); layers.sidewalks.getSource().addFeatures(readFeatures(state.layers.nativeSidewalkSurface));
|
||||
layers.osm.getSource().clear(); layers.osm.getSource().addFeatures(rawRoadFeatures());
|
||||
layers.lanes.getSource().clear(); layers.lanes.getSource().addFeatures(readFeatures(state.layers.laneCenterlines));
|
||||
layers.lanes.getSource().clear(); layers.lanes.getSource().addFeatures(readFeatures(state.layers.laneCenterlines, (feature) => !feature.properties?.cluster_internal && !feature.properties?.cluster_preview_hidden));
|
||||
layers.edgeLines.getSource().clear(); layers.edgeLines.getSource().addFeatures(readFeatures(state.layers.edgeLines));
|
||||
layers.directionArrows.getSource().clear(); layers.directionArrows.getSource().addFeatures(readFeatures(state.layers.directionArrows));
|
||||
layers.markings.getSource().clear(); layers.markings.getSource().addFeatures([...readFeatures(state.layers.laneSeparators), ...readFeatures(state.layers.turnArrows)]);
|
||||
layers.centerLines.getSource().clear(); layers.centerLines.getSource().addFeatures(readFeatures(state.layers.centerLines));
|
||||
layers.directionArrows.getSource().clear(); layers.directionArrows.getSource().addFeatures(readFeatures(state.layers.directionArrows, (feature) => !feature.properties?.cluster_preview_hidden));
|
||||
layers.markings.getSource().clear(); layers.markings.getSource().addFeatures([...readFeatures(state.layers.laneSeparators, (feature) => !feature.properties?.cluster_preview_hidden), ...readFeatures(state.layers.turnArrows, (feature) => !feature.properties?.cluster_preview_hidden)]);
|
||||
layers.centerLines.getSource().clear(); layers.centerLines.getSource().addFeatures(readFeatures(state.layers.centerLines, (feature) => !feature.properties?.cluster_preview_hidden));
|
||||
layers.controls.getSource().clear(); layers.controls.getSource().addFeatures([...readFeatures(state.layers.crosswalks), ...readFeatures(state.layers.vehicleStopLines)]);
|
||||
const signalFeatures = readFeatures(state.trafficSignals?.assemblies || { type: "FeatureCollection", features: [] }); const armFeatures = []; const faceFeatures = []; const headFeatures = []; for (const signal of state.trafficRuntime?.signals || []) { const arm = signal.pose?.arm; const head = signal.pose?.head; if (!arm || !head) continue; const properties = { signal_uid: signal.id }; const headPoint = fromLonLat([head.longitude, head.latitude]); const radians = Number(head.faceHeadingDegrees) * Math.PI / 180; const faceEnd = [headPoint[0] + Math.sin(radians) * 2.5, headPoint[1] + Math.cos(radians) * 2.5]; armFeatures.push(new Feature({ geometry: new LineString([fromLonLat([arm.from.longitude, arm.from.latitude]), fromLonLat([arm.to.longitude, arm.to.latitude])]), signal_component: "mast", ...properties })); faceFeatures.push(new Feature({ geometry: new LineString([headPoint, faceEnd]), signal_component: "face", ...properties })); headFeatures.push(new Feature({ geometry: new Point(faceEnd), signal_component: "head", face_heading_deg: head.faceHeadingDegrees, ...properties })); } layers.signals.getSource().clear(); layers.signals.getSource().addFeatures([...armFeatures, ...faceFeatures, ...signalFeatures, ...headFeatures]); const pickerValue = signalPicker.value; signalPicker.replaceChildren(new Option("选择设施", "")); signalFeatures.forEach((feature) => signalPicker.add(new Option(feature.get("display_id") || feature.get("signal_uid"), feature.get("signal_uid")))); signalPicker.value = pickerValue;
|
||||
layers.connectors.getSource().clear(); layers.connectors.getSource().addFeatures(readFeatures(state.layers.connectors));
|
||||
layers.connectors.getSource().clear(); layers.connectors.getSource().addFeatures(readFeatures(state.layers.connectors, (feature) => !feature.properties?.cluster_internal));
|
||||
layers.diagnostics.getSource().clear(); layers.diagnostics.getSource().addFeatures(readFeatures({ type: "FeatureCollection", features: state.compiled.diagnostics.filter((item) => item.geometry).map(({ geometry, ...properties }) => ({ type: "Feature", properties, geometry })) }));
|
||||
const extent = layers.osm.getSource().getExtent(); if (Number.isFinite(extent[0])) map.getView().fit(extent, { padding: [48, 48, 48, 48], maxZoom: 19 });
|
||||
}
|
||||
@@ -220,8 +226,8 @@ function selectJunction(feature) {
|
||||
selectedJunction = feature; selectedRoad = null; selectedMovement = null; form.hidden = true; hint.hidden = true; selectedJunctionPanel.hidden = false;
|
||||
const properties = feature.getProperties(); const roadIds = String(properties.source_road_ids || "").split(",").filter(Boolean);
|
||||
const roads = roadIds.map((id) => state.compiled.model.roads.find((road) => road.id === id)).filter(Boolean);
|
||||
junctionDetail.textContent = JSON.stringify({ OSM节点: properties.osm_node_id, 类型: properties.kind === "t" ? "T字路口" : "十字路口", 参与方向道路: roads.map((road) => ({ 道路: roadLabel(road), OSM道路: road.osmWayIds, 节点顺序: road.sourceNodeIds })), 构面规则: properties.rule, 边界策略: properties.boundary_mode, 基础截面面积平方米: properties.approach_area_m2, 最终路口面积平方米: properties.surface_area_m2, 外缘扩张倍率: properties.expansion_ratio, 路口退让距离米: properties.cutback_m, 行驶动作数: properties.movement_count, 已绘制连接数: properties.connector_count }, null, 2);
|
||||
message(`已选中路口:OSM 节点 ${properties.osm_node_id}`);
|
||||
junctionDetail.textContent = JSON.stringify({ OSM节点: properties.osm_node_id || properties.osm_node_ids, 类型: properties.kind === "cluster" ? "复合路口簇" : properties.kind === "t" ? "T字路口" : "十字路口", 参与方向道路: roads.map((road) => ({ 道路: roadLabel(road), OSM道路: road.osmWayIds, 节点顺序: road.sourceNodeIds })), 构面规则: properties.rule, 模板: properties.template, 边界策略: properties.boundary_mode, 基础截面面积平方米: properties.approach_area_m2, 最终路口面积平方米: properties.surface_area_m2, 外缘扩张倍率: properties.expansion_ratio, 路口退让距离米: properties.cutback_m, 行驶动作数: properties.movement_count, 已绘制连接数: properties.connector_count }, null, 2);
|
||||
message(`已选中路口:OSM 节点 ${properties.osm_node_id || properties.osm_node_ids}`);
|
||||
}
|
||||
function selectSignal(feature) {
|
||||
selectedSignal = feature.get("signal_uid"); const p = feature.getProperties(); signalPicker.value = selectedSignal;
|
||||
@@ -304,13 +310,30 @@ centerLineForm.onsubmit = (event) => { event.preventDefault(); stageSelectedCent
|
||||
centerLineStyleInput.onchange = stageSelectedCenterLineStyle;
|
||||
async function saveStagedChanges() { if (!staged.length) return true; const existing = state.overrides.overrides.filter((item) => !staged.some((change) => change.id === item.id)); const response = await fetch("/api/overrides", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ schema: "native-road-overrides/v1", overrides: [...existing, ...staged] }) }); const result = await response.json(); if (!result.ok) { message(result.error); return false; } state.overrides = result.overrides; staged = []; updateDirtyState(); return true; }
|
||||
saveButton.onclick = async () => { if (await saveStagedChanges()) message("已保存,点击“保存并重新生成”写入几何"); };
|
||||
compileButton.onclick = async () => { if (!await saveStagedChanges()) return; message("正在保存修改并重新生成..."); const response = await fetch("/api/compile", { method: "POST" }); state = await response.json(); staged = []; updateDirtyState(); updateSources(); renderDiagnostics(); renderSummary(); selectRoad(selectedRoad ? state.compiled.model.roads.find((road) => road.id === selectedRoad.id) : null); message("已保存并重新生成"); };
|
||||
compileButton.onclick = async () => {
|
||||
if (!await saveStagedChanges()) return;
|
||||
message("正在保存修改并重新生成...");
|
||||
const response = await fetch("/api/compile", { method: "POST", cache: "no-store" });
|
||||
const nextState = await response.json();
|
||||
if (!response.ok || nextState.ok === false || !nextState.compiled?.model || !nextState.layers) {
|
||||
message(`重新生成失败:${nextState.error || `HTTP ${response.status}`}`);
|
||||
return;
|
||||
}
|
||||
state = nextState;
|
||||
staged = [];
|
||||
updateDirtyState();
|
||||
updateSources();
|
||||
renderDiagnostics();
|
||||
renderSummary();
|
||||
selectRoad(selectedRoad ? state.compiled.model.roads.find((road) => road.id === selectedRoad.id) : null);
|
||||
message("已保存并重新生成");
|
||||
};
|
||||
for (const input of document.querySelectorAll("[data-layer]")) input.onchange = () => { const visible = input.checked; layers[input.dataset.layer].setVisible(visible); if (input.dataset.layer === "osm") layers.osmDirection.setVisible(visible); };
|
||||
scenePreviewToggle.onchange = () => {
|
||||
scenePreview = scenePreviewToggle.checked;
|
||||
for (const input of document.querySelectorAll("[data-layer]")) {
|
||||
const layer = input.dataset.layer;
|
||||
if (["osm", "lanes", "reference"].includes(layer)) layers[layer].setVisible(!scenePreview && input.checked);
|
||||
if (["osm", "lanes", "reference", "gaodeReference"].includes(layer)) layers[layer].setVisible(!scenePreview && input.checked);
|
||||
}
|
||||
layers.osmDirection.setVisible(!scenePreview && document.querySelector('[data-layer="osm"]').checked);
|
||||
layers.connectors.setVisible(!scenePreview && document.querySelector('[data-layer="lanes"]').checked);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>道路编译工作台</title><link rel="stylesheet" href="/vendor/ol/ol.css"><link rel="stylesheet" href="/app.css"></head>
|
||||
<body><header><strong>道路编译工作台</strong><span id="area"></span><span id="status"></span><span id="dirty-state" aria-live="polite"></span><label style="display:inline;margin:0 0 0 auto;white-space:nowrap"><input id="scene-preview" type="checkbox"> 场景效果</label><button id="save">保存修改</button><button id="compile">保存并重新生成</button></header>
|
||||
<main><aside class="issues"><h1>图层</h1><label><input data-layer="osm" type="checkbox" checked> OSM 道路中心线</label><label><input data-layer="native" type="checkbox" checked> 自研道路与路口面</label><label><input data-layer="sidewalks" type="checkbox" checked> 路缘与步行带</label><label><input data-layer="lanes" type="checkbox" checked> 车道与转向路径</label><label><input data-layer="reference" type="checkbox"> osm2streets 参考面</label><hr><h1>当前编译概览</h1><dl id="summary"></dl><hr><h1>待检查问题</h1><div id="diagnostic-filters" class="segmented"><button data-diagnostic-filter="all" type="button">全部</button><button data-diagnostic-filter="candidates" type="button">可连接</button><button data-diagnostic-filter="other" type="button">其他</button></div><ul id="diagnostics"></ul></aside><section id="map" class="map"></section><aside class="inspector"><h1>当前道路设置</h1><p id="hint">点击道路、车道、转向路径或路口面以查看详情。</p><section id="selected-junction" hidden><h2>当前路口</h2><output id="junction-detail"></output></section><form id="road-form" hidden><label>道路</label><output id="road-name"></output><output id="movement-summary"></output><output id="lane-convention"></output><section id="selected-movement" hidden><h2>当前行驶动作</h2><output id="movement-detail"></output></section><div id="direction-switch"></div><label>本方向道路宽度(米)<input id="width" type="number" min="1" step="0.01"></label><label>本方向车道数<input id="lanes" type="number" min="1" step="1"></label><label><input id="left" type="checkbox"> 左侧有路缘与步行带</label><label><input id="right" type="checkbox"> 右侧有路缘与步行带</label><button type="submit">暂存本道路修改</button></form><form id="center-line-form" hidden><h2 id="marking-style-heading">道路中心线样式</h2><output id="center-line-segment"></output><label>样式<select id="center-line-style"><option value="yellow-dashed">黄色虚线(默认)</option><option value="white-dashed">白色虚线</option><option value="yellow-solid">黄色实线</option><option value="white-solid">白色实线</option></select></label><button type="submit">暂存标线样式</button></form><hr><h2>路口连接</h2><div id="connections">请选择一条道路。</div><button id="add-connection" type="button" hidden>手工新增驶出连接</button><details><summary>技术详情与来源</summary><pre id="evidence">无</pre></details></aside></main><script type="importmap">{"imports":{"rbush":"/vendor/rbush/index.js","quickselect":"/vendor/quickselect/index.js"}}</script><script type="module" src="/app.js"></script></body></html>
|
||||
<main><aside class="issues"><h1>图层</h1><label><input data-layer="osm" type="checkbox" checked> OSM 道路中心线</label><label><input data-layer="native" type="checkbox" checked> 自研道路与路口面</label><label><input data-layer="sidewalks" type="checkbox" checked> 路缘与步行带</label><label><input data-layer="lanes" type="checkbox" checked> 车道与转向路径</label><label><input data-layer="gaodeReference" type="checkbox" checked> 高德规整路口参考</label><label><input data-layer="reference" type="checkbox"> osm2streets 参考面</label><hr><h1>当前编译概览</h1><dl id="summary"></dl><hr><h1>待检查问题</h1><div id="diagnostic-filters" class="segmented"><button data-diagnostic-filter="all" type="button">全部</button><button data-diagnostic-filter="candidates" type="button">可连接</button><button data-diagnostic-filter="other" type="button">其他</button></div><ul id="diagnostics"></ul></aside><section id="map" class="map"></section><aside class="inspector"><h1>当前道路设置</h1><p id="hint">点击道路、车道、转向路径或路口面以查看详情。</p><section id="selected-junction" hidden><h2>当前路口</h2><output id="junction-detail"></output></section><form id="road-form" hidden><label>道路</label><output id="road-name"></output><output id="movement-summary"></output><output id="lane-convention"></output><section id="selected-movement" hidden><h2>当前行驶动作</h2><output id="movement-detail"></output></section><div id="direction-switch"></div><label>本方向道路宽度(米)<input id="width" type="number" min="1" step="0.01"></label><label>本方向车道数<input id="lanes" type="number" min="1" step="1"></label><label><input id="left" type="checkbox"> 左侧有路缘与步行带</label><label><input id="right" type="checkbox"> 右侧有路缘与步行带</label><button type="submit">暂存本道路修改</button></form><form id="center-line-form" hidden><h2 id="marking-style-heading">道路中心线样式</h2><output id="center-line-segment"></output><label>样式<select id="center-line-style"><option value="yellow-dashed">黄色虚线(默认)</option><option value="white-dashed">白色虚线</option><option value="yellow-solid">黄色实线</option><option value="white-solid">白色实线</option></select></label><button type="submit">暂存标线样式</button></form><hr><h2>路口连接</h2><div id="connections">请选择一条道路。</div><button id="add-connection" type="button" hidden>手工新增驶出连接</button><details><summary>技术详情与来源</summary><pre id="evidence">无</pre></details></aside></main><script type="importmap">{"imports":{"rbush":"/vendor/rbush/index.js","quickselect":"/vendor/quickselect/index.js"}}</script><script type="module" src="/app.js"></script></body></html>
|
||||
|
||||
Reference in New Issue
Block a user