fix: disable native edge lines by default
This commit is contained in:
@@ -107,6 +107,9 @@ function normalizeAreaConfig(raw, options = {}) {
|
||||
turnLaneArrows: {
|
||||
enabled: booleanOption(raw.turnLaneArrows?.enabled, false, "turnLaneArrows.enabled"),
|
||||
},
|
||||
nativeRoad: {
|
||||
edgeLines: booleanOption(raw.nativeRoad?.edgeLines, false, "nativeRoad.edgeLines"),
|
||||
},
|
||||
osm2streets: raw.osm2streets || {
|
||||
debug_each_step: false,
|
||||
dual_carriageway_experiment: false,
|
||||
|
||||
@@ -12,6 +12,8 @@ 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 CROSSWALK_JUNCTION_INSET_METERS = 1.5;
|
||||
const CROSSWALK_MAX_JUNCTION_INSET_METERS = 4;
|
||||
const CENTER_LINE_DASH_LENGTH_METERS = 2;
|
||||
const CENTER_LINE_DASH_GAP_METERS = 2;
|
||||
const CENTER_LINE_WIDTH_METERS = .25;
|
||||
@@ -240,7 +242,7 @@ function nearbyManualCandidates(endpoints, from) {
|
||||
return endpoints.filter((to) => to.side === "start" && to.roadId !== from.roadId && !sameOsmWay(endpoints, from.roadId, to.roadId)).map((to) => ({ to, distanceMeters: distanceMeters(from.coordinate, to.coordinate) })).filter((item) => item.distanceMeters <= 35).sort((a, b) => a.distanceMeters - b.distanceMeters).slice(0, 3).map(({ to, distanceMeters: meters }) => ({ toEndpointId: to.id, roadId: to.roadId, distanceMeters: Math.round(meters * 10) / 10 }));
|
||||
}
|
||||
|
||||
function compileGeometry(model, overrides = { overrides: [] }) {
|
||||
function compileGeometry(model, overrides = { overrides: [] }, options = {}) {
|
||||
const diagnostics = [...model.diagnostics];
|
||||
const junctionPlans = compileJunctionPlans(model);
|
||||
const features = [];
|
||||
@@ -260,8 +262,8 @@ function compileGeometry(model, overrides = { overrides: [] }) {
|
||||
features.push({ type: "Feature", properties: { native_id: surfaceId, directional_road_ids: directions.map((item) => item.id).join(","), osm_way_ids: road.osmWayIds.join(","), source_road_id: road.sourceRoadId, width_m: totalWidth, lane_count: directions.reduce((sum, item) => item.laneCount + sum, 0), provenance: JSON.stringify(directions.map((item) => item.provenance)), override_ids: directions.flatMap((item) => item.appliedOverrideIds).join(",") }, geometry: { type: "Polygon", coordinates: [ring] } });
|
||||
}
|
||||
const lanes = compileLaneCenterlines(model, diagnostics, junctionPlans);
|
||||
const edgeLines = compileEdgeLines(model, overrides, junctionPlans);
|
||||
const controls = compileControlMarkings(model, lanes, diagnostics);
|
||||
const edgeLines = options.edgeLines === false ? [] : compileEdgeLines(model, overrides, junctionPlans);
|
||||
const controls = compileControlMarkings(model, lanes, diagnostics, junctionPlans);
|
||||
const centerLines = compileCenterLines(model, overrides, junctionPlans, controls, diagnostics);
|
||||
const markings = compileLaneMarkings(model, overrides, lanes, diagnostics, junctionPlans, controls);
|
||||
const sidewalks = compileSidewalkSurfaces(model, diagnostics, junctionPlans);
|
||||
@@ -275,7 +277,12 @@ function compileEdgeLines(model, overrides, junctionPlans) {
|
||||
const features = [];
|
||||
for (const road of model.roads) {
|
||||
const line = trimLineAtJunctions(road.centerline, road.sourceNodeIds, junctionPlans);
|
||||
for (const offset of [-1, 1]) {
|
||||
const bidirectional = model.roads.some((item) => item.segmentId === road.segmentId && item.id !== road.id);
|
||||
// On a two-way segment, the inner edge is the road centre boundary and is
|
||||
// owned by center_lines. Emit only each directional carriageway's outer
|
||||
// edge; emitting both sides makes the layer look like a second centreline.
|
||||
const offsets = bidirectional ? [-1] : [-1, 1];
|
||||
for (const offset of offsets) {
|
||||
const side = offset < 0 ? "right" : "left";
|
||||
const style = edgeLineStyle(overrides, road.id, side);
|
||||
const centerline = offsetLine(line, offset * road.widthMeters / 2);
|
||||
@@ -337,7 +344,7 @@ function edgeLineStyle(overrides, roadId, side) {
|
||||
return value ? { color: value.color, pattern: value.pattern } : { color: "white", pattern: "solid" };
|
||||
}
|
||||
|
||||
function compileControlMarkings(model, lanes, diagnostics) {
|
||||
function compileControlMarkings(model, lanes, diagnostics, junctionPlans = new Map()) {
|
||||
const crosswalks = []; const stopLines = [];
|
||||
const arrivalEndpointIds = new Set(model.connections.filter((connection) => connection.enabled).map((connection) => connection.fromEndpointId));
|
||||
for (const crossing of model.crossings || []) {
|
||||
@@ -346,23 +353,33 @@ function compileControlMarkings(model, lanes, diagnostics) {
|
||||
if (!candidate || candidate.placement.distance > 12) { diagnostics.push(diagnostic("warning", `crossing:node/${crossing.id}`, [crossing.id], "crossing-no-native-lane", "人行横道无法匹配到安全的原生车道,未生成标线。", crossing.coordinate)); continue; }
|
||||
const approach = candidates.filter((item) => arrivalEndpointIds.has(`endpoint:${item.road.id}:end`) && item.junctionDistanceMeters > STOP_LINE_OFFSET_METERS && item.junctionDistanceMeters <= STOP_LINE_MAX_APPROACH_DISTANCE_METERS).sort((a, b) => a.junctionDistanceMeters - b.junctionDistanceMeters || a.placement.distance - b.placement.distance)[0];
|
||||
const crosswalkCandidate = approach || candidate;
|
||||
const junctionInsetMeters = approach ? crossingJunctionInset(approach, junctionPlans) : 0;
|
||||
const controlCenter = offsetByMeters(crossing.coordinate, crosswalkCandidate.placement.axis, junctionInsetMeters);
|
||||
const { axis } = crosswalkCandidate.placement; const across = [-axis[1], axis[0]];
|
||||
for (let index = 0; index < 6; index += 1) crosswalks.push(controlFeature("crosswalk", crossing, crosswalkCandidate, index + 1, rectangleAt(crossing.coordinate, axis, across, 3, .45, -2.25 + index * .9)));
|
||||
for (let index = 0; index < 6; index += 1) crosswalks.push(controlFeature("crosswalk", crossing, crosswalkCandidate, index + 1, rectangleAt(controlCenter, axis, across, 3, .45, -2.25 + index * .9), { junctionInsetMeters }));
|
||||
if (!approach) { diagnostics.push(diagnostic("info", `crossing:node/${crossing.id}`, [crossing.id], "crossing-no-safe-stop-line", "人行横道没有可确认的路口进口车道,保留斑马线但未生成停止线。", crossing.coordinate)); continue; }
|
||||
const rawRoadPlacement = nearestLanePlacement(approach.road.centerline, crossing.coordinate);
|
||||
const rawRoadPlacement = nearestLanePlacement(approach.road.centerline, controlCenter);
|
||||
const laneOffset = rawRoadPlacement ? project(approach.placement.point, rawRoadPlacement.point) : [0, 0];
|
||||
const lateralOffset = laneOffset[0] * across[0] + laneOffset[1] * across[1];
|
||||
const laneCenterAtCrossing = offsetByMeters(crossing.coordinate, across, lateralOffset);
|
||||
const laneCenterAtCrossing = offsetByMeters(controlCenter, across, lateralOffset);
|
||||
const stopCenter = offsetByMeters(laneCenterAtCrossing, approach.placement.axis, -STOP_LINE_OFFSET_METERS);
|
||||
stopLines.push(controlFeature("stop-line", crossing, approach, 1, rectangleAt(stopCenter, across, approach.placement.axis, approach.road.widthMeters, .45, 0)));
|
||||
stopLines.push(controlFeature("stop-line", crossing, approach, 1, rectangleAt(stopCenter, across, approach.placement.axis, approach.road.widthMeters, .45, 0), { junctionInsetMeters }));
|
||||
}
|
||||
return { crosswalks, stopLines };
|
||||
}
|
||||
|
||||
function crossingJunctionInset(candidate, junctionPlans) {
|
||||
const junctionNodeId = candidate.road.sourceNodeIds.at(-1);
|
||||
const plan = junctionPlans.get(junctionNodeId);
|
||||
if (!plan) return 0;
|
||||
const targetDistance = Math.max(0, plan.cutbackMeters - CROSSWALK_JUNCTION_INSET_METERS);
|
||||
return Math.min(CROSSWALK_MAX_JUNCTION_INSET_METERS, Math.max(0, candidate.junctionDistanceMeters - targetDistance));
|
||||
}
|
||||
|
||||
function nearestLanePlacement(line, target) { let best = null; let traversedMeters = 0; for (let index = 1; index < line.length; index += 1) { const a = line[index - 1]; const b = line[index]; const vector = project(b, a); const length = Math.hypot(...vector); if (!length) continue; const relative = project(target, a); const ratio = Math.max(0, Math.min(1, (relative[0] * vector[0] + relative[1] * vector[1]) / (length * length))); const point = interpolate(a, b, ratio); const distance = distanceMeters(point, target); if (!best || distance < best.distance) best = { point, axis: [vector[0] / length, vector[1] / length], distance, distanceToEndMeters: lineLengthMeters(line) - traversedMeters - length * ratio }; traversedMeters += length; } return best; }
|
||||
function offsetByMeters(point, axis, meters) { return unproject([axis[0] * meters, axis[1] * meters], point); }
|
||||
function rectangleAt(center, axis, across, length, width, offset) { const shifted = offsetByMeters(center, across, offset); const corners = [[-length / 2, -width / 2], [length / 2, -width / 2], [length / 2, width / 2], [-length / 2, width / 2]].map(([forward, side]) => unproject([axis[0] * forward + across[0] * side, axis[1] * forward + across[1] * side], shifted)); return [...corners, corners[0]]; }
|
||||
function controlFeature(kind, crossing, candidate, part, ring) { const stop = kind === "stop-line"; return { type: "Feature", properties: { native_id: `${kind}:node/${crossing.id}:${part}`, crossing_node_id: crossing.id, road_id: candidate.road.id, lane_id: candidate.lane.id, osm_way_ids: candidate.road.osmWayIds.join(","), direction: candidate.road.direction, placement_method: "native-lane-nearest-point/v1", provenance: stop ? "native-road-stop-line/v1" : "native-road-crosswalk/v1" }, geometry: { type: "Polygon", coordinates: [ring] } }; }
|
||||
function controlFeature(kind, crossing, candidate, part, ring, placement = {}) { const stop = kind === "stop-line"; return { type: "Feature", properties: { native_id: `${kind}:node/${crossing.id}:${part}`, crossing_node_id: crossing.id, road_id: candidate.road.id, lane_id: candidate.lane.id, osm_way_ids: candidate.road.osmWayIds.join(","), direction: candidate.road.direction, placement_method: "native-lane-nearest-point/v1", junction_inset_m: Math.round((placement.junctionInsetMeters || 0) * 100) / 100, provenance: stop ? "native-road-stop-line/v1" : "native-road-crosswalk/v1" }, geometry: { type: "Polygon", coordinates: [ring] } }; }
|
||||
|
||||
function compileLaneMarkings(model, overrides, lanes, diagnostics, junctionPlans, controls) {
|
||||
const separators = []; const directionArrows = []; const turnArrows = [];
|
||||
|
||||
Reference in New Issue
Block a user