feat: complete native traffic signal workflow

This commit is contained in:
2026-08-18 11:46:19 +08:00
parent 7204c28161
commit 5accc0a4b1
17 changed files with 355 additions and 19 deletions

View File

@@ -47,6 +47,9 @@ const originalStop = [first.properties.stop_lon, first.properties.stop_lat];
first.geometry.coordinates[0] += 0.0001;
first.properties.display_id = "A-01";
first.properties.heading_deg = 42;
first.properties.mast_heading_deg = 17;
first.properties.mast_reach_m = 8.5;
first.properties.face_heading_deg = 203;
first.properties.z_offset_m = 1.25;
const runtime = buildTrafficSignalsFromFeatures(edited);
assert.equal(new Set(runtime.signals.map((signal) => signal.nodeKey)).size, runtime.signals.length);
@@ -61,9 +64,22 @@ const changed = runtime.signals.find((signal) => signal.id === first.properties.
assert.equal(changed.displayId, "A-01");
assert.equal(changed.longitude, first.geometry.coordinates[0]);
assert.equal(changed.headingDegrees, 42);
assert.equal(changed.mastHeadingDegrees, 17);
assert.equal(changed.faceHeadingDegrees, 203);
assert.equal(changed.mastReachMeters, 8.5);
assert.deepEqual([changed.stopLongitude, changed.stopLatitude], originalStop, "moving a pole preserves the stop point");
assert.equal(changed.pose.pole.height, 1.25);
assert.equal(changed.pose.arm.from.height, 7.5);
assert.ok(changed.pose.arm.to.latitude > changed.pose.arm.from.latitude, "mast direction controls the arm independently");
assert.equal(changed.pose.head.faceHeadingDegrees, 203, "face direction is independent from mast direction");
const legacy = structuredClone(cross);
legacy.features[0].properties.heading_deg = 42;
delete legacy.features[0].properties.mast_heading_deg;
delete legacy.features[0].properties.face_heading_deg;
const migrated = validateTrafficSignalFeatures(legacy).features[0].properties;
assert.equal(migrated.mast_heading_deg, 312, "legacy heading preserves the historic mast direction");
assert.equal(migrated.face_heading_deg, 222, "legacy heading preserves the historic face direction");
edited.features[1].properties.enabled = "0";
assert.equal(buildTrafficSignalsFromFeatures(edited).signals.length, 3, "disabled assemblies are omitted");
@@ -100,7 +116,7 @@ for (const disabledValue of [false, 0, "0", "false", "no"]) {
disabled.features[0].properties.enabled = disabledValue;
assert.equal(buildTrafficSignalsFromFeatures(disabled).signals.length, 3);
}
for (const key of ["heading_deg", "phase_group", "stop_lon", "stop_lat", "z_offset_m"]) {
for (const key of ["phase_group", "stop_lon", "stop_lat", "z_offset_m"]) {
const missingNumber = structuredClone(cross);
missingNumber.features[0].properties[key] = null;
assert.throws(
@@ -109,5 +125,8 @@ for (const key of ["heading_deg", "phase_group", "stop_lon", "stop_lat", "z_offs
`${key} must not silently coerce null to zero`,
);
}
const missingDirections = structuredClone(cross);
for (const key of ["heading_deg", "mast_heading_deg", "face_heading_deg"]) missingDirections.features[0].properties[key] = null;
assert.throws(() => validateTrafficSignalFeatures(missingDirections), /missing mast_heading_deg/);
console.log("Traffic signal tests passed.");