From a4d6181fb99764d2f5493c8238224bac4f7b125e Mon Sep 17 00:00:00 2001 From: Jeff Blakely <7987380+jeffblakely@users.noreply.github.com> Date: Mon, 12 Oct 2026 12:26:05 -0400 Subject: [PATCH] feat(bikeshare): add Detroit, Pittsburgh, Aspen and Kona & Hilo Register four live US systems on Lyft's PBSC platform: MoGo (Detroit, 81 stations), POGOH (Pittsburgh, 60), WE-cycle (Aspen, 101) and HIBike (Kona and Hilo, 23). The proxy already allows *.publicbikesystem.net, so no server change is needed. HIBike's centre sits between Kailua-Kona and Hilo so a camera over either town loads the whole system. The test checks each system is registered, passes the proxy's host and endpoint checks, and has an activation radius covering its farthest station as measured against the live feed. --- CHANGELOG.md | 4 ++ src/layers/bikeshare/registry.js | 50 ++++++++++++++++++++++ src/layers/bikeshare/usPbscCities.test.mjs | 31 ++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 src/layers/bikeshare/usPbscCities.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index b24ac95ed..5fd137e89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +- Add Detroit (MoGo), Pittsburgh (POGOH), Aspen (WE-cycle) and Kona & Hilo + (HIBike) to the GBFS bikeshare registry, the layer's first stations in + Michigan and Pennsylvania. + - Treat explicitly current, complete empty vessel coverage as a successful update, clearing obsolete contacts while retaining stale or incomplete snapshots. diff --git a/src/layers/bikeshare/registry.js b/src/layers/bikeshare/registry.js index ff752f664..38d417b04 100644 --- a/src/layers/bikeshare/registry.js +++ b/src/layers/bikeshare/registry.js @@ -48,6 +48,56 @@ const RAW_GBFS_CITY_REGISTRY = [ 'https://gbfs.lyft.com/gbfs/2.3/chi/en/station_status.json', provider: 'Divvy', }, + { + id: 'detroit-mogo', + city: 'Detroit, MI', + centerLat: 42.3314, + centerLon: -83.0458, + loadRadiusKm: 90, + stationInformationUrl: + 'https://detroit.publicbikesystem.net/customer/gbfs/v2/en/station_information.json', + stationStatusUrl: + 'https://detroit.publicbikesystem.net/customer/gbfs/v2/en/station_status.json', + provider: 'MoGo', + }, + { + id: 'pittsburgh-pogoh', + city: 'Pittsburgh, PA', + centerLat: 40.4406, + centerLon: -79.9959, + loadRadiusKm: 90, + stationInformationUrl: + 'https://pittsburgh.publicbikesystem.net/customer/gbfs/v2/en/station_information.json', + stationStatusUrl: + 'https://pittsburgh.publicbikesystem.net/customer/gbfs/v2/en/station_status.json', + provider: 'POGOH', + }, + { + id: 'aspen-we-cycle', + city: 'Aspen, CO', + centerLat: 39.1911, + centerLon: -106.8175, + loadRadiusKm: 90, + stationInformationUrl: + 'https://aspen.publicbikesystem.net/customer/gbfs/v2/en/station_information.json', + stationStatusUrl: + 'https://aspen.publicbikesystem.net/customer/gbfs/v2/en/station_status.json', + provider: 'WE-cycle', + }, + // HIBike serves both Kailua-Kona and Hilo, about 95 km apart; the centre sits + // between them so a camera over either town loads the whole system. + { + id: 'hawaii-island-hibike', + city: 'Kona & Hilo, HI', + centerLat: 19.68, + centerLon: -155.54, + loadRadiusKm: 90, + stationInformationUrl: + 'https://kona.publicbikesystem.net/customer/gbfs/v2/en/station_information.json', + stationStatusUrl: + 'https://kona.publicbikesystem.net/customer/gbfs/v2/en/station_status.json', + provider: 'HIBike', + }, { id: 'dc-capital-bikeshare', city: 'Washington, DC', diff --git a/src/layers/bikeshare/usPbscCities.test.mjs b/src/layers/bikeshare/usPbscCities.test.mjs new file mode 100644 index 000000000..4ff15c370 --- /dev/null +++ b/src/layers/bikeshare/usPbscCities.test.mjs @@ -0,0 +1,31 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { CITY_BY_ID } from './registry.js'; +import { isAllowedGbfsHost, isAllowedGbfsPath } from '../../data/gbfsSource.js'; +import { CITY_RANGE_BASE_KM } from './policy.js'; + +// Each city's PBSC host, and its farthest station from the registered centre +// as measured against the live feed (km), so a camera over any part of the +// system activates it. +const CITIES = { + 'detroit-mogo': { host: 'detroit.publicbikesystem.net', provider: 'MoGo', farthestKm: 22.4 }, + 'pittsburgh-pogoh': { host: 'pittsburgh.publicbikesystem.net', provider: 'POGOH', farthestKm: 9.1 }, + 'aspen-we-cycle': { host: 'aspen.publicbikesystem.net', provider: 'WE-cycle', farthestKm: 42.6 }, + 'hawaii-island-hibike': { host: 'kona.publicbikesystem.net', provider: 'HIBike', farthestKm: 53.8 }, +}; + +test('the added US PBSC systems are registered and readable through the proxy', () => { + for (const [id, expected] of Object.entries(CITIES)) { + const city = CITY_BY_ID.get(id); + assert.ok(city, `${id} is registered`); + assert.equal(city.provider, expected.provider); + assert.deepEqual(city.hosts, [expected.host]); + for (const feed of [city.stationInformationUrl, city.stationStatusUrl]) { + const url = new URL(feed); + assert.ok(isAllowedGbfsHost(url.hostname), `${id} host ${url.hostname}`); + assert.ok(isAllowedGbfsPath(url.pathname), `${id} path ${url.pathname}`); + } + const radius = Math.max(CITY_RANGE_BASE_KM, city.loadRadiusKm); + assert.ok(radius > expected.farthestKm, `${id} radius ${radius} covers its farthest station`); + } +});