fix(cctv): read TranStar dir as the camera's side of the road, at low confidence

TranStar's `dir` field is the side of the freeway a camera sits on, not
the way it faces. TranStar's own map script (translateCameraDirection)
shows it as "Traffic closest to the camera is traveling": South is
eastbound, North westbound, West southbound and anything else
northbound. The live catalog agrees: I-10 cameras are North/South and
I-45 cameras East/West.

Treating `dir` as a high-confidence facing pointed most Houston cameras
across or away from the road they watch, and dropped the "estimated"
marking. Map it to the travel direction instead (S 90, N 270, W 180,
E 0) as a low-confidence prior with the low-confidence pose, and keep
the id-hash fallback for None or unknown values.
This commit is contained in:
Sameh Khamis committed 2026-11-03 15:02:30 -08:00
1 parent 09bdc314d0
commit 8cd0cc82f5
5 files changed
+66 -23

No files matched your search

+23 -10
View File
@@ -20,7 +20,6 @@ import {
isLikelyHoustonCoordinate,
} from '../../server/providers/cctv/normalize.js';
import { createCctvCatalog } from '../../server/providers/cctv/catalog.js';
import { directionToHeading } from './directionText.js';
/**
* A response whose body is a live stream, plus a flag that flips when the
@@ -75,8 +74,9 @@ test('a Houston row maps to a source on the pinned HTTPS frame host', () => {
assert.equal(source.url, HOUSTON_IMAGE_URL(DOWNTOWN_ID));
assert.equal(source.url, source.snapshotUrl);
assert.ok(source.url.startsWith(HOUSTON_IMAGE_ORIGIN));
assert.equal(source.headingConfidence, 'high');
assert.equal(source.headingDeg, 180);
// dir South = the camera sits south of I-10; the nearest lanes run east.
assert.equal(source.headingConfidence, 'low');
assert.equal(source.headingDeg, 90);
});
test('frame URLs are synthesized from the numeric id; the payload URL is ignored', () => {
@@ -105,13 +105,19 @@ test('zero ids, off-city geometry and unusable ids are dropped', () => {
assert.equal(houstonCameraToSource(row({ id: '13711' })).id, 'houston-13711');
});
test('bare cardinal dirs are high-confidence; None uses the id-hash fallback', () => {
assert.equal(directionToHeading('South', true), 180);
assert.equal(directionToHeading('None', true), NaN);
const south = houstonCameraToSource(row({ dir: 'South' }));
assert.equal(south.headingConfidence, 'high');
assert.equal(south.headingDeg, 180);
test('dir is the side of the road: it gives a low-confidence travel heading', () => {
// TranStar's map reads dir as "Traffic closest to the camera is traveling":
// South -> Eastbound, North -> Westbound, West -> Southbound, East -> Northbound.
const expected = { South: 90, North: 270, West: 180, East: 0, south: 90 };
for (const [dir, heading] of Object.entries(expected)) {
const source = houstonCameraToSource(row({ dir }));
assert.equal(source.headingDeg, heading, `dir ${dir}`);
assert.equal(source.headingConfidence, 'low', `dir ${dir}`);
assert.equal(source.pitchDeg, -18, `dir ${dir}`);
assert.equal(source.fovDeg, 44, `dir ${dir}`);
assert.equal(source.rangeM, 145, `dir ${dir}`);
assert.equal(source.mountHeightM, 8, `dir ${dir}`);
}
const none = houstonCameraToSource(row({ dir: 'None' }));
assert.equal(none.headingConfidence, 'low');
@@ -120,6 +126,13 @@ test('bare cardinal dirs are high-confidence; None uses the id-hash fallback', (
none.headingDeg,
houstonCameraToSource(row({ dir: '' })).headingDeg,
);
for (const dir of ['Northeast', 'constructor', '__proto__', undefined]) {
assert.equal(
houstonCameraToSource(row({ dir })).headingDeg,
none.headingDeg,
`dir ${dir} falls back to the id hash`,
);
}
});
test('a nameless row still gets a label', () => {