fix(gbfs): count docks, not PBSC's geofenced area, as station capacity

PBSC adds the bikes a station accepts in the geofenced area around its
docks to `capacity`, and reports that part as `geofenced_capacity`, which
is not a GBFS field. Bilbao Bizi publishes 39 docks as capacity 839 with
geofenced_capacity 800, so its stations would read as nearly empty: red,
drawn at maximum size, and carded "39 avail · 0 docks · 839 cap".
BiciMAD (671 of 678 stations, about twice the docks) and Dbizi (11 of 70)
do the same.

The shared parser now reads `capacity − geofenced_capacity` when the
extension is present and no larger than the total, so the layer, its
colour ratio and get_bike_share all see the docks. No registered system
changes: none of the 52 publishes a geofenced capacity above 0 (live
feeds, 2026-10-17, all 13,397 capacities identical).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ulihhhhandClaude Opus 5.5 authored and Said Turksever committed 2026-10-17 22:33:57 +01:00
1 parent ee686a3207
commit 53cda29be4
2 files changed
+35 -1

No files matched your search

+16 -1
View File
@@ -81,6 +81,21 @@ function stationName(value) {
return fallback;
}
/**
* Docks a station physically has. PBSC adds the bikes a station accepts in its
* geofenced area around the docks to `capacity` and reports that part as
* `geofenced_capacity` (not a GBFS field): Bilbao Bizi publishes 39 docks as
* capacity 839 with geofenced_capacity 800. Without the extension, or when it
* would exceed the total, `capacity` is read as given.
*/
function dockCapacity(raw) {
const capacity = toNonNegativeInteger(raw?.capacity);
const geofenced = toNonNegativeInteger(raw?.geofenced_capacity);
if (capacity == null || geofenced == null || geofenced > capacity)
return capacity;
return capacity - geofenced;
}
/**
* Parse a GBFS station_information payload into a Map of station metadata.
* Handles field-name variations across different GBFS providers
@@ -105,7 +120,7 @@ export function parseStationInformation(payload) {
name: stationName(raw?.name) || stationName(raw?.short_name),
lat,
lon,
capacity: toNonNegativeInteger(raw?.capacity),
capacity: dockCapacity(raw),
isInstalled: normalizeGbfsBool(raw?.is_installed, true),
isRenting: normalizeGbfsBool(raw?.is_renting, true),
isReturning: normalizeGbfsBool(raw?.is_returning, true),
+19
View File
@@ -15,6 +15,25 @@ const parseName = (fields, version) =>
data: { stations: [station(fields)] },
}).get('s1').name;
const parseCapacity = (fields) =>
parseStationInformation({ data: { stations: [station(fields)] } }).get('s1')
.capacity;
test('capacity counts docks, not the geofenced area PBSC adds to it', () => {
// Bilbao Bizi, BiciMAD and Dbizi as published on 2026-10-17.
assert.equal(parseCapacity({ capacity: 839, geofenced_capacity: 800 }), 39);
assert.equal(parseCapacity({ capacity: 43, geofenced_capacity: 20 }), 23);
assert.equal(parseCapacity({ capacity: 19, geofenced_capacity: 0 }), 19);
});
test('capacity is read as given without a usable geofenced_capacity', () => {
assert.equal(parseCapacity({ capacity: 27 }), 27);
assert.equal(parseCapacity({ capacity: 27, geofenced_capacity: null }), 27);
assert.equal(parseCapacity({ capacity: 27, geofenced_capacity: 'n/a' }), 27);
assert.equal(parseCapacity({ capacity: 10, geofenced_capacity: 30 }), 10);
assert.equal(parseCapacity({ capacity: null, geofenced_capacity: 5 }), null);
});
test('GBFS v2 station names remain trimmed strings', () => {
assert.equal(
parseName({ name: ' Republic Square ', short_name: 'Short' }, '2.3'),