feat(bikeshare): add Kolumbus, Farte and Lillestrøm city bikes

Cover every Norwegian station-based city-bike system with an open GBFS
feed: Kolumbus Bysykkel (Stavanger) and Farte Bysykkel (Skien/Porsgrunn)
through Entur Mobility, and Bysykkel Lillestrøm through JCDecaux
Cyclocity.

api.entur.io and api.cyclocity.fr join the GBFS allowlist, each kept to
its GBFS path prefix since both hosts front other APIs. Entur serves its
feeds without a .json suffix, so the endpoint checks accept either form,
and requests to Entur carry the ET-Client-Name header it requires.
Attributed under NLOD (Entur) and Licence Ouverte (JCDecaux).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Trygve HemminghyttandClaude Opus 5.5 committed 2026-10-05 23:02:37 +02:00
1 parent 42543c1fa1
commit fa61955cf1
10 files changed
+241 -18

No files matched your search

+24 -4
View File
@@ -429,7 +429,7 @@ test('bike share names the systems it did not search', async () => {
);
});
test('bike share over Oslo searches Oslo Bysykkel only', async () => {
test('bike share over Oslo searches only the nearby Norwegian systems', async () => {
const requested = [];
const result = await composeCatalog({
tools: coreTools,
@@ -445,15 +445,35 @@ test('bike share over Oslo searches Oslo Bysykkel only', async () => {
}).call('get_bike_share', {
area: { lat: 59.9139, lon: 10.7522, radius_km: 5 },
});
assert.deepEqual(result.data.systems, ['Oslo, Norway']);
assert.deepEqual(result.data.systems.sort(), [
'Lillestrøm, Norway',
'Oslo, Norway',
]);
assert.deepEqual(result.data.systems_not_searched, []);
assert.ok(
requested.every((url) =>
url.startsWith('https://gbfs.urbansharing.com/oslobysykkel.no/'),
requested.every(
(url) =>
url.startsWith('https://gbfs.urbansharing.com/oslobysykkel.no/') ||
url.startsWith('https://api.cyclocity.fr/contracts/lillestrom/'),
),
);
});
test('bike share over Stavanger searches Kolumbus Bysykkel', async () => {
const result = await composeCatalog({
tools: coreTools,
services: {
bikeshare: {
systems: GBFS_CITY_REGISTRY,
getStations: async () => ({ data: { stations: [] } }),
},
},
}).call('get_bike_share', {
area: { lat: 58.97, lon: 5.7331, radius_km: 5 },
});
assert.deepEqual(result.data.systems, ['Stavanger, Norway']);
});
test('traffic counts only roads inside a radius area and reports missing tiles', async () => {
const flow = (segments, partial = false) => ({
getStatus: async () => ({ hasKey: true }),