feat(transit): add Guelph and Kingston, register Barrie off

Guelph's feed publishes the internal database key of a route (`2991`)
where the rider sees the number on the bus (`1`), which would have put
"Route 2991" on the label a person reads. So a feed may now carry its own
`routeLabel`, the same way it may carry its own `modeWords`. Guelph's table
is its 33 routes, taken from the City's own published GTFS; an id missing
from it renders unchanged, because a raw id you can compare against the bus
beats a blank. Every other feed is identity and carries no table.

Kingston is catalogued by the City against this exact URL. Its licence PDF
link is pinned to the canonical location rather than the legacy path that
currently redirects there — Durham's entry records what happens when such a
redirect is eventually dropped.

Barrie ships REGISTERED AND OFF. The City offers its licence for acceptance
behind a click-through gate, and nobody on this project has accepted it.
Whether to accept is a person's decision, not an assumption a poller should
make, so the feed is visible, documented, and one line from enabled.
This commit is contained in:
Cliff Flood committed 2026-10-15 20:18:15 -04:00
1 parent 849cb80375
commit 11dd914c2f
7 files changed
+223 -9

No files matched your search

+9 -4
View File
@@ -4,7 +4,10 @@
* scene state, no network: everything here is exercised by node:test.
*/
import { TRANSIT_MODE_ICON } from '../../data/transitFeeds.js';
import {
TRANSIT_MODE_ICON,
transitRouteLabel,
} from '../../data/transitFeeds.js';
import { displayMotion, playbackPosition } from './movement.js';
export const TRANSIT_SELECTED_OVERLAY_SOURCE_ID = 'transit-selected';
@@ -337,7 +340,7 @@ export function buildTransitSelectionCopy(
) {
const icon = TRANSIT_MODE_ICON[mode] || TRANSIT_MODE_ICON.unknown;
const routeLabel = record.routeId
? `Route ${record.routeId}`
? `Route ${transitRouteLabel(feed, record.routeId)}`
: record.label
? `Vehicle ${record.label}`
: `Vehicle ${record.id}`;
@@ -515,12 +518,14 @@ export function transitModeAbbr(mode, feed = null) {
* names one, else the fleet number, else the raw id. What a person standing at
* the stop would call it.
* @param {object} record Normalized vehicle record.
* @param {object|null} [feed] Registry entry, for feeds whose route ids are
* internal keys rather than the number on the vehicle.
* @returns {string}
*/
export function transitDetectionId(record) {
export function transitDetectionId(record, feed = null) {
const route =
typeof record?.routeId === 'string' ? record.routeId.trim() : '';
if (route) return elideRouteText(route);
if (route) return elideRouteText(transitRouteLabel(feed, route));
const label = typeof record?.label === 'string' ? record.label.trim() : '';
if (label) return elideRouteText(label);
return elideRouteText(String(record?.id || 'TRANSIT'));
+4 -1
View File
@@ -143,7 +143,10 @@ export function createQueries({ state, parts }) {
// Route and operator text change with the record and the mode, both of
// which arrive on polls — the same events that rebuild this list.
if (!entry.labelId || entry.labelRecord !== entry.record) {
entry.labelId = transitDetectionId(entry.record);
entry.labelId = transitDetectionId(
entry.record,
feedForEntry(state, entry),
);
entry.labelRecord = entry.record;
}
if (!entry.labelClass || entry.labelMode !== entry.mode) {
+24
View File
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
import {
buildTransitSelectionCopy,
transitDetectionClass,
transitDetectionId,
transitDetectionMetric,
transitModeAbbr,
transitModeWord,
@@ -50,3 +51,26 @@ test('detection labels carry the operator vocabulary within the field width', ()
const metric = transitDetectionMetric({ mode: 'tram' }, 'tram', 0, ttc());
assert.match(metric, /^STREETCAR/);
});
test('a Guelph vehicle is labelled with the number on the bus, not the database key', () => {
const guelph = getTransitFeed('guelph-transit');
const record = { id: '196', routeId: '2991', timestamp: 0 };
const copy = buildTransitSelectionCopy(guelph, record, 'bus', 0, null, null);
assert.match(copy.title, /Route 1\b/);
assert.doesNotMatch(copy.title, /2991/);
assert.equal(transitDetectionId(record, guelph), '1');
});
test('a feed that already speaks rider route numbers is untouched', () => {
const record = { id: '4400', routeId: '501', timestamp: 0 };
assert.equal(transitDetectionId(record, ttc()), '501');
const copy = buildTransitSelectionCopy(ttc(), record, 'tram', 0, null, null);
assert.match(copy.title, /Route 501/);
// No feed at all still renders the raw route rather than throwing.
assert.equal(transitDetectionId(record), '501');
});
test('a vehicle with no route still falls back to its fleet number', () => {
const guelph = getTransitFeed('guelph-transit');
assert.equal(transitDetectionId({ id: '196', label: '196' }, guelph), '196');
});