Files
gods-eye-view/src/layers/bikeshare/registry.js
T

395 lines
11 KiB
JavaScript

function buildBcycleUrls(systemId) {
return {
stationInformationUrl: `https://gbfs.bcycle.com/${systemId}/station_information.json`,
stationStatusUrl: `https://gbfs.bcycle.com/${systemId}/station_status.json`,
};
}
function bcycleEntry({
id,
city,
centerLat,
centerLon,
systemId,
loadRadiusKm = 100,
provider = 'BCycle',
}) {
return {
id,
city,
centerLat,
centerLon,
loadRadiusKm,
provider,
...buildBcycleUrls(systemId),
};
}
const RAW_GBFS_CITY_REGISTRY = [
{
id: 'nyc-citibike',
city: 'New York, NY',
centerLat: 40.7484,
centerLon: -73.9967,
loadRadiusKm: 140,
stationInformationUrl:
'https://gbfs.lyft.com/gbfs/2.3/bkn/en/station_information.json',
stationStatusUrl:
'https://gbfs.lyft.com/gbfs/2.3/bkn/en/station_status.json',
provider: 'Citi Bike',
},
{
id: 'chicago-divvy',
city: 'Chicago, IL',
centerLat: 41.8781,
centerLon: -87.6298,
loadRadiusKm: 120,
stationInformationUrl:
'https://gbfs.lyft.com/gbfs/2.3/chi/en/station_information.json',
stationStatusUrl:
'https://gbfs.lyft.com/gbfs/2.3/chi/en/station_status.json',
provider: 'Divvy',
},
{
id: 'dc-capital-bikeshare',
city: 'Washington, DC',
centerLat: 38.9072,
centerLon: -77.0369,
loadRadiusKm: 120,
stationInformationUrl:
'https://gbfs.lyft.com/gbfs/2.3/dca-cabi/en/station_information.json',
stationStatusUrl:
'https://gbfs.lyft.com/gbfs/2.3/dca-cabi/en/station_status.json',
provider: 'Capital Bikeshare',
},
{
id: 'sf-bay-wheels',
city: 'San Francisco, CA',
centerLat: 37.7749,
centerLon: -122.4194,
loadRadiusKm: 110,
stationInformationUrl:
'https://gbfs.lyft.com/gbfs/2.3/bay/en/station_information.json',
stationStatusUrl:
'https://gbfs.lyft.com/gbfs/2.3/bay/en/station_status.json',
provider: 'Bay Wheels',
},
{
id: 'boston-bluebikes',
city: 'Boston, MA',
centerLat: 42.3601,
centerLon: -71.0589,
loadRadiusKm: 100,
stationInformationUrl:
'https://gbfs.bluebikes.com/gbfs/en/station_information.json',
stationStatusUrl: 'https://gbfs.bluebikes.com/gbfs/en/station_status.json',
provider: 'Blue Bikes',
},
{
id: 'philadelphia-indego',
city: 'Philadelphia, PA',
centerLat: 39.9526,
centerLon: -75.1652,
loadRadiusKm: 100,
stationInformationUrl:
'https://gbfs.bcycle.com/bcycle_indego/station_information.json',
stationStatusUrl:
'https://gbfs.bcycle.com/bcycle_indego/station_status.json',
provider: 'Indego',
},
{
id: 'portland-biketown',
city: 'Portland, OR',
centerLat: 45.5152,
centerLon: -122.6784,
loadRadiusKm: 95,
stationInformationUrl:
'https://gbfs.biketownpdx.com/gbfs/2.3/en/station_information.json',
stationStatusUrl:
'https://gbfs.biketownpdx.com/gbfs/2.3/en/station_status.json',
provider: 'BIKETOWN',
},
{
id: 'la-metro-bike',
city: 'Los Angeles, CA',
centerLat: 34.0522,
centerLon: -118.2437,
loadRadiusKm: 120,
stationInformationUrl:
'https://gbfs.bcycle.com/bcycle_lametro/station_information.json',
stationStatusUrl:
'https://gbfs.bcycle.com/bcycle_lametro/station_status.json',
provider: 'Metro Bike',
},
{
id: 'austin-capmetro',
city: 'Austin, TX',
centerLat: 30.2672,
centerLon: -97.7431,
loadRadiusKm: 90,
stationInformationUrl:
'https://austin.publicbikesystem.net/customer/gbfs/v2/en/station_information.json',
stationStatusUrl:
'https://austin.publicbikesystem.net/customer/gbfs/v2/en/station_status.json',
provider: 'CapMetro',
},
{
id: 'honolulu-biki',
city: 'Honolulu, HI',
centerLat: 21.3069,
centerLon: -157.8583,
loadRadiusKm: 90,
stationInformationUrl:
'https://hon.publicbikesystem.net/customer/gbfs/v2/en/station_information.json',
stationStatusUrl:
'https://hon.publicbikesystem.net/customer/gbfs/v2/en/station_status.json',
provider: 'Biki',
},
{
id: 'columbus-cogo',
city: 'Columbus, OH',
centerLat: 39.9612,
centerLon: -82.9988,
loadRadiusKm: 90,
stationInformationUrl:
'https://gbfs.cogobikeshare.com/gbfs/2.3/en/station_information.json',
stationStatusUrl:
'https://gbfs.cogobikeshare.com/gbfs/2.3/en/station_status.json',
provider: 'CoGo',
},
{
id: 'chattanooga-bikechatt',
city: 'Chattanooga, TN',
centerLat: 35.0456,
centerLon: -85.3097,
loadRadiusKm: 90,
stationInformationUrl:
'https://chat.publicbikesystem.net/customer/gbfs/v2/en/station_information.json',
stationStatusUrl:
'https://chat.publicbikesystem.net/customer/gbfs/v2/en/station_status.json',
provider: 'Bike Chattanooga',
},
bcycleEntry({
id: 'boulder-bcycle',
city: 'Boulder, CO',
centerLat: 40.015,
centerLon: -105.2705,
systemId: 'bcycle_boulder',
}),
bcycleEntry({
id: 'milwaukee-bublr',
city: 'Milwaukee, WI',
centerLat: 43.0389,
centerLon: -87.9065,
systemId: 'bcycle_bublr',
provider: 'Bublr',
}),
bcycleEntry({
id: 'madison-bcycle',
city: 'Madison, WI',
centerLat: 43.0731,
centerLon: -89.4012,
systemId: 'bcycle_madison',
}),
bcycleEntry({
id: 'nashville-bcycle',
city: 'Nashville, TN',
centerLat: 36.1627,
centerLon: -86.7816,
systemId: 'bcycle_nashville',
}),
bcycleEntry({
id: 'salt-lake-greenbike',
city: 'Salt Lake City, UT',
centerLat: 40.7608,
centerLon: -111.891,
systemId: 'bcycle_greenbikeslc',
provider: 'GREENbike',
}),
bcycleEntry({
id: 'san-antonio-bcycle',
city: 'San Antonio, TX',
centerLat: 29.4241,
centerLon: -98.4936,
systemId: 'bcycle_sanantonio',
}),
bcycleEntry({
id: 'cincinnati-red-bike',
city: 'Cincinnati, OH',
centerLat: 39.1031,
centerLon: -84.512,
systemId: 'bcycle_cincyredbike',
provider: 'Red Bike',
}),
bcycleEntry({
id: 'el-paso-bcycle',
city: 'El Paso, TX',
centerLat: 31.7619,
centerLon: -106.485,
systemId: 'bcycle_elpaso',
}),
bcycleEntry({
id: 'indianapolis-pacers',
city: 'Indianapolis, IN',
centerLat: 39.7684,
centerLon: -86.1581,
systemId: 'bcycle_pacersbikeshare',
provider: 'Pacers Bikeshare',
}),
bcycleEntry({
id: 'fort-lauderdale-broward',
city: 'Fort Lauderdale, FL',
centerLat: 26.1224,
centerLon: -80.1373,
systemId: 'bcycle_broward',
provider: 'Broward B-cycle',
}),
bcycleEntry({
id: 'memphis-bcycle',
city: 'Memphis, TN',
centerLat: 35.1495,
centerLon: -90.049,
systemId: 'bcycle_memphis',
}),
bcycleEntry({
id: 'des-moines-bcycle',
city: 'Des Moines, IA',
centerLat: 41.5868,
centerLon: -93.625,
systemId: 'bcycle_desmoines',
}),
bcycleEntry({
id: 'tucson-tugo',
city: 'Tucson, AZ',
centerLat: 32.2226,
centerLon: -110.9747,
systemId: 'bcycle_tugo',
provider: 'Tugo',
}),
bcycleEntry({
id: 'fort-worth-trinity',
city: 'Fort Worth, TX',
centerLat: 32.7555,
centerLon: -97.3308,
systemId: 'bcycle_fortworth',
provider: 'Trinity Metro',
}),
bcycleEntry({
id: 'omaha-heartland',
city: 'Omaha, NE',
centerLat: 41.2565,
centerLon: -95.9345,
systemId: 'bcycle_heartland',
provider: 'Heartland B-cycle',
}),
bcycleEntry({
id: 'lincoln-bikelnk',
city: 'Lincoln, NE',
centerLat: 40.8136,
centerLon: -96.7026,
systemId: 'bcycle_bikelnk',
provider: 'BikeLNK',
}),
bcycleEntry({
id: 'greenville-sc-bcycle',
city: 'Greenville, SC',
centerLat: 34.8526,
centerLon: -82.394,
systemId: 'bcycle_greenville',
}),
bcycleEntry({
id: 'buffalo-reddy',
city: 'Buffalo, NY',
centerLat: 42.8864,
centerLon: -78.8784,
systemId: 'bcycle_reddy',
provider: 'Reddy Bikeshare',
}),
bcycleEntry({
id: 'las-vegas-rtc-bike-share',
city: 'Las Vegas, NV',
centerLat: 36.1699,
centerLon: -115.1398,
systemId: 'bcycle_rtcbikeshare',
provider: 'RTC Bike Share',
}),
bcycleEntry({
id: 'santa-barbara-bcycle',
city: 'Santa Barbara, CA',
centerLat: 34.4208,
centerLon: -119.6982,
systemId: 'bcycle_santabarbara',
}),
];
function normalizeRegistryEntry(entry) {
const id = String(entry?.id || '')
.trim()
.toLowerCase();
const city = String(entry?.city || '').trim();
const centerLat = Number(entry?.centerLat);
const centerLon = Number(entry?.centerLon);
const loadRadiusKm = Number(entry?.loadRadiusKm);
const stationInformationUrl = new URL(
String(entry?.stationInformationUrl || '').trim(),
);
const stationStatusUrl = new URL(
String(entry?.stationStatusUrl || '').trim(),
);
if (!id) throw new Error('GBFS entry id is required');
if (!city) throw new Error(`GBFS entry "${id}" city is required`);
if (!Number.isFinite(centerLat) || !Number.isFinite(centerLon)) {
throw new Error(`GBFS entry "${id}" has invalid center coordinates`);
}
// Enforce HTTPS-only for GBFS feeds
if (
stationInformationUrl.protocol !== 'https:' ||
stationStatusUrl.protocol !== 'https:'
) {
throw new Error(`GBFS entry "${id}" must use https URLs`);
}
// Validate that URLs end with expected GBFS endpoint filenames
if (!/\/station_information\.json$/i.test(stationInformationUrl.pathname)) {
throw new Error(`GBFS entry "${id}" station_information URL is invalid`);
}
if (!/\/station_status\.json$/i.test(stationStatusUrl.pathname)) {
throw new Error(`GBFS entry "${id}" station_status URL is invalid`);
}
// Deduplicate hostnames across both feed URLs for proxy allowlisting
const hosts = Array.from(
new Set([
stationInformationUrl.hostname.toLowerCase(),
stationStatusUrl.hostname.toLowerCase(),
]),
);
return {
id,
city,
centerLat,
centerLon,
loadRadiusKm:
Number.isFinite(loadRadiusKm) && loadRadiusKm > 0
? loadRadiusKm
: CITY_RANGE_BASE_KM,
stationInformationUrl: stationInformationUrl.toString(),
stationStatusUrl: stationStatusUrl.toString(),
provider: String(entry?.provider || 'GBFS').trim() || 'GBFS',
hosts,
};
}
const GBFS_CITY_REGISTRY = (() => {
const seen = new Set();
return RAW_GBFS_CITY_REGISTRY.map((entry) => {
const normalized = normalizeRegistryEntry(entry);
if (seen.has(normalized.id)) {
throw new Error(`Duplicate GBFS city id: ${normalized.id}`);
}
seen.add(normalized.id);
return normalized;
});
})();
const CITY_BY_ID = new Map(
GBFS_CITY_REGISTRY.map((entry) => [entry.id, entry]),
);
export { GBFS_CITY_REGISTRY, CITY_BY_ID };