feat: add native road center lines
This commit is contained in:
@@ -292,7 +292,7 @@ function buildBlenderScene(area, roadProvider) {
|
||||
|
||||
function ensureNativeRoadLayers(area) {
|
||||
ensureFile(path.join(area.outputs.nativeRoadDir, "compiled.json"), "Native road compilation");
|
||||
for (const file of ["road_surface.geojson", "intersection_surface.geojson", "sidewalk_surface.geojson", "lane_separators.geojson", "direction_arrows.geojson", "turn_arrows.geojson", "crosswalks.geojson", "vehicle_stop_lines.geojson"]) {
|
||||
for (const file of ["road_surface.geojson", "intersection_surface.geojson", "sidewalk_surface.geojson", "lane_separators.geojson", "center_lines.geojson", "direction_arrows.geojson", "turn_arrows.geojson", "crosswalks.geojson", "vehicle_stop_lines.geojson"]) {
|
||||
ensureFile(path.join(area.outputs.nativeRoadDir, "layers", file), `Native road layer ${file}`);
|
||||
}
|
||||
}
|
||||
@@ -305,6 +305,7 @@ function nativeRoadRecords(area) {
|
||||
nativeIntersectionSurface: fileRecord(path.join(root, "intersection_surface.geojson")),
|
||||
nativeSidewalkSurface: fileRecord(path.join(root, "sidewalk_surface.geojson")),
|
||||
nativeLaneSeparators: fileRecord(path.join(root, "lane_separators.geojson")),
|
||||
nativeCenterLines: fileRecord(path.join(root, "center_lines.geojson")),
|
||||
nativeDirectionArrows: fileRecord(path.join(root, "direction_arrows.geojson")),
|
||||
nativeTurnArrows: fileRecord(path.join(root, "turn_arrows.geojson")),
|
||||
nativeCrosswalks: fileRecord(path.join(root, "crosswalks.geojson")),
|
||||
@@ -319,6 +320,7 @@ function nativeRoadFeatureCounts(area) {
|
||||
intersectionSurface: featureCount(path.join(root, "intersection_surface.geojson")),
|
||||
sidewalkSurface: featureCount(path.join(root, "sidewalk_surface.geojson")),
|
||||
laneSeparators: featureCount(path.join(root, "lane_separators.geojson")),
|
||||
centerLines: featureCount(path.join(root, "center_lines.geojson")),
|
||||
directionArrows: featureCount(path.join(root, "direction_arrows.geojson")),
|
||||
turnArrows: featureCount(path.join(root, "turn_arrows.geojson")),
|
||||
crosswalks: featureCount(path.join(root, "crosswalks.geojson")),
|
||||
|
||||
@@ -34,7 +34,7 @@ function compileArea(configPath) {
|
||||
model: { roads: model.roads, endpoints: model.endpoints, connections: model.connections },
|
||||
movements: compiled.movements,
|
||||
diagnostics: compiled.diagnostics,
|
||||
layers: { roadSurface: "layers/road_surface.geojson", sidewalkSurface: "layers/sidewalk_surface.geojson", intersectionSurface: "layers/intersection_surface.geojson", laneCenterlines: "layers/lane_centerlines.geojson", laneSeparators: "layers/lane_separators.geojson", directionArrows: "layers/direction_arrows.geojson", turnArrows: "layers/turn_arrows.geojson", crosswalks: "layers/crosswalks.geojson", vehicleStopLines: "layers/vehicle_stop_lines.geojson", connectors: "layers/connectors.geojson" },
|
||||
layers: { roadSurface: "layers/road_surface.geojson", sidewalkSurface: "layers/sidewalk_surface.geojson", intersectionSurface: "layers/intersection_surface.geojson", laneCenterlines: "layers/lane_centerlines.geojson", laneSeparators: "layers/lane_separators.geojson", centerLines: "layers/center_lines.geojson", directionArrows: "layers/direction_arrows.geojson", turnArrows: "layers/turn_arrows.geojson", crosswalks: "layers/crosswalks.geojson", vehicleStopLines: "layers/vehicle_stop_lines.geojson", connectors: "layers/connectors.geojson" },
|
||||
};
|
||||
const comparison = compareOsm2Streets(area, result.model, compiled);
|
||||
writeJsonAtomic(path.join(staging, "compiled.json"), result);
|
||||
@@ -45,6 +45,7 @@ function compileArea(configPath) {
|
||||
writeJsonAtomic(path.join(staging, "layers", "intersection_surface.geojson"), compiled.intersectionSurface);
|
||||
writeJsonAtomic(path.join(staging, "layers", "lane_centerlines.geojson"), compiled.laneCenterlines);
|
||||
writeJsonAtomic(path.join(staging, "layers", "lane_separators.geojson"), compiled.laneSeparators);
|
||||
writeJsonAtomic(path.join(staging, "layers", "center_lines.geojson"), compiled.centerLines);
|
||||
writeJsonAtomic(path.join(staging, "layers", "direction_arrows.geojson"), compiled.directionArrows);
|
||||
writeJsonAtomic(path.join(staging, "layers", "turn_arrows.geojson"), compiled.turnArrows);
|
||||
writeJsonAtomic(path.join(staging, "layers", "crosswalks.geojson"), compiled.crosswalks);
|
||||
@@ -86,6 +87,7 @@ function compareOsm2Streets(area, model, compiled) {
|
||||
nativeMaxJunctionExpansionRatio: junctions.reduce((maximum, feature) => Math.max(maximum, Number(feature.properties.expansion_ratio) || 0), 0),
|
||||
nativeLaneCenterlineFeatures: compiled.laneCenterlines.features.length,
|
||||
nativeLaneSeparatorFeatures: compiled.laneSeparators.features.length,
|
||||
nativeCenterLineFeatures: compiled.centerLines.features.length,
|
||||
nativeDirectionArrowFeatures: compiled.directionArrows.features.length,
|
||||
nativeTurnArrowFeatures: compiled.turnArrows.features.length,
|
||||
nativeCrosswalkFeatures: compiled.crosswalks.features.length,
|
||||
|
||||
@@ -12,6 +12,9 @@ const DIRECTION_ARROW_INTERVAL_METERS = 32;
|
||||
const DIRECTION_ARROW_ENDPOINT_BUFFER_METERS = 14;
|
||||
const STOP_LINE_OFFSET_METERS = 2.7;
|
||||
const STOP_LINE_MAX_APPROACH_DISTANCE_METERS = 25;
|
||||
const CENTER_LINE_DASH_LENGTH_METERS = 2;
|
||||
const CENTER_LINE_DASH_GAP_METERS = 2;
|
||||
const CENTER_LINE_WIDTH_METERS = .25;
|
||||
|
||||
function parseOsmRoads(xml) {
|
||||
const nodes = new Map();
|
||||
@@ -244,12 +247,39 @@ function compileGeometry(model, overrides = { overrides: [] }) {
|
||||
}
|
||||
const lanes = compileLaneCenterlines(model, diagnostics, junctionPlans);
|
||||
const controls = compileControlMarkings(model, lanes, diagnostics);
|
||||
const centerLines = compileCenterLines(model, junctionPlans, controls, diagnostics);
|
||||
const markings = compileLaneMarkings(model, lanes, diagnostics, junctionPlans, controls);
|
||||
const sidewalks = compileSidewalkSurfaces(model, diagnostics, junctionPlans);
|
||||
const connectorResult = compileConnectors(model, lanes, diagnostics, overrides);
|
||||
const junctionFeatures = compileJunctionSurfaces(model, junctionPlans, connectorResult.features, connectorResult.movements, diagnostics);
|
||||
validateConnectorContainment(connectorResult.features, junctionFeatures, diagnostics);
|
||||
return { roadSurface: { type: "FeatureCollection", features }, sidewalkSurface: { type: "FeatureCollection", features: sidewalks }, intersectionSurface: { type: "FeatureCollection", features: junctionFeatures }, laneCenterlines: { type: "FeatureCollection", features: lanes.features }, laneSeparators: { type: "FeatureCollection", features: markings.separators }, directionArrows: { type: "FeatureCollection", features: markings.directionArrows }, turnArrows: { type: "FeatureCollection", features: markings.turnArrows }, crosswalks: { type: "FeatureCollection", features: controls.crosswalks }, vehicleStopLines: { type: "FeatureCollection", features: controls.stopLines }, connectors: { type: "FeatureCollection", features: connectorResult.features }, movements: connectorResult.movements, diagnostics };
|
||||
return { roadSurface: { type: "FeatureCollection", features }, sidewalkSurface: { type: "FeatureCollection", features: sidewalks }, intersectionSurface: { type: "FeatureCollection", features: junctionFeatures }, laneCenterlines: { type: "FeatureCollection", features: lanes.features }, laneSeparators: { type: "FeatureCollection", features: markings.separators }, centerLines: { type: "FeatureCollection", features: centerLines }, directionArrows: { type: "FeatureCollection", features: markings.directionArrows }, turnArrows: { type: "FeatureCollection", features: markings.turnArrows }, crosswalks: { type: "FeatureCollection", features: controls.crosswalks }, vehicleStopLines: { type: "FeatureCollection", features: controls.stopLines }, connectors: { type: "FeatureCollection", features: connectorResult.features }, movements: connectorResult.movements, diagnostics };
|
||||
}
|
||||
|
||||
function compileCenterLines(model, junctionPlans, controls, diagnostics) {
|
||||
const features = [];
|
||||
const controlFeatures = [...controls.crosswalks, ...controls.stopLines];
|
||||
const segments = new Map();
|
||||
for (const road of model.roads) {
|
||||
if (!segments.has(road.segmentId)) segments.set(road.segmentId, []);
|
||||
segments.get(road.segmentId).push(road);
|
||||
}
|
||||
for (const [segmentId, roads] of segments) {
|
||||
const forward = roads.find((road) => road.direction === "forward");
|
||||
const backward = roads.find((road) => road.direction === "backward");
|
||||
if (roads.length !== 2 || !forward || !backward || forward.highway === "service" || backward.highway === "service") continue;
|
||||
const line = trimLineAtJunctions(forward.centerline, forward.sourceNodeIds, junctionPlans);
|
||||
const length = lineLengthMeters(line);
|
||||
if (line.length < 2 || !Number.isFinite(length)) { diagnostics.push(diagnostic("warning", segmentId, forward.osmWayIds, "invalid-center-line", "双向道路无法生成有效道路中心虚线。", forward.centerline[0])); continue; }
|
||||
for (let start = 0, dashIndex = 1; start + CENTER_LINE_DASH_LENGTH_METERS <= length; start += CENTER_LINE_DASH_LENGTH_METERS + CENTER_LINE_DASH_GAP_METERS, dashIndex += 1) {
|
||||
const placement = pointAndAxisAlongLine(line, start + CENTER_LINE_DASH_LENGTH_METERS / 2);
|
||||
if (!placement) continue;
|
||||
const ring = rectangleAt(placement.point, placement.axis, [-placement.axis[1], placement.axis[0]], CENTER_LINE_DASH_LENGTH_METERS, CENTER_LINE_WIDTH_METERS, 0);
|
||||
if (ringsOverlapControl([ring], controlFeatures)) continue;
|
||||
features.push({ type: "Feature", properties: { native_id: `center-line:${segmentId}:${dashIndex}`, segment_id: segmentId, road_id: forward.id, directional_road_ids: roads.map((road) => road.id).join(","), osm_way_ids: forward.osmWayIds.join(","), dash_index: dashIndex, dash_length_m: CENTER_LINE_DASH_LENGTH_METERS, dash_gap_m: CENTER_LINE_DASH_GAP_METERS, placement_rule: "native-bidirectional-centerline/v1", provenance: "native-road-center-line/v1" }, geometry: { type: "Polygon", coordinates: [ring] } });
|
||||
}
|
||||
}
|
||||
return features;
|
||||
}
|
||||
|
||||
function compileControlMarkings(model, lanes, diagnostics) {
|
||||
|
||||
@@ -48,7 +48,7 @@ function handle(request, response, area, configPath) {
|
||||
function state(area) {
|
||||
const nativeDir = area.outputs.nativeRoadDir;
|
||||
const osm2streetsRoadSurface = path.join(area.outputs.geojsonDir, "road_surface.geojson");
|
||||
return { areaId: area.id, compiled: readCompiled(area), overrides: loadOverrides(area.outputs.nativeRoadOverrides), comparison: readJson(path.join(nativeDir, "comparison.json")), layers: { nativeRoadSurface: readLayer(path.join(nativeDir, "layers", "road_surface.geojson")), nativeSidewalkSurface: readLayer(path.join(nativeDir, "layers", "sidewalk_surface.geojson")), nativeIntersectionSurface: readLayer(path.join(nativeDir, "layers", "intersection_surface.geojson")), laneCenterlines: readLayer(path.join(nativeDir, "layers", "lane_centerlines.geojson")), laneSeparators: readLayer(path.join(nativeDir, "layers", "lane_separators.geojson")), directionArrows: readLayer(path.join(nativeDir, "layers", "direction_arrows.geojson")), turnArrows: readLayer(path.join(nativeDir, "layers", "turn_arrows.geojson")), crosswalks: readLayer(path.join(nativeDir, "layers", "crosswalks.geojson")), vehicleStopLines: readLayer(path.join(nativeDir, "layers", "vehicle_stop_lines.geojson")), connectors: readLayer(path.join(nativeDir, "layers", "connectors.geojson")), osm2streetsRoadSurface: fs.existsSync(osm2streetsRoadSurface) ? readLayer(osm2streetsRoadSurface) : null } };
|
||||
return { areaId: area.id, compiled: readCompiled(area), overrides: loadOverrides(area.outputs.nativeRoadOverrides), comparison: readJson(path.join(nativeDir, "comparison.json")), layers: { nativeRoadSurface: readLayer(path.join(nativeDir, "layers", "road_surface.geojson")), nativeSidewalkSurface: readLayer(path.join(nativeDir, "layers", "sidewalk_surface.geojson")), nativeIntersectionSurface: readLayer(path.join(nativeDir, "layers", "intersection_surface.geojson")), laneCenterlines: readLayer(path.join(nativeDir, "layers", "lane_centerlines.geojson")), laneSeparators: readLayer(path.join(nativeDir, "layers", "lane_separators.geojson")), centerLines: readLayer(path.join(nativeDir, "layers", "center_lines.geojson")), directionArrows: readLayer(path.join(nativeDir, "layers", "direction_arrows.geojson")), turnArrows: readLayer(path.join(nativeDir, "layers", "turn_arrows.geojson")), crosswalks: readLayer(path.join(nativeDir, "layers", "crosswalks.geojson")), vehicleStopLines: readLayer(path.join(nativeDir, "layers", "vehicle_stop_lines.geojson")), connectors: readLayer(path.join(nativeDir, "layers", "connectors.geojson")), osm2streetsRoadSurface: fs.existsSync(osm2streetsRoadSurface) ? readLayer(osm2streetsRoadSurface) : null } };
|
||||
}
|
||||
function readCompiled(area) { return readJson(path.join(area.outputs.nativeRoadDir, "compiled.json")); }
|
||||
function readJson(file) { return JSON.parse(fs.readFileSync(file, "utf8")); }
|
||||
|
||||
@@ -27,6 +27,14 @@ assert.equal(geometry.sidewalkSurface.features.length, 2);
|
||||
assert.ok(geometry.sidewalkSurface.features.every((feature) => feature.geometry.coordinates[0].length >= 5));
|
||||
assert.equal(geometry.laneCenterlines.features.length, model.roads.reduce((sum, road) => sum + road.laneCount, 0));
|
||||
assert.ok(geometry.laneSeparators.features.every((feature) => feature.geometry.type === "Polygon" && feature.properties.provenance === "native-road-lane-separator/v1"));
|
||||
assert.ok(geometry.centerLines.features.length > 0);
|
||||
assert.ok(geometry.centerLines.features.every((feature) => feature.geometry.type === "Polygon" && feature.properties.provenance === "native-road-center-line/v1" && feature.properties.dash_length_m === 2 && feature.properties.dash_gap_m === 2));
|
||||
for (const feature of geometry.centerLines.features) {
|
||||
const ring = feature.geometry.coordinates[0];
|
||||
const lengths = [distance(ring[0], ring[1]), distance(ring[1], ring[2])].sort((a, b) => a - b);
|
||||
assert.ok(Math.abs(lengths[0] - .25) < .01 && Math.abs(lengths[1] - 2) < .01);
|
||||
assert.ok(feature.properties.segment_id && feature.properties.directional_road_ids && feature.properties.osm_way_ids && feature.properties.placement_rule);
|
||||
}
|
||||
assert.ok(geometry.directionArrows.features.every((feature) => feature.geometry.type === "Polygon" && feature.properties.provenance === "native-road-direction-arrow/v1" && feature.properties.placement_interval_meters === 32));
|
||||
assert.ok(geometry.turnArrows.features.every((feature) => feature.geometry.type === "Polygon" && feature.properties.provenance === "native-road-turn-arrow/v1"));
|
||||
assert.ok(geometry.connectors.features.length > 0);
|
||||
@@ -46,10 +54,15 @@ assert.equal(controlGeometry.vehicleStopLines.features.length, 1);
|
||||
assert.ok(controlGeometry.crosswalks.features.every((feature) => feature.properties.crossing_node_id === "2" && feature.properties.provenance === "native-road-crosswalk/v1"));
|
||||
assert.ok(controlGeometry.vehicleStopLines.features.every((feature) => feature.properties.crossing_node_id === "2" && feature.properties.provenance === "native-road-stop-line/v1"));
|
||||
assert.ok(controlGeometry.diagnostics.some((item) => item.rule === "crossing-no-native-lane" && item.sourceIds.includes("5")));
|
||||
assert.ok(controlGeometry.centerLines.features.length > 0);
|
||||
assert.ok(controlGeometry.centerLines.features.every((dash) => ![...controlGeometry.crosswalks.features, ...controlGeometry.vehicleStopLines.features].some((control) => ringsOverlap(dash.geometry.coordinates[0], control.geometry.coordinates[0]))));
|
||||
const arrowControlOsm = `<osm><node id="1" lon="114" lat="30"/><node id="2" lon="114.00095" lat="30"><tag k="highway" v="crossing"/><tag k="crossing:markings" v="zebra"/></node><node id="3" lon="114.001" lat="30"/><way id="63"><nd ref="1"/><nd ref="2"/><nd ref="3"/><tag k="highway" v="residential"/><tag k="oneway" v="yes"/><tag k="lanes" v="1"/><tag k="turn:lanes" v="through"/></way></osm>`;
|
||||
const arrowControlGeometry = compileGeometry(compileRoadModel(arrowControlOsm, empty));
|
||||
assert.ok(arrowControlGeometry.turnArrows.features.length > 0);
|
||||
assert.ok(arrowControlGeometry.turnArrows.features.every((feature) => feature.properties.placement_distance_meters > 6));
|
||||
assert.equal(arrowControlGeometry.centerLines.features.length, 0);
|
||||
const serviceCenterLineOsm = `<osm><node id="1" lon="114" lat="30"/><node id="2" lon="114.001" lat="30"/><way id="64"><nd ref="1"/><nd ref="2"/><tag k="highway" v="service"/></way></osm>`;
|
||||
assert.equal(compileGeometry(compileRoadModel(serviceCenterLineOsm, empty)).centerLines.features.length, 0);
|
||||
const crossOsm = `<osm><node id="1" lon="114" lat="30"/><node id="2" lon="114.001" lat="30"/><node id="3" lon="114.002" lat="30"/><node id="4" lon="114.001" lat="30.001"/><node id="5" lon="114.001" lat="29.999"/><way id="40"><nd ref="1"/><nd ref="2"/><tag k="highway" v="residential"/><tag k="sidewalk" v="both"/></way><way id="41"><nd ref="2"/><nd ref="3"/><tag k="highway" v="residential"/><tag k="sidewalk" v="both"/></way><way id="42"><nd ref="5"/><nd ref="2"/><tag k="highway" v="residential"/><tag k="sidewalk" v="both"/></way><way id="43"><nd ref="2"/><nd ref="4"/><tag k="highway" v="residential"/><tag k="sidewalk" v="both"/></way></osm>`;
|
||||
const crossCenter = [114.001, 30];
|
||||
const crossGeometry = compileGeometry(compileRoadModel(crossOsm, empty));
|
||||
@@ -112,6 +125,9 @@ try {
|
||||
const compiledArea = compileArea(config);
|
||||
assert.equal(compiledArea.result.areaId, "fresh");
|
||||
assert.ok(fs.existsSync(path.join(outputRoot, "fresh", "native-road", "compiled.json")));
|
||||
const centerLineLayer = JSON.parse(fs.readFileSync(path.join(outputRoot, "fresh", "native-road", "layers", "center_lines.geojson"), "utf8"));
|
||||
assert.equal(centerLineLayer.type, "FeatureCollection");
|
||||
assert.equal(centerLineLayer.features.length, compiledArea.comparison.nativeCenterLineFeatures);
|
||||
assert.equal(compiledArea.comparison.schema, "native-road-comparison/v2");
|
||||
assert.equal(compiledArea.comparison.nativeRoadCount, compiledArea.result.model.roads.length);
|
||||
assert.equal(compiledArea.comparison.nativePublishedMovementCount, compiledArea.result.movements.filter((movement) => movement.geometryPublished).length);
|
||||
@@ -123,4 +139,13 @@ try {
|
||||
} finally {
|
||||
fs.rmSync(freshArea, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
function distance(a, b) {
|
||||
return Math.hypot((b[0] - a[0]) * 111320 * Math.cos(a[1] * Math.PI / 180), (b[1] - a[1]) * 111320);
|
||||
}
|
||||
function ringsOverlap(first, second) {
|
||||
const bounds = (ring) => [Math.min(...ring.map((point) => point[0])), Math.min(...ring.map((point) => point[1])), Math.max(...ring.map((point) => point[0])), Math.max(...ring.map((point) => point[1]))];
|
||||
const a = bounds(first); const b = bounds(second);
|
||||
return !(a[0] > b[2] || a[2] < b[0] || a[1] > b[3] || a[3] < b[1]);
|
||||
}
|
||||
console.log("native road tests passed");
|
||||
|
||||
@@ -27,6 +27,10 @@ assert.match(app, /外缘扩张倍率/);
|
||||
assert.match(app, /最大外缘扩张/);
|
||||
assert.match(app, /道路方向箭头/);
|
||||
assert.match(app, /native-road-direction-arrow\/v1/);
|
||||
assert.match(app, /data-layer="centerLines" type="checkbox" checked> 道路中心虚线/);
|
||||
assert.match(app, /centerLines: new VectorLayer/);
|
||||
assert.match(app, /state\.layers\.centerLines/);
|
||||
assert.match(app, /native-road-center-line\/v1/);
|
||||
assert.match(app, /data-layer="controls" type="checkbox" checked> 斑马线与停止线/);
|
||||
assert.match(app, /controls: new VectorLayer/);
|
||||
assert.match(app, /state\.layers\.crosswalks/);
|
||||
|
||||
@@ -45,9 +45,11 @@ const directionArrowsToggle = document.createElement("label");
|
||||
directionArrowsToggle.innerHTML = '<input data-layer="directionArrows" type="checkbox" checked> 道路方向箭头';
|
||||
const markingsToggle = document.createElement("label");
|
||||
markingsToggle.innerHTML = '<input data-layer="markings" type="checkbox" checked> 车道分隔线与路口转向箭头';
|
||||
const centerLinesToggle = document.createElement("label");
|
||||
centerLinesToggle.innerHTML = '<input data-layer="centerLines" type="checkbox" checked> 道路中心虚线';
|
||||
const controlsToggle = document.createElement("label");
|
||||
controlsToggle.innerHTML = '<input data-layer="controls" type="checkbox" checked> 斑马线与停止线';
|
||||
document.querySelector('[data-layer="lanes"]').closest("label").after(directionArrowsToggle, markingsToggle, controlsToggle);
|
||||
document.querySelector('[data-layer="lanes"]').closest("label").after(directionArrowsToggle, markingsToggle, centerLinesToggle, controlsToggle);
|
||||
|
||||
let state;
|
||||
let selectedRoad = null;
|
||||
@@ -66,6 +68,7 @@ const layers = {
|
||||
lanes: new VectorLayer({ source: source(), style: laneStyle }),
|
||||
directionArrows: new VectorLayer({ source: source(), style: markingStyle }),
|
||||
markings: new VectorLayer({ source: source(), style: markingStyle }),
|
||||
centerLines: new VectorLayer({ source: source(), style: centerLineStyle }),
|
||||
controls: new VectorLayer({ source: source(), style: markingStyle }),
|
||||
osmDirection: new VectorLayer({ source: source(), style: (feature) => new Style({ image: new RegularShape({ points: 3, radius: 9, rotation: feature.get("rotation"), fill: new Fill({ color: "#006e91" }), stroke: new Stroke({ color: "#fff", width: 1.5 }) }) }), zIndex: 11 }),
|
||||
connectors: new VectorLayer({ source: source(), style: (feature) => effectiveConnectorEnabled(feature.getProperties()) ? new Style({ stroke: new Stroke({ color: roadIdFromLane(feature.get("from_lane_id")) === selectedRoad?.id ? "#d1226f" : "#ad3a76", width: roadIdFromLane(feature.get("from_lane_id")) === selectedRoad?.id ? 4 : 2, lineDash: [7, 5] }) }) : null }),
|
||||
@@ -73,8 +76,8 @@ 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.sidewalks, layers.osm, layers.lanes, layers.directionArrows, layers.markings, layers.controls, 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.controls, layers.connectors, layers.native, layers.diagnostics].includes(layer), hitTolerance: 8, style: new Style({ stroke: new Stroke({ color: "#005e89", width: 5 }), fill: new Fill({ color: "rgba(0, 94, 137, .18)" }) }) });
|
||||
const map = new Map({ target: "map", layers: [layers.reference, layers.native, layers.sidewalks, layers.osm, layers.lanes, layers.directionArrows, layers.markings, layers.centerLines, layers.controls, 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.controls, layers.connectors, layers.native, layers.diagnostics].includes(layer), hitTolerance: 8, style: new Style({ stroke: new Stroke({ color: "#005e89", width: 5 }), fill: new Fill({ color: "rgba(0, 94, 137, .18)" }) }) });
|
||||
map.addInteraction(select);
|
||||
select.on("select", ({ selected }) => {
|
||||
const feature = selected[0];
|
||||
@@ -87,15 +90,17 @@ select.on("select", ({ selected }) => {
|
||||
const road = roadForFeature(feature);
|
||||
const directionArrow = provenance === "native-road-direction-arrow/v1";
|
||||
const turnArrow = provenance === "native-road-turn-arrow/v1";
|
||||
const centerLine = provenance === "native-road-center-line/v1";
|
||||
const crosswalk = provenance === "native-road-crosswalk/v1";
|
||||
const stopLine = provenance === "native-road-stop-line/v1";
|
||||
const markingType = crosswalk ? "斑马线" : stopLine ? "停止线" : directionArrow ? "道路方向箭头" : turnArrow ? "路口转向箭头" : "车道分隔线";
|
||||
const markingType = centerLine ? "道路中心虚线" : crosswalk ? "斑马线" : stopLine ? "停止线" : directionArrow ? "道路方向箭头" : turnArrow ? "路口转向箭头" : "车道分隔线";
|
||||
selectRoad(road);
|
||||
evidence.textContent = JSON.stringify({
|
||||
标线类型: markingType,
|
||||
人行横道节点: crosswalk || stopLine ? feature.get("crossing_node_id") : null,
|
||||
OSM道路: feature.get("osm_way_ids"),
|
||||
原生道路: feature.get("road_id"),
|
||||
道路段: centerLine ? feature.get("segment_id") : null,
|
||||
方向: feature.get("direction"),
|
||||
车道: feature.get("lane_id") || feature.get("lane_index") || `${feature.get("left_lane_index")} 与 ${feature.get("right_lane_index")} 之间`,
|
||||
转向: turnArrow ? feature.get("maneuver") : null,
|
||||
@@ -125,6 +130,7 @@ 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 markingStyle() { return new Style({ fill: new Fill({ color: "#f5f6ee" }), stroke: new Stroke({ color: "#d9dacf", width: 1 }) }); }
|
||||
function centerLineStyle() { return new Style({ fill: new Fill({ color: "#f5be2a" }), stroke: new Stroke({ color: "#d29d16", width: .8 }) }); }
|
||||
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.
|
||||
@@ -159,6 +165,7 @@ function updateSources() {
|
||||
layers.lanes.getSource().clear(); layers.lanes.getSource().addFeatures(readFeatures(state.layers.laneCenterlines));
|
||||
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.controls.getSource().clear(); layers.controls.getSource().addFeatures([...readFeatures(state.layers.crosswalks), ...readFeatures(state.layers.vehicleStopLines)]);
|
||||
layers.connectors.getSource().clear(); layers.connectors.getSource().addFeatures(readFeatures(state.layers.connectors));
|
||||
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 })) }));
|
||||
@@ -219,6 +226,7 @@ function renderSummary() {
|
||||
["普通构面路口", comparison.nativeApproachEnvelopeJunctions],
|
||||
["兜底构面路口", comparison.nativeFallbackJunctions],
|
||||
["最大外缘扩张", comparison.nativeMaxJunctionExpansionRatio],
|
||||
["道路中心虚线", comparison.nativeCenterLineFeatures],
|
||||
["道路方向箭头", comparison.nativeDirectionArrowFeatures],
|
||||
["路口转向箭头", comparison.nativeTurnArrowFeatures],
|
||||
["斑马线条带", comparison.nativeCrosswalkFeatures],
|
||||
@@ -250,6 +258,7 @@ scenePreviewToggle.onchange = () => {
|
||||
layers.osmDirection.setVisible(!scenePreview && document.querySelector('[data-layer="osm"]').checked);
|
||||
layers.connectors.setVisible(!scenePreview && document.querySelector('[data-layer="lanes"]').checked);
|
||||
layers.sidewalks.setVisible(document.querySelector('[data-layer="sidewalks"]').checked);
|
||||
layers.centerLines.setVisible(document.querySelector('[data-layer="centerLines"]').checked);
|
||||
layers.controls.setVisible(document.querySelector('[data-layer="controls"]').checked);
|
||||
layers.diagnostics.setVisible(!scenePreview);
|
||||
layers.native.changed(); layers.sidewalks.changed();
|
||||
|
||||
Reference in New Issue
Block a user