feat: add native preview traffic simulation
This commit is contained in:
@@ -267,7 +267,7 @@ function compileGeometry(model, overrides = { overrides: [] }, options = {}) {
|
||||
const centerLines = compileCenterLines(model, overrides, junctionPlans, controls, diagnostics);
|
||||
const markings = compileLaneMarkings(model, overrides, lanes, diagnostics, junctionPlans, controls);
|
||||
const sidewalks = compileSidewalkSurfaces(model, diagnostics, junctionPlans);
|
||||
const connectorResult = compileConnectors(model, lanes, diagnostics, overrides);
|
||||
const connectorResult = compileConnectors(model, lanes, diagnostics, overrides, junctionPlans);
|
||||
const junctionFeatures = compileJunctionSurfaces(model, junctionPlans, connectorResult.features, connectorResult.movements, diagnostics);
|
||||
validateConnectorContainment(connectorResult.features, junctionFeatures, diagnostics);
|
||||
return { roadSurface: { type: "FeatureCollection", features }, edgeLines: { type: "FeatureCollection", features: edgeLines }, 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 };
|
||||
@@ -653,7 +653,7 @@ function compileLaneCenterlines(model, diagnostics, junctionPlans) {
|
||||
return { features, byRoadId };
|
||||
}
|
||||
|
||||
function compileConnectors(model, lanes, diagnostics, overrides) {
|
||||
function compileConnectors(model, lanes, diagnostics, overrides, junctionPlans) {
|
||||
const features = [];
|
||||
const movements = [];
|
||||
for (const connection of model.connections.filter((item) => item.enabled)) {
|
||||
@@ -669,8 +669,12 @@ function compileConnectors(model, lanes, diagnostics, overrides) {
|
||||
const override = laneOverride(overrides, defaultFromLane.id, defaultToLane.id);
|
||||
if ((!laneAllowsTurn(fromRoad, index, turn) && override?.enabled !== true) || override?.enabled === false) continue;
|
||||
const from = defaultFromLane.coordinates.at(-1); const to = defaultToLane.coordinates[0];
|
||||
const control = connectorControlPoint(model, connection, from, to);
|
||||
const coordinates = quadraticCurve(from, control, to, 12);
|
||||
const plan = junctionPlans.get(connection.nodeId);
|
||||
// Cross intersections retain the earlier center-node curve while T junctions
|
||||
// use lane tangents so their through movement does not bow toward the stem.
|
||||
const coordinates = plan?.segmentIds.size === 4
|
||||
? quadraticCurve(from, endpointCoordinate(model, connection.fromEndpointId), to, 12)
|
||||
: connectorCurve(defaultFromLane.coordinates, defaultToLane.coordinates, turn);
|
||||
const length = lineLengthMeters(coordinates);
|
||||
const id = `movement:${connection.id}:${defaultFromLane.id}->${defaultToLane.id}`;
|
||||
const provenance = override ? `override:${override.id}` : connection.provenance;
|
||||
@@ -712,12 +716,47 @@ function targetLaneIndex(turn, sourceIndex, sourceCount, targetCount) {
|
||||
if (turn === "uturn") return 0;
|
||||
return Math.min(targetCount - 1, Math.round(sourceIndex / Math.max(1, sourceCount - 1) * Math.max(0, targetCount - 1)));
|
||||
}
|
||||
function connectorControlPoint(model, connection, from, to) {
|
||||
const node = endpointCoordinate(model, connection.fromEndpointId);
|
||||
if (!node) return [(from[0] + to[0]) / 2, (from[1] + to[1]) / 2];
|
||||
// Nearby manual joins may not share exactly the same point. The midpoint
|
||||
// keeps their curve smooth without rewriting the authoritative OSM geometry.
|
||||
return node;
|
||||
function connectorCurve(incoming, outgoing, turn) {
|
||||
const start = incoming.at(-1);
|
||||
const end = outgoing[0];
|
||||
if (turn === "through") return lineCurve(start, end, 12);
|
||||
const incomingHeading = headingDegrees(incoming.at(-2), start);
|
||||
const outgoingHeading = headingDegrees(end, outgoing[1]);
|
||||
const chord = distanceMeters(start, end);
|
||||
const incomingSpan = distanceMeters(incoming.at(-2), start);
|
||||
const outgoingSpan = distanceMeters(end, outgoing[1]);
|
||||
const tangentIntersection = intersectTangentRays(start, end, incomingHeading, outgoingHeading);
|
||||
const fallbackDistance = Math.min(8, Math.max(.75, Math.min(chord * .42, incomingSpan * .8, outgoingSpan * .8)));
|
||||
const firstDistance = tangentIntersection && tangentIntersection.incoming >= 0 ? Math.min(tangentIntersection.incoming, Math.min(8, Math.max(.75, incomingSpan * 2.4))) / 3 : fallbackDistance;
|
||||
const secondDistance = tangentIntersection && tangentIntersection.outgoing >= 0 ? Math.min(tangentIntersection.outgoing, Math.min(8, Math.max(.75, outgoingSpan * 2.4))) / 3 : fallbackDistance;
|
||||
const firstControl = offsetCoordinate(start, incomingHeading, firstDistance);
|
||||
const secondControl = offsetCoordinate(end, outgoingHeading + 180, secondDistance);
|
||||
return cubicBezier(start, firstControl, secondControl, end, 12);
|
||||
}
|
||||
|
||||
function intersectTangentRays(start, end, incomingHeading, outgoingHeading) {
|
||||
const incoming = headingVector(incomingHeading);
|
||||
const outgoing = headingVector(outgoingHeading);
|
||||
const delta = project(end, start);
|
||||
const cross = incoming[0] * outgoing[1] - incoming[1] * outgoing[0];
|
||||
if (Math.abs(cross) < 1e-6) return null;
|
||||
return {
|
||||
incoming: (delta[0] * outgoing[1] - delta[1] * outgoing[0]) / cross,
|
||||
outgoing: (delta[0] * incoming[1] - delta[1] * incoming[0]) / cross,
|
||||
};
|
||||
}
|
||||
|
||||
function lineCurve(start, end, segments) {
|
||||
return Array.from({ length: segments + 1 }, (_, index) => interpolate(start, end, index / segments));
|
||||
}
|
||||
|
||||
function cubicBezier(a, firstControl, secondControl, b, segments) {
|
||||
const result = [];
|
||||
for (let index = 0; index <= segments; index += 1) {
|
||||
const t = index / segments; const u = 1 - t;
|
||||
result.push([u ** 3 * a[0] + 3 * u * u * t * firstControl[0] + 3 * u * t * t * secondControl[0] + t ** 3 * b[0], u ** 3 * a[1] + 3 * u * u * t * firstControl[1] + 3 * u * t * t * secondControl[1] + t ** 3 * b[1]]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function quadraticCurve(a, control, b, segments) {
|
||||
|
||||
Reference in New Issue
Block a user