feat: add native road center lines

This commit is contained in:
2026-08-17 10:44:01 +08:00
parent 7dc5c947a3
commit fb863dafb2
18 changed files with 324 additions and 12 deletions

View File

@@ -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) {