mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-12 14:22:21 +00:00
fix(transit): read Rio speeds as the m/s SMTR publishes
Both SMTR feeds now publish Position.speed in m/s. Compared with how far vehicles moved between reports on 2026-11-05, the published values ran 3.1x (buses) and 3.6x (BRT) below the observed speed, so the km/h conversion divided real speeds by 3.6. Drop speedUnit and the conversion.
This commit is contained in:
1 parent
e5dd7c71ac
commit
a67c7969fd
7 files changed
+14
-58
No files matched your search
@@ -398,12 +398,7 @@ function rioRouteLabel(routeId) {
|
||||
* url: string, headers?: Record<string, string>,
|
||||
* license: string, licenseUrl: string, attribution: string,
|
||||
* defaultMode: string, routeMode?: (routeId: string|null) => string,
|
||||
* speedUnit?: 'kmh',
|
||||
* }>>}
|
||||
*
|
||||
* `speedUnit: 'kmh'` marks a feed that publishes `Position.speed` in km/h
|
||||
* instead of the m/s the spec requires; the proxy converts it before anything
|
||||
* downstream reads `speedMps`.
|
||||
*/
|
||||
export const TRANSIT_FEED_REGISTRY = Object.freeze([
|
||||
Object.freeze({
|
||||
@@ -765,10 +760,9 @@ export const TRANSIT_FEED_REGISTRY = Object.freeze([
|
||||
terms: Object.freeze({
|
||||
quote:
|
||||
'This work is licensed under a Creative Commons Attribution 4.0 International License',
|
||||
note: 'The data.rio catalog page labels the feed [beta] and states a gateway limit of 5 requests a second and 60 a minute behind a 10-second cache, which the 15-second proxy TTL stays inside. It is the named replacement for the JSON GPS endpoints SMTR deprecated on 2026-10-07 and says it will switch off by 2026-11-30. SMTR relays the concessionaires’ ITS data and repairs trips with its own matching; the page places accuracy with the suppliers. Speeds arrive in km/h, not m/s (a 2026-10-29 snapshot peaked at 89), hence `speedUnit`. Route ids are internal keys (`O0731AAN0A` is line SN731) and are joined to the line names in SMTR’s published GTFS `routes.txt`, under the same CC BY 4.0 terms.',
|
||||
note: 'The data.rio catalog page labels the feed [beta] and states a gateway limit of 5 requests a second and 60 a minute behind a 10-second cache, which the 15-second proxy TTL stays inside. It is the named replacement for the JSON GPS endpoints SMTR deprecated on 2026-10-07 and says it will switch off by 2026-11-30. SMTR relays the concessionaires’ ITS data and repairs trips with its own matching; the page places accuracy with the suppliers. Speeds are m/s, as the spec requires: on 2026-11-05 they matched how far vehicles moved between reports, though a 2026-10-29 snapshot read as km/h. Route ids are internal keys (`O0731AAN0A` is line SN731) and are joined to the line names in SMTR’s published GTFS `routes.txt`, under the same CC BY 4.0 terms.',
|
||||
}),
|
||||
defaultMode: 'bus',
|
||||
speedUnit: 'kmh',
|
||||
routeLabel: rioRouteLabel,
|
||||
}),
|
||||
Object.freeze({
|
||||
@@ -788,10 +782,9 @@ export const TRANSIT_FEED_REGISTRY = Object.freeze([
|
||||
terms: Object.freeze({
|
||||
quote:
|
||||
'This work is licensed under a Creative Commons Attribution 4.0 International License',
|
||||
note: 'Same catalog terms, [beta] label and gateway limits as the bus feed. The fleet is operated by Mobi-Rio and tracked by SONDA, whose data SMTR relays. Speeds are km/h here too (peak 62 in the same snapshot). Route ids are opaque (`20000501110` is line 50) and are joined to the same `routes.txt` as the buses.',
|
||||
note: 'Same catalog terms, [beta] label and gateway limits as the bus feed. The fleet is operated by Mobi-Rio and tracked by SONDA, whose data SMTR relays. Speeds are m/s here too. Route ids are opaque (`20000501110` is line 50) and are joined to the same `routes.txt` as the buses.',
|
||||
}),
|
||||
defaultMode: 'bus',
|
||||
speedUnit: 'kmh',
|
||||
routeLabel: rioRouteLabel,
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -535,14 +535,13 @@ test('the Rio route table is a clean join of published keys to names', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('Rio feeds are CC BY 4.0, live, and declare the km/h speeds they publish', () => {
|
||||
test('Rio feeds are CC BY 4.0 and live', () => {
|
||||
for (const id of ['smtr-rio-onibus', 'smtr-rio-brt']) {
|
||||
const feed = getTransitFeed(id);
|
||||
assert.equal(feed.defaultEnabled, true, `${id} is live`);
|
||||
assert.equal(feed.license, 'CC BY 4.0');
|
||||
assert.match(feed.licenseUrl, /^https:\/\/www\.data\.rio\/documents\//);
|
||||
assert.match(feed.attribution, /Prefeitura da Cidade do Rio de Janeiro/);
|
||||
assert.equal(feed.speedUnit, 'kmh', `${id} publishes km/h`);
|
||||
assert.equal(transitModeFor(feed, null), 'bus');
|
||||
}
|
||||
const sources = readFileSync(
|
||||
|
||||
@@ -224,25 +224,6 @@ export function repairVehicleTimestamps(vehicles, headerTimestamp, fetchedAtS) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bring a feed's speeds to the m/s the spec requires. A feed registered with
|
||||
* `speedUnit: 'kmh'` publishes km/h in `Position.speed`, and read as m/s a
|
||||
* 44 km/h bus would be reported at 158 km/h. Converting here, once, keeps every
|
||||
* consumer of `speedMps` honest without each one knowing about the feed.
|
||||
*
|
||||
* @param {object} feed Registry entry.
|
||||
* @param {object[]} vehicles Normalized vehicle records.
|
||||
* @returns {object[]}
|
||||
*/
|
||||
export function normalizeFeedSpeeds(feed, vehicles) {
|
||||
if (feed?.speedUnit !== 'kmh') return vehicles;
|
||||
return vehicles.map((vehicle) =>
|
||||
Number.isFinite(vehicle.speedMps)
|
||||
? { ...vehicle, speedMps: vehicle.speedMps / 3.6 }
|
||||
: vehicle,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode upstream bytes into the JSON snapshot the browser consumes.
|
||||
*
|
||||
@@ -276,7 +257,7 @@ export function buildTransitSnapshot(feed, bytes, now = Date.now()) {
|
||||
truncated: decoded.truncated,
|
||||
count: decoded.vehicles.length,
|
||||
vehicles: repairVehicleTimestamps(
|
||||
normalizeFeedSpeeds(feed, decoded.vehicles),
|
||||
decoded.vehicles,
|
||||
decoded.timestamp,
|
||||
Math.floor(now / 1000),
|
||||
),
|
||||
|
||||
@@ -285,29 +285,14 @@ test('the snapshot builder fills timestamps from the header it decoded', () => {
|
||||
assert.equal(snapshot.feedTimestamp, 1_700_000_000);
|
||||
});
|
||||
|
||||
test('a feed that publishes km/h has its speeds converted to m/s, and only that feed', () => {
|
||||
// Rio's SMTR feeds put km/h in Position.speed; read as m/s, a 36 km/h bus
|
||||
// would be reported at 130 km/h.
|
||||
test('Rio speeds pass through as the m/s they are published in', () => {
|
||||
// SMTR publishes m/s, as the spec requires: checked against how far
|
||||
// vehicles moved between reports on 2026-11-05.
|
||||
const bytes = encodeFeed({
|
||||
entities: [
|
||||
{ id: 'moving', lat: -22.9, lon: -43.2, speed: 36 },
|
||||
{ id: 'still', lat: -22.9, lon: -43.2, speed: 0 },
|
||||
{ id: 'silent', lat: -22.9, lon: -43.2 },
|
||||
],
|
||||
entities: [{ id: 'moving', lat: -22.9, lon: -43.2, speed: 10 }],
|
||||
});
|
||||
const rio = buildTransitSnapshot(getTransitFeed('smtr-rio-onibus'), bytes, 1);
|
||||
const speeds = Object.fromEntries(
|
||||
rio.vehicles.map((vehicle) => [vehicle.id, vehicle.speedMps]),
|
||||
);
|
||||
assert.ok(Math.abs(speeds.moving - 10) < 1e-6, '36 km/h is 10 m/s');
|
||||
assert.equal(speeds.still, 0);
|
||||
assert.equal(speeds.silent, null, 'an absent speed stays absent');
|
||||
const mbta = buildTransitSnapshot(getTransitFeed('mbta'), bytes, 1);
|
||||
assert.equal(
|
||||
mbta.vehicles.find((vehicle) => vehicle.id === 'moving').speedMps,
|
||||
36,
|
||||
'a spec-conforming feed is left alone',
|
||||
);
|
||||
assert.equal(rio.vehicles[0].speedMps, 10);
|
||||
});
|
||||
|
||||
test('conditional-request validators and gzip ride along with feed identification', () => {
|
||||
|
||||
Reference in new issue
Block a user