fix(preview): derive signals from OSM controls

This commit is contained in:
2026-08-07 09:05:27 +08:00
parent 950cd1c4cd
commit 1c077a312e
7 changed files with 315 additions and 73 deletions

View File

@@ -10,6 +10,7 @@ const { makeVehicleGltf } = require("./lib/vehicle-model");
const { VEHICLE_IDS, REVERSED_MODEL_IDS, writePreviewVehicleLibrary } = require("./lib/vehicle-library");
const { allowedTurns, buildVehicleRoute, classifyConnection } = require("./lib/vehicle-route");
const { buildTrafficSignals } = require("./lib/traffic-signals");
const { parseOsm } = require("./lib/osm");
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "preview-assets-"));
const osmPath = path.join(tempDir, "fixture.osm");
@@ -144,24 +145,53 @@ assert.match(previewRuntime, /scene\.requestRender/);
assert.doesNotMatch(previewRuntime, /function addTrafficSignals\(viewer, signalData, start, placement\)/);
assert.doesNotMatch(previewRuntime, /ellipsoid:/);
const signals = buildTrafficSignals(
const trafficIntersection = { type: "FeatureCollection", features: [
{ type: "Feature", geometry: { type: "Polygon", coordinates: [[
[119.9998, 29.9998], [120.0004, 29.9998], [120.0004, 30.0003], [119.9998, 30.0003], [119.9998, 29.9998],
]] } },
] };
const tStopLines = { type: "FeatureCollection", features: [
rectangle(119.99995, 30.00005, 0.00003, 0.000006),
rectangle(120.00010, 30.00025, 0.00003, 0.000006),
rectangle(120.00035, 30.00005, 0.00003, 0.000006),
] };
const control = {
id: "traffic-t", longitude: 120.0001, latitude: 30.00005,
arms: [{ headingDegrees: 270 }, { headingDegrees: 0 }, { headingDegrees: 90 }],
};
const noControlSignals = buildTrafficSignals(tStopLines, trafficIntersection);
assert.equal(noControlSignals.signals.length, 0, "untagged intersections must not create traffic signals");
const tSignals = buildTrafficSignals(tStopLines, trafficIntersection, [control]);
assert.equal(tSignals.version, 3);
assert.equal(tSignals.signals.length, 3, "a tagged T junction has one signal per physical approach");
assert.deepEqual(tSignals.signals.map((signal) => signal.phaseGroup).sort(), [0, 0, 1]);
assert.ok(tSignals.signals.every((signal) => Number.isFinite(signal.headingDegrees)));
assert.equal(tSignals.layout.countdownLateralMeters, 1.15);
assert.equal(tSignals.layout.countdownWidthMeters, 0.82);
assert.ok(tSignals.signals.every((signal) => signal.pose?.head && signal.pose.lenses.length === 3));
const crossSignals = buildTrafficSignals(
{ type: "FeatureCollection", features: [
rectangle(120.0000, 30.0000, 0.00003, 0.000006),
rectangle(120.0002, 30.0000, 0.00003, 0.000006),
] },
{ type: "FeatureCollection", features: [
{ type: "Feature", geometry: { type: "Polygon", coordinates: [[
[119.9998, 29.9998], [120.0004, 29.9998], [120.0004, 30.0003], [119.9998, 30.0003], [119.9998, 29.9998],
]] } },
rectangle(119.99995, 30.00005, 0.00003, 0.000006),
rectangle(120.00010, 30.00025, 0.00003, 0.000006),
rectangle(120.00035, 30.00005, 0.00003, 0.000006),
rectangle(120.00010, 29.99985, 0.00003, 0.000006),
] },
trafficIntersection,
[{ ...control, arms: [{ headingDegrees: 270 }, { headingDegrees: 0 }, { headingDegrees: 90 }, { headingDegrees: 180 }] }],
);
assert.equal(signals.version, 3);
assert.equal(signals.signals.length, 2);
assert.deepEqual(signals.signals.map((signal) => signal.phaseGroup), [0, 1]);
assert.ok(signals.signals.every((signal) => Number.isFinite(signal.headingDegrees)));
assert.equal(signals.layout.countdownLateralMeters, 1.15);
assert.equal(signals.layout.countdownWidthMeters, 0.82);
assert.ok(signals.signals.every((signal) => signal.pose?.head && signal.pose.lenses.length === 3));
assert.equal(crossSignals.signals.length, 4, "a tagged cross junction retains all four approaches");
assert.deepEqual(crossSignals.signals.map((signal) => signal.phaseGroup).sort(), [0, 0, 1, 1]);
const parsedSignalControls = parseOsm(`
<osm><bounds minlon="119" minlat="29" maxlon="121" maxlat="31" />
<node id="active" lon="120" lat="30"><tag k="highway" v="traffic_signals" /><tag k="traffic_signals:direction" v="both" /></node>
<node id="directionless" lon="120" lat="30"><tag k="highway" v="traffic_signals" /></node>
<node id="deleted" lon="120" lat="30" action="delete"><tag k="highway" v="traffic_signals" /></node>
<node id="crossing" lon="120" lat="30"><tag k="highway" v="crossing" /><tag k="crossing" v="traffic_signals" /></node>
</osm>`).trafficSignalControls;
assert.deepEqual(parsedSignalControls.map((entry) => entry.id), ["active", "directionless"], "only active highway=traffic_signals nodes control vehicle signals");
fs.rmSync(tempDir, { recursive: true, force: true });
console.log("Preview asset tests passed.");