From a1f51daadcbdfca0a5fd654e3888ccc8eb47aa19 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 10 Apr 2024 14:29:38 +0200 Subject: [PATCH 1/3] Add Bikeep parking updater --- docs/sandbox/VehicleParking.md | 8 +- .../bikeep/BikeepUpdaterTest.java | 45 +++ .../ext/vehicleparking/bikeep/bikeep.json | 303 ++++++++++++++++++ .../vehicleparking/bikeep/BikeepUpdater.java | 73 +++++ .../bikeep/BikeepUpdaterParameters.java | 25 ++ .../parkapi/ParkAPIUpdater.java | 8 + .../updaters/VehicleParkingUpdaterConfig.java | 12 + .../VehicleParkingDataSourceFactory.java | 3 + .../VehicleParkingSourceType.java | 1 + 9 files changed, 474 insertions(+), 4 deletions(-) create mode 100644 src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterTest.java create mode 100644 src/ext-test/resources/org/opentripplanner/ext/vehicleparking/bikeep/bikeep.json create mode 100644 src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdater.java create mode 100644 src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterParameters.java diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index 2721fff9b0c..d8e05af8d25 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -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. @@ -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. @@ -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. @@ -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. 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..82099dfbd51 --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterTest.java @@ -0,0 +1,45 @@ +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 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()); + + 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..43ed99453b2 --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdater.java @@ -0,0 +1,73 @@ +package org.opentripplanner.ext.vehicleparking.bikeep; + +import com.fasterxml.jackson.databind.JsonNode; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.framework.i18n.NonLocalizedString; +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 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) { + 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"); + + 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) + .capacity(capacity) + .entrance(entrance) + .build(); + } + + @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..d04e1900795 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; @@ -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, } From f2a76c27a070ddf3adb2e1c110ea7e095b922b09 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 10 Apr 2024 14:43:34 +0200 Subject: [PATCH 2/3] Add tag handling --- doc-templates/VehicleParking.md | 5 +- docs/RouterConfiguration.md | 6 ++ docs/sandbox/VehicleParking.md | 62 ++++++++++++++- .../bikeep/BikeepUpdaterTest.java | 2 + .../vehicleparking/bikeep/BikeepUpdater.java | 76 +++++++++++-------- .../standalone/config/router-config.json | 6 ++ 6 files changed, 124 insertions(+), 33 deletions(-) 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 d8e05af8d25..7599a762602 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 @@ -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 name of the data source. | *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 name of the data source. + +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. + +

headers

+ +**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 index 82099dfbd51..679339f359b 100644 --- a/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdaterTest.java @@ -4,6 +4,7 @@ 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; @@ -34,6 +35,7 @@ void parse() { 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()); diff --git a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdater.java b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdater.java index 43ed99453b2..9216eb79995 100644 --- a/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdater.java +++ b/src/ext/java/org/opentripplanner/ext/vehicleparking/bikeep/BikeepUpdater.java @@ -1,8 +1,12 @@ 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; @@ -16,6 +20,9 @@ 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) { @@ -25,41 +32,48 @@ public BikeepUpdater(BikeepUpdaterParameters parameters) { @Override protected VehicleParking parseElement(JsonNode jsonNode) { - var coords = jsonNode.path("geometry").path("coordinates"); - var coordinate = new WgsCoordinate(coords.get(1).asDouble(), coords.get(0).asDouble()); + 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"); + 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"); - var availability = VehicleParkingSpaces - .builder() - .bicycleSpaces(parking.get("available").asInt()) - .build(); - var capacity = VehicleParkingSpaces - .builder() - .bicycleSpaces(parking.get("total").asInt()) - .build(); + List tags = STRING_LIST_READER.readValue(props.path("tags")); - VehicleParking.VehicleParkingEntranceCreator entrance = builder -> - builder - .entranceId(new FeedScopedId(params.feedId(), vehicleParkId.getId() + "/entrance")) - .coordinate(coordinate) - .walkAccessible(true) - .carAccessible(true); + 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) - .capacity(capacity) - .entrance(entrance) - .build(); + 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 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": [ From 091e4ed5620630e3f70e4eb585a733a2e28d4d2d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 18 Apr 2024 16:42:53 +0200 Subject: [PATCH 3/3] Update documentation --- docs/sandbox/VehicleParking.md | 100 +++++++++--------- .../updaters/VehicleParkingUpdaterConfig.java | 2 +- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/docs/sandbox/VehicleParking.md b/docs/sandbox/VehicleParking.md index 7599a762602..8d9e4942440 100644 --- a/docs/sandbox/VehicleParking.md +++ b/docs/sandbox/VehicleParking.md @@ -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. @@ -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. @@ -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. @@ -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. @@ -317,14 +317,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__13__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__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 | +| 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 @@ -334,7 +334,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/[13] -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. 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 d04e1900795..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 @@ -28,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) {