diff --git a/DATA_SOURCES.md b/DATA_SOURCES.md index 2ed14c264..eb20f793e 100644 --- a/DATA_SOURCES.md +++ b/DATA_SOURCES.md @@ -98,7 +98,7 @@ How to read this: | **Barrie Transit** (myridebarrie.ca) | Transit layer live vehicles, Barrie ON | Barrie Transit realtime data, published by the City of Barrie under a licence accepted by use of the data. The licence is revocable and requires no attribution. | Courtesy credit to the City of Barrie (the licence requires none, and its trademark clause is broad) | | **YRT/Viva** (rtu.york.ca) | Transit layer live vehicles, York Region ON | York Region Transit realtime data, first-party York Region infrastructure under the YRT Open Data Licence v2.0, which permits commercial use and makes attribution optional. Viva route ids are named by colour from York Region’s published GTFS. | "Contains public transit Information made available under YRT’s Open Data Licence" — the licence’s own wording, given though not required | | **Burlington Transit** (opendata.burlington.ca) | Transit layer live vehicles, Burlington ON | Burlington Transit realtime data under the City of Burlington Open Data Terms of Use, which permit any lawful purpose. The terms are revocable and must travel with the data, so the credit links them. Route ids are internal keys and are joined to the City’s published GTFS. | Credit to the City of Burlington plus a link to its Open Data Terms of Use, which the licence requires | -| **SMTR Rio de Janeiro** (dados.mobilidade.rio) | Transit layer live vehicles, Rio de Janeiro: municipal buses (SPPO) and the Mobi-Rio BRT, as two feeds | CC BY 4.0, stated on both data.rio catalog pages. Keyless GTFS-Realtime published by the Secretaria Municipal de Transportes, labelled beta, with a gateway limit of 5 requests a second and 60 a minute. Speeds arrive in km/h rather than m/s and are converted by the proxy. Route ids are internal keys and are joined to the line names in SMTR’s published GTFS `routes.txt` (dados.mobilidade.rio/gtfs/schedule), under the same CC BY 4.0 terms. | Attribution to SMTR / Prefeitura da Cidade do Rio de Janeiro under CC BY 4.0 | +| **SMTR Rio de Janeiro** (dados.mobilidade.rio) | Transit layer live vehicles, Rio de Janeiro: municipal buses (SPPO) and the Mobi-Rio BRT, as two feeds | CC BY 4.0, stated on both data.rio catalog pages. Keyless GTFS-Realtime published by the Secretaria Municipal de Transportes, labelled beta, with a gateway limit of 5 requests a second and 60 a minute. Route ids are internal keys and are joined to the line names in SMTR’s published GTFS `routes.txt` (dados.mobilidade.rio/gtfs/schedule), under the same CC BY 4.0 terms. | Attribution to SMTR / Prefeitura da Cidade do Rio de Janeiro under CC BY 4.0 | | **Radio Browser** | Geolocated internet-radio station directory and station-level tags | Public-domain directory data under PDDL 1.0; individual broadcaster stream terms apply | "Radio Browser" plus a link to the selected broadcaster | | **Re:Earth Terrain** (Mapterhorn) | Terrain (keyless globe stacks — OSM etc. — + `/api/terrain/heights` ellipsoidal-height lookups) | Terrain mesh: CC BY 4.0; geoid: EGM2008 (NGA, public domain) | "Terrain (keyless globe stacks): Re:Earth Terrain / Mapterhorn (CC BY 4.0) / EGM2008 (NGA)" | | **OpenSeaMap** (`tiles.openseamap.org`) | Sea Marks layer: raster overlay of buoys, lighthouses and ports (#10) | Chart tiles CC BY-SA 2.0; data ODbL 1.0 via OpenStreetMap | "Sea marks © OpenSeaMap contributors (CC BY-SA 2.0) · Data © OpenStreetMap contributors (ODbL)" | diff --git a/changelog.d/1481-rio-transit.md b/changelog.d/1481-rio-transit.md index b45b1b1fd..ac245a9c8 100644 --- a/changelog.d/1481-rio-transit.md +++ b/changelog.d/1481-rio-transit.md @@ -1,6 +1,5 @@ - Add Rio de Janeiro to the Transit layer: municipal buses and the Mobi-Rio BRT from SMTR's keyless GTFS-Realtime feeds (CC BY 4.0), the layer's first Latin - American region. A feed that publishes speed in km/h declares - `speedUnit: 'kmh'` and the proxy converts it to m/s. Rio's internal route - keys are joined to SMTR's published GTFS `routes.txt`, so a card reads - "Route SN731" rather than "Route O0731AAN0A" (IMBrandao, #1481). + American region. Rio's internal route keys are joined to SMTR's published + GTFS `routes.txt`, so a card reads "Route SN731" rather than + "Route O0731AAN0A" (IMBrandao, #1481). diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 81e71655c..170605951 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -4649,8 +4649,7 @@ Helsinki, the Netherlands, Norway, South East Queensland, Rio de Janeiro (municipal buses and the Mobi-Rio BRT, two feeds), and nine in Ontario — Toronto (TTC surface vehicles; the subway publishes no positions), MiWay, Hamilton HSR, Durham Region, YRT/Viva, Guelph, -Burlington, Barrie and Kingston. Rio's feeds publish speed in km/h, which -the proxy converts to m/s before any card reads it. Mode silhouettes +Burlington, Barrie and Kingston. Mode silhouettes identify buses, trams, subways, trains, ferries and unclassified vehicles. Retained transit history uses the same outlier quarantine as live observations and diff --git a/src/data/transitFeeds.js b/src/data/transitFeeds.js index d7993a954..aa953c00b 100644 --- a/src/data/transitFeeds.js +++ b/src/data/transitFeeds.js @@ -398,12 +398,7 @@ function rioRouteLabel(routeId) { * url: string, headers?: Record, * 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, }), ]); diff --git a/src/data/transitFeeds.test.mjs b/src/data/transitFeeds.test.mjs index 8333207d5..dbae35c24 100644 --- a/src/data/transitFeeds.test.mjs +++ b/src/data/transitFeeds.test.mjs @@ -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( diff --git a/src/data/transitProxy.js b/src/data/transitProxy.js index 2923c9c32..458240e9d 100644 --- a/src/data/transitProxy.js +++ b/src/data/transitProxy.js @@ -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), ), diff --git a/src/data/transitProxy.test.mjs b/src/data/transitProxy.test.mjs index 445e658f2..07911a939 100644 --- a/src/data/transitProxy.test.mjs +++ b/src/data/transitProxy.test.mjs @@ -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', () => {