diff --git a/doc-templates/VehicleParking.md b/doc-templates/VehicleParking.md
index 50bd8806267..5d149e40f9a 100644
--- a/doc-templates/VehicleParking.md
+++ b/doc-templates/VehicleParking.md
@@ -3,7 +3,7 @@
## Contact Info
- For HSL Park and Ride updater: Digitransit team, HSL, Helsinki, Finland
-- For Bikely and NOI updater: Leonard Ehrenfried, [mail@leonard.io](mailto:mail@leonard.io)
+- For Bikely, NOI and Bikeep updater: Leonard Ehrenfried, [mail@leonard.io](mailto:mail@leonard.io)
## Documentation
@@ -44,6 +44,9 @@ All updaters have the following parameters in common:
+## Bikeep
+
+
## Changelog
diff --git a/docs/RouterConfiguration.md b/docs/RouterConfiguration.md
index e0f1d81b590..918717d439f 100644
--- a/docs/RouterConfiguration.md
+++ b/docs/RouterConfiguration.md
@@ -844,6 +844,12 @@ Used to group requests when monitoring OTP.
"fromDateTime" : "-P1D",
"timeout" : 300000
}
+ },
+ {
+ "type" : "vehicle-parking",
+ "feedId" : "bikeep",
+ "sourceType" : "bikeep",
+ "url" : "https://services.bikeep.com/location/v1/public-areas/no-baia-mobility/locations"
}
],
"rideHailingServices" : [
diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md
index 2721fff9b0c..8d9e4942440 100644
--- a/docs/sandbox/VehicleParking.md
+++ b/docs/sandbox/VehicleParking.md
@@ -3,7 +3,7 @@
## Contact Info
- For HSL Park and Ride updater: Digitransit team, HSL, Helsinki, Finland
-- For Bikely and NOI updater: Leonard Ehrenfried, [mail@leonard.io](mailto:mail@leonard.io)
+- For Bikely, NOI and Bikeep updater: Leonard Ehrenfried, [mail@leonard.io](mailto:mail@leonard.io)
## Documentation
@@ -33,17 +33,17 @@ All updaters have the following parameters in common:
-| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
-|---------------------------------|:-----------:|----------------------------------------------|:----------:|---------------|:-----:|
-| type = "vehicle-parking" | `enum` | The type of the updater. | *Required* | | 1.5 |
-| facilitiesFrequencySec | `integer` | How often the facilities should be updated. | *Optional* | `3600` | 2.2 |
-| facilitiesUrl | `string` | URL of the facilities. | *Optional* | | 2.2 |
-| [feedId](#u__2__feedId) | `string` | The name of the data source. | *Required* | | 2.2 |
-| hubsUrl | `string` | Hubs URL | *Optional* | | 2.2 |
-| [sourceType](#u__2__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 |
-| [timeZone](#u__2__timeZone) | `time-zone` | The time zone of the feed. | *Optional* | | 2.2 |
-| utilizationsFrequencySec | `integer` | How often the utilization should be updated. | *Optional* | `600` | 2.2 |
-| utilizationsUrl | `string` | URL of the utilization data. | *Optional* | | 2.2 |
+| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
+|---------------------------------|:-----------:|------------------------------------------------------------------------------|:----------:|---------------|:-----:|
+| type = "vehicle-parking" | `enum` | The type of the updater. | *Required* | | 1.5 |
+| facilitiesFrequencySec | `integer` | How often the facilities should be updated. | *Optional* | `3600` | 2.2 |
+| facilitiesUrl | `string` | URL of the facilities. | *Optional* | | 2.2 |
+| [feedId](#u__2__feedId) | `string` | The id of the data source, which will be the prefix of the parking lot's id. | *Required* | | 2.2 |
+| hubsUrl | `string` | Hubs URL | *Optional* | | 2.2 |
+| [sourceType](#u__2__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 |
+| [timeZone](#u__2__timeZone) | `time-zone` | The time zone of the feed. | *Optional* | | 2.2 |
+| utilizationsFrequencySec | `integer` | How often the utilization should be updated. | *Optional* | `600` | 2.2 |
+| utilizationsUrl | `string` | URL of the utilization data. | *Optional* | | 2.2 |
#### Details
@@ -53,7 +53,7 @@ All updaters have the following parameters in common:
**Since version:** `2.2` ∙ **Type:** `string` ∙ **Cardinality:** `Required`
**Path:** /updaters/[2]
-The name of the data source.
+The id of the data source, which will be the prefix of the parking lot's id.
This will end up in the API responses as the feed id of of the parking lot.
@@ -61,7 +61,7 @@ This will end up in the API responses as the feed id of of the parking lot.
**Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required`
**Path:** /updaters/[2]
-**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub`
+**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep`
The source of the vehicle updates.
@@ -104,16 +104,16 @@ Used for converting abstract opening hours into concrete points in time.
-| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
-|---------------------------------|:---------------:|----------------------------------------------------------------------------|:----------:|---------------|:-----:|
-| type = "vehicle-parking" | `enum` | The type of the updater. | *Required* | | 1.5 |
-| [feedId](#u__3__feedId) | `string` | The name of the data source. | *Required* | | 2.2 |
-| frequency | `duration` | How often to update the source. | *Optional* | `"PT1M"` | 2.2 |
-| [sourceType](#u__3__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 |
-| [timeZone](#u__3__timeZone) | `time-zone` | The time zone of the feed. | *Optional* | | 2.2 |
-| url | `string` | URL of the resource. | *Required* | | 2.2 |
-| [headers](#u__3__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.2 |
-| [tags](#u__3__tags) | `string[]` | Tags to add to the parking lots. | *Optional* | | 2.2 |
+| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
+|---------------------------------|:---------------:|------------------------------------------------------------------------------|:----------:|---------------|:-----:|
+| type = "vehicle-parking" | `enum` | The type of the updater. | *Required* | | 1.5 |
+| [feedId](#u__3__feedId) | `string` | The id of the data source, which will be the prefix of the parking lot's id. | *Required* | | 2.2 |
+| frequency | `duration` | How often to update the source. | *Optional* | `"PT1M"` | 2.2 |
+| [sourceType](#u__3__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 |
+| [timeZone](#u__3__timeZone) | `time-zone` | The time zone of the feed. | *Optional* | | 2.2 |
+| url | `string` | URL of the resource. | *Required* | | 2.2 |
+| [headers](#u__3__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.2 |
+| [tags](#u__3__tags) | `string[]` | Tags to add to the parking lots. | *Optional* | | 2.2 |
#### Details
@@ -123,7 +123,7 @@ Used for converting abstract opening hours into concrete points in time.
**Since version:** `2.2` ∙ **Type:** `string` ∙ **Cardinality:** `Required`
**Path:** /updaters/[3]
-The name of the data source.
+The id of the data source, which will be the prefix of the parking lot's id.
This will end up in the API responses as the feed id of of the parking lot.
@@ -131,7 +131,7 @@ This will end up in the API responses as the feed id of of the parking lot.
**Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required`
**Path:** /updaters/[3]
-**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub`
+**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep`
The source of the vehicle updates.
@@ -191,14 +191,14 @@ Tags to add to the parking lots.
-| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
-|---------------------------------|:---------------:|----------------------------------------------------------------------------|:----------:|---------------|:-----:|
-| type = "vehicle-parking" | `enum` | The type of the updater. | *Required* | | 1.5 |
-| [feedId](#u__4__feedId) | `string` | The name of the data source. | *Required* | | 2.2 |
-| frequency | `duration` | How often to update the source. | *Optional* | `"PT1M"` | 2.3 |
-| [sourceType](#u__4__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 |
-| url | `uri` | URL of the locations endpoint. | *Required* | | 2.3 |
-| [headers](#u__4__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 |
+| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
+|---------------------------------|:---------------:|------------------------------------------------------------------------------|:----------:|---------------|:-----:|
+| type = "vehicle-parking" | `enum` | The type of the updater. | *Required* | | 1.5 |
+| [feedId](#u__4__feedId) | `string` | The id of the data source, which will be the prefix of the parking lot's id. | *Required* | | 2.2 |
+| frequency | `duration` | How often to update the source. | *Optional* | `"PT1M"` | 2.3 |
+| [sourceType](#u__4__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 |
+| url | `uri` | URL of the locations endpoint. | *Required* | | 2.3 |
+| [headers](#u__4__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.3 |
#### Details
@@ -208,7 +208,7 @@ Tags to add to the parking lots.
**Since version:** `2.2` ∙ **Type:** `string` ∙ **Cardinality:** `Required`
**Path:** /updaters/[4]
-The name of the data source.
+The id of the data source, which will be the prefix of the parking lot's id.
This will end up in the API responses as the feed id of of the parking lot.
@@ -216,7 +216,7 @@ This will end up in the API responses as the feed id of of the parking lot.
**Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required`
**Path:** /updaters/[4]
-**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub`
+**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep`
The source of the vehicle updates.
@@ -256,14 +256,14 @@ HTTP headers to add to the request. Any header key, value can be inserted.
-| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
-|---------------------------------|:---------------:|----------------------------------------------------------------------------|:----------:|---------------|:-----:|
-| type = "vehicle-parking" | `enum` | The type of the updater. | *Required* | | 1.5 |
-| [feedId](#u__5__feedId) | `string` | The name of the data source. | *Required* | | 2.2 |
-| frequency | `duration` | How often to update the source. | *Optional* | `"PT1M"` | 2.6 |
-| [sourceType](#u__5__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 |
-| url | `uri` | URL of the locations endpoint. | *Required* | | 2.6 |
-| [headers](#u__5__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.6 |
+| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
+|---------------------------------|:---------------:|------------------------------------------------------------------------------|:----------:|---------------|:-----:|
+| type = "vehicle-parking" | `enum` | The type of the updater. | *Required* | | 1.5 |
+| [feedId](#u__5__feedId) | `string` | The id of the data source, which will be the prefix of the parking lot's id. | *Required* | | 2.2 |
+| frequency | `duration` | How often to update the source. | *Optional* | `"PT1M"` | 2.6 |
+| [sourceType](#u__5__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 |
+| url | `uri` | URL of the locations endpoint. | *Required* | | 2.6 |
+| [headers](#u__5__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.6 |
#### Details
@@ -273,7 +273,7 @@ HTTP headers to add to the request. Any header key, value can be inserted.
**Since version:** `2.2` ∙ **Type:** `string` ∙ **Cardinality:** `Required`
**Path:** /updaters/[5]
-The name of the data source.
+The id of the data source, which will be the prefix of the parking lot's id.
This will end up in the API responses as the feed id of of the parking lot.
@@ -281,7 +281,7 @@ This will end up in the API responses as the feed id of of the parking lot.
**Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required`
**Path:** /updaters/[5]
-**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub`
+**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep`
The source of the vehicle updates.
@@ -312,6 +312,66 @@ HTTP headers to add to the request. Any header key, value can be inserted.
+## Bikeep
+
+
+
+
+| Config Parameter | Type | Summary | Req./Opt. | Default Value | Since |
+|----------------------------------|:---------------:|------------------------------------------------------------------------------|:----------:|---------------|:-----:|
+| type = "vehicle-parking" | `enum` | The type of the updater. | *Required* | | 1.5 |
+| [feedId](#u__13__feedId) | `string` | The id of the data source, which will be the prefix of the parking lot's id. | *Required* | | 2.2 |
+| frequency | `duration` | How often to update the source. | *Optional* | `"PT1M"` | 2.6 |
+| [sourceType](#u__13__sourceType) | `enum` | The source of the vehicle updates. | *Required* | | 2.2 |
+| url | `uri` | URL of the locations endpoint. | *Required* | | 2.6 |
+| [headers](#u__13__headers) | `map of string` | HTTP headers to add to the request. Any header key, value can be inserted. | *Optional* | | 2.6 |
+
+
+#### Details
+
+
feedId
+
+**Since version:** `2.2` ∙ **Type:** `string` ∙ **Cardinality:** `Required`
+**Path:** /updaters/[13]
+
+The id of the data source, which will be the prefix of the parking lot's id.
+
+This will end up in the API responses as the feed id of of the parking lot.
+
+sourceType
+
+**Since version:** `2.2` ∙ **Type:** `enum` ∙ **Cardinality:** `Required`
+**Path:** /updaters/[13]
+**Enum values:** `park-api` | `bicycle-park-api` | `hsl-park` | `bikely` | `noi-open-data-hub` | `bikeep`
+
+The source of the vehicle updates.
+
+
+
+**Since version:** `2.6` ∙ **Type:** `map of string` ∙ **Cardinality:** `Optional`
+**Path:** /updaters/[13]
+
+HTTP headers to add to the request. Any header key, value can be inserted.
+
+
+
+##### Example configuration
+
+```JSON
+// router-config.json
+{
+ "updaters" : [
+ {
+ "type" : "vehicle-parking",
+ "feedId" : "bikeep",
+ "sourceType" : "bikeep",
+ "url" : "https://services.bikeep.com/location/v1/public-areas/no-baia-mobility/locations"
+ }
+ ]
+}
+```
+
+
## Changelog
diff --git a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterTest.java b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterTest.java
new file mode 100644
index 00000000000..679339f359b
--- /dev/null
+++ b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterTest.java
@@ -0,0 +1,47 @@
+package org.opentripplanner.ext.vehicleparking.bikeep;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.time.Duration;
+import java.util.Set;
+import org.junit.jupiter.api.Test;
+import org.opentripplanner.test.support.ResourceLoader;
+import org.opentripplanner.updater.spi.HttpHeaders;
+
+class BikeepUpdaterTest {
+
+ @Test
+ void parse() {
+ var uri = ResourceLoader.of(this).uri("bikeep.json");
+ var parameters = new BikeepUpdaterParameters(
+ "bikeep",
+ uri,
+ "bikeep",
+ Duration.ofSeconds(30),
+ HttpHeaders.empty()
+ );
+ var updater = new BikeepUpdater(parameters);
+ updater.update();
+ var lots = updater.getUpdates();
+
+ assertEquals(9, lots.size());
+
+ lots.forEach(l -> assertNotNull(l.getName()));
+
+ var first = lots.getFirst();
+ assertEquals("bikeep:224121", first.getId().toString());
+ assertEquals("(60.40593, 4.99634)", first.getCoordinate().toString());
+ assertEquals("Ågotnes Terminal", first.getName().toString());
+ assertEquals(10, first.getAvailability().getBicycleSpaces());
+ assertEquals(10, first.getCapacity().getBicycleSpaces());
+ assertEquals(Set.of("FREE", "PRIVATE", "BIKE", "BOOKABLE"), first.getTags());
+
+ var last = lots.getLast();
+ assertEquals("bikeep:224111", last.getId().toString());
+ assertEquals("(59.88741, 10.5205)", last.getCoordinate().toString());
+ assertEquals("Sandvika Storsenter Nytorget", last.getName().toString());
+ assertEquals(13, last.getAvailability().getBicycleSpaces());
+ assertEquals(15, last.getCapacity().getBicycleSpaces());
+ }
+}
diff --git a/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/bikeep/bikeep.json b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/bikeep/bikeep.json
new file mode 100644
index 00000000000..6f164077ee1
--- /dev/null
+++ b/src/ext-test/resources/org/opentripplanner/ext/vehicleparking/bikeep/bikeep.json
@@ -0,0 +1,303 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ 4.996344,
+ 60.405932
+ ]
+ },
+ "properties": {
+ "code": "224121",
+ "label": "Ågotnes Terminal",
+ "name": "#224121 Ågotnes Terminal",
+ "address": "Ågotnes",
+ "tags": [
+ "FREE",
+ "BIKE",
+ "PRIVATE",
+ "BOOKABLE"
+ ],
+ "icon": {
+ "png": "",
+ "png2x": "",
+ "svg": ""
+ },
+ "parking": {
+ "available": 10,
+ "online": 10,
+ "total": 10
+ },
+ "renting": null
+ }
+ },
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ 10.666802,
+ 59.436443
+ ]
+ },
+ "properties": {
+ "code": "226261",
+ "label": "Gågata Østre",
+ "name": "#226261 Gågata Østre",
+ "address": "Dronningens gate, Moss",
+ "tags": [
+ "FREE",
+ "PRIVATE",
+ "BOOKABLE",
+ "BIKE"
+ ],
+ "icon": {
+ "png": "",
+ "png2x": "",
+ "svg": ""
+ },
+ "parking": {
+ "available": 7,
+ "online": 10,
+ "total": 10
+ },
+ "renting": null
+ }
+ },
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ 10.661444,
+ 59.435401
+ ]
+ },
+ "properties": {
+ "code": "226259",
+ "label": "Gågata Vestre",
+ "name": "#226259 Gågata Vestre",
+ "address": "Dronningens gate, Moss",
+ "tags": [
+ "BIKE",
+ "FREE",
+ "PRIVATE",
+ "BOOKABLE"
+ ],
+ "icon": {
+ "png": "",
+ "png2x": "",
+ "svg": ""
+ },
+ "parking": {
+ "available": 5,
+ "online": 5,
+ "total": 5
+ },
+ "renting": null
+ }
+ },
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ 10.774958,
+ 59.946535
+ ]
+ },
+ "properties": {
+ "code": "223443",
+ "label": "Storo Storsenter",
+ "name": "#223443 Storo Storsenter",
+ "address": "Norway",
+ "tags": [
+ "BIKE",
+ "PRIVATE",
+ "BOOKABLE",
+ "FREE"
+ ],
+ "icon": {
+ "png": "https://assets.bikeep.com/locations/icons/bikeep.png",
+ "png2x": "https://assets.bikeep.com/locations/icons/bikeep@2x.png",
+ "svg": ""
+ },
+ "parking": {
+ "available": 17,
+ "online": 20,
+ "total": 20
+ },
+ "renting": null
+ }
+ },
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ 10.501222,
+ 59.914578
+ ]
+ },
+ "properties": {
+ "code": "224519",
+ "label": "Kolsås Sykkelhotell",
+ "name": "#224519 Kolsås Sykkelhotell",
+ "address": "Norway",
+ "tags": [
+ "PRIVATE",
+ "FREE",
+ "BOOKABLE",
+ "BIKE_HOUSE",
+ "BIKE"
+ ],
+ "icon": {
+ "png": "https://assets.bikeep.com/locations/icons/bikeep.png",
+ "png2x": "https://assets.bikeep.com/locations/icons/bikeep@2x.png",
+ "svg": ""
+ },
+ "parking": {
+ "available": 13,
+ "online": 22,
+ "total": 22
+ },
+ "renting": null
+ }
+ },
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ 10.663716,
+ 59.435539
+ ]
+ },
+ "properties": {
+ "code": "226260",
+ "label": "Gågata Midtre",
+ "name": "#226260 Gågata Midtre",
+ "address": "Dronningens gate, Moss",
+ "tags": [
+ "FREE",
+ "BOOKABLE",
+ "PRIVATE",
+ "BIKE"
+ ],
+ "icon": {
+ "png": "",
+ "png2x": "",
+ "svg": ""
+ },
+ "parking": {
+ "available": 5,
+ "online": 5,
+ "total": 5
+ },
+ "renting": null
+ }
+ },
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ 5.320344,
+ 60.463246
+ ]
+ },
+ "properties": {
+ "code": "226266",
+ "label": "Åsane Sykkelhus",
+ "name": "#226266 Åsane Sykkelhus",
+ "address": "Åsane terminal",
+ "tags": [
+ "BOOKABLE",
+ "BIKE",
+ "FREE",
+ "PRIVATE"
+ ],
+ "icon": {
+ "png": "",
+ "png2x": "",
+ "svg": ""
+ },
+ "parking": {
+ "available": 11,
+ "online": 12,
+ "total": 12
+ },
+ "renting": null
+ }
+ },
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ 10.521137,
+ 59.889181
+ ]
+ },
+ "properties": {
+ "code": "224112",
+ "label": "Sandvika Storsenter Kjørbokollen",
+ "name": "#224112 Sandvika Storsenter Kjørbokollen",
+ "address": "Brodtkorbsgate 7, Sandvika",
+ "tags": [
+ "PRIVATE",
+ "FREE",
+ "BIKE",
+ "BOOKABLE"
+ ],
+ "icon": {
+ "png": "",
+ "png2x": "",
+ "svg": ""
+ },
+ "parking": {
+ "available": 5,
+ "online": 5,
+ "total": 5
+ },
+ "renting": null
+ }
+ },
+ {
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [
+ 10.520496,
+ 59.887412
+ ]
+ },
+ "properties": {
+ "code": "224111",
+ "label": "Sandvika Storsenter Nytorget",
+ "name": "#224111 Sandvika Storsenter Nytorget",
+ "address": "Sandviksveien 176, Sandvika",
+ "tags": [
+ "BIKE",
+ "BOOKABLE",
+ "PRIVATE",
+ "FREE"
+ ],
+ "icon": {
+ "png": "",
+ "png2x": "",
+ "svg": ""
+ },
+ "parking": {
+ "available": 13,
+ "online": 15,
+ "total": 15
+ },
+ "renting": null
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdater.java
new file mode 100644
index 00000000000..9216eb79995
--- /dev/null
+++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdater.java
@@ -0,0 +1,87 @@
+package org.opentripplanner.ext.vehicleparking.bikeep;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectReader;
+import java.io.IOException;
+import java.util.List;
+import org.opentripplanner.framework.geometry.WgsCoordinate;
+import org.opentripplanner.framework.i18n.NonLocalizedString;
+import org.opentripplanner.framework.json.ObjectMappers;
+import org.opentripplanner.framework.tostring.ToStringBuilder;
+import org.opentripplanner.routing.vehicle_parking.VehicleParking;
+import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces;
+import org.opentripplanner.routing.vehicle_parking.VehicleParkingState;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+import org.opentripplanner.updater.spi.GenericJsonDataSource;
+
+/**
+ * Vehicle parking updater for Bikeep's API.
+ */
+public class BikeepUpdater extends GenericJsonDataSource {
+
+ private static final String JSON_PARSE_PATH = "features";
+ private static final ObjectReader STRING_LIST_READER = ObjectMappers
+ .ignoringExtraFields()
+ .readerForListOf(String.class);
+ private final BikeepUpdaterParameters params;
+
+ public BikeepUpdater(BikeepUpdaterParameters parameters) {
+ super(parameters.url().toString(), JSON_PARSE_PATH, parameters.httpHeaders());
+ this.params = parameters;
+ }
+
+ @Override
+ protected VehicleParking parseElement(JsonNode jsonNode) {
+ try {
+ var coords = jsonNode.path("geometry").path("coordinates");
+ var coordinate = new WgsCoordinate(coords.get(1).asDouble(), coords.get(0).asDouble());
+
+ var props = jsonNode.path("properties");
+ var vehicleParkId = new FeedScopedId(params.feedId(), props.path("code").asText());
+ var name = new NonLocalizedString(props.path("label").asText());
+ var parking = props.path("parking");
+
+ List tags = STRING_LIST_READER.readValue(props.path("tags"));
+
+ var availability = VehicleParkingSpaces
+ .builder()
+ .bicycleSpaces(parking.get("available").asInt())
+ .build();
+ var capacity = VehicleParkingSpaces
+ .builder()
+ .bicycleSpaces(parking.get("total").asInt())
+ .build();
+
+ VehicleParking.VehicleParkingEntranceCreator entrance = builder ->
+ builder
+ .entranceId(new FeedScopedId(params.feedId(), vehicleParkId.getId() + "/entrance"))
+ .coordinate(coordinate)
+ .walkAccessible(true)
+ .carAccessible(true);
+
+ return VehicleParking
+ .builder()
+ .id(vehicleParkId)
+ .name(name)
+ .state(VehicleParkingState.OPERATIONAL)
+ .coordinate(coordinate)
+ .bicyclePlaces(true)
+ .availability(availability)
+ .tags(tags)
+ .capacity(capacity)
+ .entrance(entrance)
+ .build();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder
+ .of(this.getClass())
+ .addStr("feedId", this.params.feedId())
+ .addStr("url", this.params.url().toString())
+ .toString();
+ }
+}
diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java
new file mode 100644
index 00000000000..be937ecdd5e
--- /dev/null
+++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java
@@ -0,0 +1,25 @@
+package org.opentripplanner.ext.vehicleparking.bikeep;
+
+import java.net.URI;
+import java.time.Duration;
+import org.opentripplanner.updater.spi.HttpHeaders;
+import org.opentripplanner.updater.vehicle_parking.VehicleParkingSourceType;
+import org.opentripplanner.updater.vehicle_parking.VehicleParkingUpdaterParameters;
+
+/**
+ * Class that extends {@link VehicleParkingUpdaterParameters} with parameters required by {@link
+ * BikeepUpdater}.
+ */
+public record BikeepUpdaterParameters(
+ String configRef,
+ URI url,
+ String feedId,
+ Duration frequency,
+ HttpHeaders httpHeaders
+)
+ implements VehicleParkingUpdaterParameters {
+ @Override
+ public VehicleParkingSourceType sourceType() {
+ return VehicleParkingSourceType.BIKEEP;
+ }
+}
diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdater.java
index fd9389f8ee3..c6f775e588f 100644
--- a/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdater.java
+++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/parkapi/ParkAPIUpdater.java
@@ -12,6 +12,7 @@
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.framework.i18n.NonLocalizedString;
import org.opentripplanner.framework.i18n.TranslatedString;
+import org.opentripplanner.framework.tostring.ToStringBuilder;
import org.opentripplanner.model.calendar.openinghours.OHCalendar;
import org.opentripplanner.model.calendar.openinghours.OpeningHoursCalendarService;
import org.opentripplanner.openstreetmap.OSMOpeningHoursParser;
@@ -36,6 +37,7 @@ abstract class ParkAPIUpdater extends GenericJsonDataSource {
private final Collection staticTags;
private final OSMOpeningHoursParser osmOpeningHoursParser;
+ private final String url;
public ParkAPIUpdater(
ParkAPIUpdaterParameters parameters,
@@ -46,6 +48,7 @@ public ParkAPIUpdater(
this.staticTags = parameters.tags();
this.osmOpeningHoursParser =
new OSMOpeningHoursParser(openingHoursCalendarService, parameters.timeZone());
+ this.url = parameters.url();
}
@Override
@@ -196,4 +199,9 @@ private List parseTags(JsonNode node, String... tagNames) {
}
return tagList;
}
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.of(getClass()).addStr("feedId", feedId).addObj("url", url).toString();
+ }
}
diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java
index f0306941547..c687899009f 100644
--- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java
+++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/VehicleParkingUpdaterConfig.java
@@ -8,6 +8,7 @@
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Set;
+import org.opentripplanner.ext.vehicleparking.bikeep.BikeepUpdaterParameters;
import org.opentripplanner.ext.vehicleparking.bikely.BikelyUpdaterParameters;
import org.opentripplanner.ext.vehicleparking.hslpark.HslParkUpdaterParameters;
import org.opentripplanner.ext.vehicleparking.noi.NoiUpdaterParameters;
@@ -27,7 +28,7 @@ public static VehicleParkingUpdaterParameters create(String updaterRef, NodeAdap
var feedId = c
.of("feedId")
.since(V2_2)
- .summary("The name of the data source.")
+ .summary("The id of the data source, which will be the prefix of the parking lot's id.")
.description("This will end up in the API responses as the feed id of of the parking lot.")
.asString();
return switch (sourceType) {
@@ -88,6 +89,17 @@ public static VehicleParkingUpdaterParameters create(String updaterRef, NodeAdap
.asDuration(Duration.ofMinutes(1)),
HttpHeadersConfig.headers(c, V2_6)
);
+ case BIKEEP -> new BikeepUpdaterParameters(
+ updaterRef,
+ c.of("url").since(V2_6).summary("URL of the locations endpoint.").asUri(),
+ feedId,
+ c
+ .of("frequency")
+ .since(V2_6)
+ .summary("How often to update the source.")
+ .asDuration(Duration.ofMinutes(1)),
+ HttpHeadersConfig.headers(c, V2_6)
+ );
};
}
diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java
index 3fe465a8cd5..a956fda0d87 100644
--- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java
+++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingDataSourceFactory.java
@@ -1,5 +1,7 @@
package org.opentripplanner.updater.vehicle_parking;
+import org.opentripplanner.ext.vehicleparking.bikeep.BikeepUpdater;
+import org.opentripplanner.ext.vehicleparking.bikeep.BikeepUpdaterParameters;
import org.opentripplanner.ext.vehicleparking.bikely.BikelyUpdater;
import org.opentripplanner.ext.vehicleparking.bikely.BikelyUpdaterParameters;
import org.opentripplanner.ext.vehicleparking.hslpark.HslParkUpdater;
@@ -39,6 +41,7 @@ public static DataSource create(
);
case BIKELY -> new BikelyUpdater((BikelyUpdaterParameters) parameters);
case NOI_OPEN_DATA_HUB -> new NoiUpdater((NoiUpdaterParameters) parameters);
+ case BIKEEP -> new BikeepUpdater((BikeepUpdaterParameters) parameters);
};
}
}
diff --git a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java
index 3a0cb7d31b3..f6a28177d8e 100644
--- a/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java
+++ b/src/main/java/org/opentripplanner/updater/vehicle_parking/VehicleParkingSourceType.java
@@ -6,4 +6,5 @@ public enum VehicleParkingSourceType {
HSL_PARK,
BIKELY,
NOI_OPEN_DATA_HUB,
+ BIKEEP,
}
diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json
index f5eaa1f942b..3b43ef1c5d2 100644
--- a/src/test/resources/standalone/config/router-config.json
+++ b/src/test/resources/standalone/config/router-config.json
@@ -420,6 +420,12 @@
"fromDateTime": "-P1D",
"timeout": 300000
}
+ },
+ {
+ "type": "vehicle-parking",
+ "feedId": "bikeep",
+ "sourceType": "bikeep",
+ "url": "https://services.bikeep.com/location/v1/public-areas/no-baia-mobility/locations"
}
],
"rideHailingServices": [