From c8f005f9a347a9c0c68c10efff5a63ca5af23dfe Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 13 Jun 2023 10:50:21 +0300 Subject: [PATCH 001/165] Add initial version of planConnection schema --- .../legacygraphqlapi/schema.graphqls | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index dd6bf025265..c759f689d37 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -8,10 +8,16 @@ This is only worth it when the execution is long running, i.e. more than ~50 mil """ directive @async on FIELD_DEFINITION +directive @oneOf on FIELD_DEFINITION + schema { query: QueryType } +input AccessibilityPreferences { + enabled: Boolean +} + """A public transport agency""" type Agency implements Node { """ @@ -273,6 +279,10 @@ enum AlertSeverityLevelType { SEVERE } +input AlightPreferences { + slack: Int +} + type OpeningHours { """ OSM-formatted string of the opening hours. @@ -758,6 +768,21 @@ enum BikesAllowed { NOT_ALLOWED } +input BikePreferences { + reluctance: Float + bikeWalking: WalkPreferences + speed: Float + switchTime: Int + switchCost: Int + optimize: OptimizationCriteria + boardCost: Int +} + +input BoardPreferences { + waitReluctance: Float + slack: Int +} + """Car park represents a location where cars can be parked.""" type CarPark implements Node & PlaceInterface { """ @@ -802,6 +827,10 @@ type CarPark implements Node & PlaceInterface { openingHours: OpeningHours } +input CarPreferences { + reluctance: Float +} + """Cluster is a list of stops grouped by name and proximity""" type Cluster implements Node { """ @@ -829,6 +858,13 @@ type Cluster implements Node { stops: [Stop!] } +input Cost @oneOf { + static: Float + function: CostFunction +} + +scalar CostFunction + type Coordinates { """Latitude (WGS 84)""" lat: Float @@ -837,6 +873,23 @@ type Coordinates { lon: Float } +input SearchWindowOptions @oneOf { + departure: SearchWindow + arrival: SearchWindow +} + +input SearchWindow { + dateTime: DateTime + timeZone: TimeZone + before: SearchWindowRestriction + after: SearchWindowRestriction +} + +input SearchWindowRestriction { + maxOffset: Duration + numberOfItineraries: Int +} + type debugOutput { totalTime: Long pathCalculationTime: Long @@ -1012,6 +1065,11 @@ type StopGeometries { googleEncoded: [Geometry] } +input Coordinate { + lat: Float! + lon: Float! +} + input InputBanned { """A comma-separated list of banned route ids""" routes: String @@ -1195,6 +1253,43 @@ input ParkingFilter { select: [ParkingFilterOperation!] } +input PlanDebugSettings { + debugItineraryFilter: Boolean +} + +input PlanLocation @oneOf { + coordinate: Coordinate + """TODO should place string be a scalar?""" + place: String +} + +input PlanLocations { + origin: PlanLocation + destination: PlanLocation +} + +input PlanPreferences { + street: PlanStreetPreferences + transit: TransitPreferences + accessibility: AccessibilityPreferences +} + +input PlanStreetModes { + direct: StreetMode + access: StreetMode + egress: StreetMode + transfer: StreetMode +} + +input PlanStreetPreferences { + modes: PlanStreetModes + bike: BikePreferences + scooter: ScooterPreferences + car: CarPreferences + walk: WalkPreferences + rental: RentalPreferences +} + """ Relative importances of optimization factors. Only effective for bicycling legs. Invariant: `timeFactor + slopeFactor + safetyFactor == 1` @@ -1448,6 +1543,11 @@ type Money { amount: Float! } +"""" +An ISO-8601-formatted datetime, i.e. `2023-06-13T14:30` for 2:30pm on June 13th 2023. +""" +scalar DateTime + """" An ISO-8601-formatted duration, i.e. `PT2H30M` for 2 hours and 30 minutes. """ @@ -1836,6 +1936,26 @@ type PageInfo { endCursor: String } +enum StreetMode { + """BICYCLE""" + BICYCLE + + """CAR""" + CAR + + """"Private car trips shared with others.""" + CARPOOL + + """Enables flexible transit for access and egress legs""" + FLEX + + """SCOOTER""" + SCOOTER + + """WALK""" + WALK +} + """Realtime vehicle position""" type VehiclePosition { """Feed-scoped ID that uniquely identifies the vehicle in the format FeedId:VehicleId""" @@ -2049,6 +2169,18 @@ type BookingInfo { dropOffMessage: String } +input OptimizationCriteria @oneOf { + type: OptimizationType + triangle: InputTriangle +} + +enum OptimizationType { + QUICK + SAFE + FLAT + GREENWAYS +} + type Place { """ For transit stops, the name of the stop. For points of interest, the name of the POI. @@ -2203,6 +2335,41 @@ type Plan { debugOutput: debugOutput! } +type PlanConnection { + searchDateTime: PlanDateTime + + locations: PlanPlaces + + routingErrors: [RoutingError!]! + + debugInformation: PlanDebugInformation! + + edges: [PlanEdge] + + pageInfo: PageInfo! +} + +type PlanDateTime { + date: DateTime + timeZone: TimeZone +} + +type PlanDebugInformation { + totalTime: Int + pathCalculationTime: Int + searchWindowUsed: Long +} + +type PlanEdge { + node: Itinerary + cursor: String! +} + +type PlanPlaces { + from: Place! + to: Place! +} + """ List of coordinates in an encoded polyline format (see https://developers.google.com/maps/documentation/utilities/polylinealgorithm). @@ -3050,6 +3217,23 @@ type QueryType { """ startTransitTripId: String @deprecated(reason: "Not implemented in OTP2") ): Plan @async + + planConnection( + searchWindow: SearchWindowOptions + locations: PlanLocations + preferences: PlanPreferences + debugSettings: PlanDebugSettings + locale: String + before: String + after: String + first: Int + last: Int + ): PlanConnection @async +} + +input RealtimePreferences { + omitCanceled: Boolean + ignoreRealtimeUpdates: Boolean } enum RealtimeState { @@ -3078,6 +3262,13 @@ enum RealtimeState { MODIFIED } +input RentalPreferences { + allowKeepingVehicleAtDestination: Boolean + keepingVehicleAtDestinationCost: Int + allowedNetworks: [String!] + bannedNetworks: [String!] +} + """ Route represents a public transportation service, usually from point A to point B and *back*, shown to customers under a single name, e.g. bus 550. Routes @@ -3248,6 +3439,14 @@ enum AbsoluteDirection { NORTHWEST } +input ScooterPreferences { + reluctance: Float + scooterWalking: WalkPreferences + speed: Float + optimize: OptimizationCriteria + boardCost: Int +} + type step { """The distance in meters that this step takes.""" distance: Float @@ -3694,6 +3893,30 @@ type TicketType implements Node { zones: [String!] } +scalar TimeZone + +input TransferPreferences { + penalty: Int + minimumTime: Duration +} + +input TransitModePreferences { + mode: TransitMode! + reluctance: Cost +} + +input TransitPreferences { + """TODO should we reuse this or make a new design""" + unpreferred: InputUnpreferred + """TODO should we reuse this or make a new design""" + banned: InputBanned + board: BoardPreferences + alight: AlightPreferences + transfer: TransferPreferences + realtime: RealtimePreferences + modes: [TransitModePreference!] +} + """Text with language""" type TranslatedString { text: String @@ -3877,6 +4100,13 @@ enum VertexType { PARKANDRIDE } +input WalkPreferences { + speed: Float + reluctance: Float + safetyFactor: Float + boardCost: Int +} + enum WheelchairBoarding { """There is no accessibility information for the stop.""" NO_INFORMATION From d0e1e6dd677d5ddbde3d9387400b22b65dcb8fc3 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 7 Aug 2023 23:04:52 +0300 Subject: [PATCH 002/165] Update src/ext/resources/legacygraphqlapi/schema.graphqls Co-authored-by: Leonard Ehrenfried --- src/ext/resources/legacygraphqlapi/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index c759f689d37..70170611a47 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -1544,7 +1544,7 @@ type Money { } """" -An ISO-8601-formatted datetime, i.e. `2023-06-13T14:30` for 2:30pm on June 13th 2023. +An ISO-8601-formatted datetime with offset, i.e. `2023-06-13T14:30+03:00` for 2:30pm on June 13th 2023 at Helsinki's offset from UTC at that time. """ scalar DateTime From 145897dad2db99e30a3ced41a262e6253bae14ca Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sat, 19 Aug 2023 23:46:57 +0300 Subject: [PATCH 003/165] Update plan debug settings --- .../legacygraphqlapi/schema.graphqls | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index 70170611a47..34a103dfa52 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -1254,7 +1254,7 @@ input ParkingFilter { } input PlanDebugSettings { - debugItineraryFilter: Boolean + itineraryFilterDebugProfile: ItineraryFilterDebugProfile = OFF } input PlanLocation @oneOf { @@ -4036,6 +4036,32 @@ type Trip implements Node { ): [Alert] } +""" +Enable this to attach a system notice to itineraries instead of removing them. This is very +convenient when tuning the itinerary-filter-chain. +""" +enum ItineraryFilterDebugProfile { + """ + Only return the requested number of itineraries, counting both actual and deleted ones. + The top `numItineraries` using the request sort order is returned. This does not work + with paging, itineraries after the limit, but inside the search-window are skipped when + moving to the next page. + """ + LIMIT_TO_NUMBER_OF_ITINERARIES + + """ + Return all itineraries, including deleted ones, inside the actual search-window used + (the requested search-window may differ). + """ + LIMIT_TO_SEARCH_WINDOW + + "List all itineraries, including all deleted itineraries." + LIST_ALL + + "By default, the debug itinerary filters is turned off." + OFF +} + """Entities, which are relevant for a trip and can contain alerts""" enum TripAlertType { """Alerts affecting the trip""" From aeebe3850de44496ff57efc112038075495b5944 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sat, 19 Aug 2023 23:48:35 +0300 Subject: [PATCH 004/165] Remove plan locations wrapper --- src/ext/resources/legacygraphqlapi/schema.graphqls | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index 34a103dfa52..ece661654b0 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -1263,11 +1263,6 @@ input PlanLocation @oneOf { place: String } -input PlanLocations { - origin: PlanLocation - destination: PlanLocation -} - input PlanPreferences { street: PlanStreetPreferences transit: TransitPreferences @@ -3220,7 +3215,8 @@ type QueryType { planConnection( searchWindow: SearchWindowOptions - locations: PlanLocations + origin: PlanLocation + destination: PlanLocation preferences: PlanPreferences debugSettings: PlanDebugSettings locale: String From 1a5ae1b705db625888856843ec3725a67f095090 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sat, 19 Aug 2023 23:51:04 +0300 Subject: [PATCH 005/165] Add wrapper for wheelchair preferences --- src/ext/resources/legacygraphqlapi/schema.graphqls | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index ece661654b0..ee0ba5dc12f 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -15,7 +15,7 @@ schema { } input AccessibilityPreferences { - enabled: Boolean + wheelchair: WheelchairPreferences } """A public transport agency""" @@ -4141,3 +4141,7 @@ enum WheelchairBoarding { """Wheelchair boarding is not possible at this stop.""" NOT_POSSIBLE } + +input WheelchairPreferences { + enabled: Boolean +} From f3a4219870faed100b53be6fddf8acdacf5747d9 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sun, 20 Aug 2023 00:00:25 +0300 Subject: [PATCH 006/165] Don't use abbreviations for lat/lon --- src/ext/resources/legacygraphqlapi/schema.graphqls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index ee0ba5dc12f..c37c3771bf8 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -1066,8 +1066,8 @@ type StopGeometries { } input Coordinate { - lat: Float! - lon: Float! + latitude: Float! + longitude: Float! } input InputBanned { From 910717a739f12e99b3a445c338fd24a8e4b73637 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sun, 20 Aug 2023 00:04:33 +0300 Subject: [PATCH 007/165] Remove usage of timezone --- src/ext/resources/legacygraphqlapi/schema.graphqls | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index c37c3771bf8..b95a309b936 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -879,8 +879,7 @@ input SearchWindowOptions @oneOf { } input SearchWindow { - dateTime: DateTime - timeZone: TimeZone + dateTime: DateTimeWithOffset before: SearchWindowRestriction after: SearchWindowRestriction } @@ -1541,7 +1540,7 @@ type Money { """" An ISO-8601-formatted datetime with offset, i.e. `2023-06-13T14:30+03:00` for 2:30pm on June 13th 2023 at Helsinki's offset from UTC at that time. """ -scalar DateTime +scalar DateTimeWithOffset """" An ISO-8601-formatted duration, i.e. `PT2H30M` for 2 hours and 30 minutes. @@ -2331,7 +2330,7 @@ type Plan { } type PlanConnection { - searchDateTime: PlanDateTime + searchDateTime: DateTimeWithOffset locations: PlanPlaces @@ -2344,11 +2343,6 @@ type PlanConnection { pageInfo: PageInfo! } -type PlanDateTime { - date: DateTime - timeZone: TimeZone -} - type PlanDebugInformation { totalTime: Int pathCalculationTime: Int @@ -3889,8 +3883,6 @@ type TicketType implements Node { zones: [String!] } -scalar TimeZone - input TransferPreferences { penalty: Int minimumTime: Duration From d2f3af5bcb700ec4e04059e676e6a3ccc61b6ab5 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sun, 20 Aug 2023 00:07:18 +0300 Subject: [PATCH 008/165] Use duration instead of long for debug information --- src/ext/resources/legacygraphqlapi/schema.graphqls | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index b95a309b936..a03e82d07e8 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -1937,9 +1937,6 @@ enum StreetMode { """CAR""" CAR - """"Private car trips shared with others.""" - CARPOOL - """Enables flexible transit for access and egress legs""" FLEX @@ -2346,7 +2343,7 @@ type PlanConnection { type PlanDebugInformation { totalTime: Int pathCalculationTime: Int - searchWindowUsed: Long + searchWindowUsed: Duration } type PlanEdge { From 00b165b330698d1a9270c4d87e3b1e487b33e8b9 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 21 Aug 2023 17:12:23 +0300 Subject: [PATCH 009/165] Use Duration instead of Int for bike switchTime --- src/ext/resources/legacygraphqlapi/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index a03e82d07e8..30890476a46 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -772,7 +772,7 @@ input BikePreferences { reluctance: Float bikeWalking: WalkPreferences speed: Float - switchTime: Int + switchTime: Duration switchCost: Int optimize: OptimizationCriteria boardCost: Int From c26c09c38c79c822a723fa1d09beb1e685a2525d Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 21 Aug 2023 17:19:45 +0300 Subject: [PATCH 010/165] Update search window model --- src/ext/resources/legacygraphqlapi/schema.graphqls | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index 30890476a46..a8f5a0a7134 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -880,12 +880,13 @@ input SearchWindowOptions @oneOf { input SearchWindow { dateTime: DateTimeWithOffset - before: SearchWindowRestriction - after: SearchWindowRestriction + duration: Duration + numberOfItineraries: Int + gracePeriod: SearchWindowGracePeriod } -input SearchWindowRestriction { - maxOffset: Duration +input SearchWindowGracePeriod { + duration: Duration numberOfItineraries: Int } From 27e7fb10fa928f560948fed91506eb191d713108 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 21 Aug 2023 17:28:27 +0300 Subject: [PATCH 011/165] Split place location --- src/ext/resources/legacygraphqlapi/schema.graphqls | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index a8f5a0a7134..456c22d8600 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -1257,10 +1257,10 @@ input PlanDebugSettings { itineraryFilterDebugProfile: ItineraryFilterDebugProfile = OFF } -input PlanLocation @oneOf { +input PlanLocation { coordinate: Coordinate - """TODO should place string be a scalar?""" - place: String + stop: FeedScopedId + label: String } input PlanPreferences { @@ -1548,6 +1548,8 @@ An ISO-8601-formatted duration, i.e. `PT2H30M` for 2 hours and 30 minutes. """ scalar Duration +scalar FeedScopedId + type RideHailingProvider { "The ID of the ride hailing provider." id: String! From a83ffa46bcb8b67e9d0c5e34629718073b2cdda4 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 24 Aug 2023 23:37:00 +0300 Subject: [PATCH 012/165] Introduce DurationInSeconds --- src/ext/resources/legacygraphqlapi/schema.graphqls | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index 456c22d8600..f8f08f68505 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -1548,6 +1548,11 @@ An ISO-8601-formatted duration, i.e. `PT2H30M` for 2 hours and 30 minutes. """ scalar Duration +"""" +String representation of a duration in seconds. Examples of the format: `300s`, `1s`, `2.5s`, `3.14s` or `15.233s`. +""" +scalar DurationInSeconds + scalar FeedScopedId type RideHailingProvider { @@ -2345,7 +2350,7 @@ type PlanConnection { type PlanDebugInformation { totalTime: Int - pathCalculationTime: Int + pathCalculationTime: DurationInSeconds searchWindowUsed: Duration } From aa8455a361174750c6af1530ad21c49f08cccefb Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 24 Aug 2023 23:48:24 +0300 Subject: [PATCH 013/165] Remove extra quotes --- src/ext/resources/legacygraphqlapi/schema.graphqls | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index f8f08f68505..48a8c5ed7c4 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -1538,17 +1538,17 @@ type Money { amount: Float! } -"""" +""" An ISO-8601-formatted datetime with offset, i.e. `2023-06-13T14:30+03:00` for 2:30pm on June 13th 2023 at Helsinki's offset from UTC at that time. """ scalar DateTimeWithOffset -"""" +""" An ISO-8601-formatted duration, i.e. `PT2H30M` for 2 hours and 30 minutes. """ scalar Duration -"""" +""" String representation of a duration in seconds. Examples of the format: `300s`, `1s`, `2.5s`, `3.14s` or `15.233s`. """ scalar DurationInSeconds @@ -1832,7 +1832,7 @@ enum Mode { """TRAM""" TRAM - """"Private car trips shared with others.""" + """Private car trips shared with others.""" CARPOOL """A taxi, possibly operated by a public transport agency.""" @@ -1882,7 +1882,7 @@ enum TransitMode { """TRAM""" TRAM - """"Private car trips shared with others.""" + """Private car trips shared with others.""" CARPOOL """A taxi, possibly operated by a public transport agency.""" From 31e5416446d0db2510cb86e186d3f1dc0dcb351b Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 28 Aug 2023 23:12:17 +0300 Subject: [PATCH 014/165] Simplify cost --- src/ext/resources/legacygraphqlapi/schema.graphqls | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index 48a8c5ed7c4..836e7912513 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -858,11 +858,6 @@ type Cluster implements Node { stops: [Stop!] } -input Cost @oneOf { - static: Float - function: CostFunction -} - scalar CostFunction type Coordinates { @@ -3895,7 +3890,7 @@ input TransferPreferences { input TransitModePreferences { mode: TransitMode! - reluctance: Cost + reluctance: CostFunction } input TransitPreferences { From 8372e112566973d9a99dac64e0645d7251e81464 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 28 Aug 2023 23:19:41 +0300 Subject: [PATCH 015/165] Merge realtime preferences into enum --- src/ext/resources/legacygraphqlapi/schema.graphqls | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index 836e7912513..cb7823d51e6 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -3221,9 +3221,10 @@ type QueryType { ): PlanConnection @async } -input RealtimePreferences { - omitCanceled: Boolean - ignoreRealtimeUpdates: Boolean +enum RealtimePreference { + USE_EVERYTHING + RETURN_NO_CANCELLATIONS + IGNORE_EVERYTHING } enum RealtimeState { @@ -3901,7 +3902,7 @@ input TransitPreferences { board: BoardPreferences alight: AlightPreferences transfer: TransferPreferences - realtime: RealtimePreferences + realtime: RealtimePreference = USE_EVERYTHING modes: [TransitModePreference!] } From c8635e353515a2f4dc96c0974c66b41fd6482b7f Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 28 Aug 2023 23:21:11 +0300 Subject: [PATCH 016/165] Make slack parameter clearer --- src/ext/resources/legacygraphqlapi/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index cb7823d51e6..1d208e0567d 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -3886,7 +3886,7 @@ type TicketType implements Node { input TransferPreferences { penalty: Int - minimumTime: Duration + minimumWaitingTime: Duration } input TransitModePreferences { From f58d5cd5a6fcfbe579929311b71be27a81c7d900 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 31 Aug 2023 15:09:46 +0300 Subject: [PATCH 017/165] Refactor datetime/search window input --- .../resources/legacygraphqlapi/schema.graphqls | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index 1d208e0567d..a868cbf8859 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -868,19 +868,18 @@ type Coordinates { lon: Float } -input SearchWindowOptions @oneOf { - departure: SearchWindow - arrival: SearchWindow +input PlanDateTimeInput @oneOf { + earliestDeparture: DateTimeWithOffset + latestArrival: DateTimeWithOffset } -input SearchWindow { - dateTime: DateTimeWithOffset +input SearchWindowInput { duration: Duration numberOfItineraries: Int - gracePeriod: SearchWindowGracePeriod + gracePeriod: SearchWindowGracePeriodInput } -input SearchWindowGracePeriod { +input SearchWindowGracePeriodInput { duration: Duration numberOfItineraries: Int } @@ -3208,7 +3207,8 @@ type QueryType { ): Plan @async planConnection( - searchWindow: SearchWindowOptions + dateTime: PlanDateTimeInput + searchWindow: SearchWindowInput origin: PlanLocation destination: PlanLocation preferences: PlanPreferences From c921b2053e12b3ed27867b6bc7bdde09627a6ae4 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 31 Aug 2023 15:10:34 +0300 Subject: [PATCH 018/165] Rename penalty -> costPenalty --- src/ext/resources/legacygraphqlapi/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index a868cbf8859..48c1c251006 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -3885,7 +3885,7 @@ type TicketType implements Node { } input TransferPreferences { - penalty: Int + costPenalty: Int minimumWaitingTime: Duration } From 8075a6729351b6f7480e427d744ba99ea614d00e Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 31 Aug 2023 15:14:49 +0300 Subject: [PATCH 019/165] Improve street modes --- .../legacygraphqlapi/schema.graphqls | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index 48c1c251006..e86608c7c20 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -1264,10 +1264,10 @@ input PlanPreferences { } input PlanStreetModes { - direct: StreetMode - access: StreetMode - egress: StreetMode - transfer: StreetMode + direct: [StreetModeInputOptions!] + access: [StreetModeInputOptions!] + egress: [StreetModeInputOptions!] + transfer: [StreetModeInputOptions!] } input PlanStreetPreferences { @@ -1932,20 +1932,29 @@ type PageInfo { endCursor: String } -enum StreetMode { - """BICYCLE""" +enum StreetModeInputOptions { BICYCLE - """CAR""" + BICYCLE_RENTAL + + BICYCLE_PARKING + CAR - """Enables flexible transit for access and egress legs""" + CAR_RENTAL + + CAR_PARKING + + CAR_PICKUP + + CAR_DROPOFF + FLEX - """SCOOTER""" SCOOTER - """WALK""" + SCOOTER_RENTAL + WALK } From 6a4e7c23e99cc70fa09ea7197d732ee0cbe1af60 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 15 Sep 2023 11:44:07 +0300 Subject: [PATCH 020/165] Split street modes into different enums --- .../legacygraphqlapi/schema.graphqls | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index e86608c7c20..56d35eb4c82 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -1264,10 +1264,14 @@ input PlanPreferences { } input PlanStreetModes { - direct: [StreetModeInputOptions!] - access: [StreetModeInputOptions!] - egress: [StreetModeInputOptions!] - transfer: [StreetModeInputOptions!] + """Selection of only one allowed for now.""" + direct: [PlanDirectMode!] + """Selection of only one allowed for now.""" + access: [PlanAccessEgressMode!] + """Selection of only one allowed for now.""" + egress: [PlanAccessEgressMode!] + """Selection of only one allowed for now.""" + transfer: [PlanTransferMode!] } input PlanStreetPreferences { @@ -1932,7 +1936,7 @@ type PageInfo { endCursor: String } -enum StreetModeInputOptions { +enum PlanAccessEgressMode { BICYCLE BICYCLE_RENTAL @@ -1958,6 +1962,40 @@ enum StreetModeInputOptions { WALK } +enum PlanDirectMode { + BICYCLE + + BICYCLE_RENTAL + + BICYCLE_PARKING + + CAR + + CAR_RENTAL + + CAR_PARKING + + CAR_PICKUP + + CAR_DROPOFF + + FLEX + + SCOOTER + + SCOOTER_RENTAL + + WALK +} + +enum PlanTransferMode { + BICYCLE + + SCOOTER + + WALK +} + """Realtime vehicle position""" type VehiclePosition { """Feed-scoped ID that uniquely identifies the vehicle in the format FeedId:VehicleId""" From 7f1e064d259d0d8cd39ec4662c1881760f0a9f77 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 15 Sep 2023 11:47:01 +0300 Subject: [PATCH 021/165] Remove pickup and dropoff from direct --- src/ext/resources/legacygraphqlapi/schema.graphqls | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ext/resources/legacygraphqlapi/schema.graphqls b/src/ext/resources/legacygraphqlapi/schema.graphqls index 56d35eb4c82..2216beda0d2 100644 --- a/src/ext/resources/legacygraphqlapi/schema.graphqls +++ b/src/ext/resources/legacygraphqlapi/schema.graphqls @@ -1975,10 +1975,6 @@ enum PlanDirectMode { CAR_PARKING - CAR_PICKUP - - CAR_DROPOFF - FLEX SCOOTER From 371fe80a9d177c068494ca603a8cd37f58bd557a Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 15 Sep 2023 13:34:24 +0300 Subject: [PATCH 022/165] Add parking preferences --- src/ext/resources/gtfsgraphqlapi/schema.graphqls | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index 13e40adfeb4..0309f343a88 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -1284,6 +1284,7 @@ input PlanStreetPreferences { car: CarPreferences walk: WalkPreferences rental: RentalPreferences + parking: VehicleParkingInput } """ From 461887139fbbeab1f3a9da625f848a7687b64b51 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 15 Sep 2023 13:34:41 +0300 Subject: [PATCH 023/165] Add transfer number limits --- src/ext/resources/gtfsgraphqlapi/schema.graphqls | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index 0309f343a88..6cceed4c674 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -4091,6 +4091,8 @@ type TicketType implements Node { input TransferPreferences { costPenalty: Int minimumWaitingTime: Duration + maximumAdditionalTransfers: Int = 5 + maximumTransfers: Int = 12 } input TransitModePreferences { From 5cb141aa0098905a014b93ed1baaa8656391ce12 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sun, 17 Sep 2023 00:57:26 +0300 Subject: [PATCH 024/165] Improve vehicle walking preferences --- .../resources/gtfsgraphqlapi/schema.graphqls | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index 6cceed4c674..00146831478 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -770,10 +770,8 @@ enum BikesAllowed { input BikePreferences { reluctance: Float - bikeWalking: WalkPreferences + bikeWalking: VehicleWalkPreferences speed: Float - switchTime: Duration - switchCost: Int optimize: OptimizationCriteria boardCost: Int } @@ -3622,7 +3620,7 @@ enum AbsoluteDirection { input ScooterPreferences { reluctance: Float - scooterWalking: WalkPreferences + scooterWalking: VehicleWalkPreferences speed: Float optimize: OptimizationCriteria boardCost: Int @@ -4322,6 +4320,28 @@ enum VertexType { PARKANDRIDE } +input VehicleWalkPreferences { + """Speed when walking a vehicle.""" + speed: Float + + """ + Reluctance in a format of `30s + 2.0 x` where the static cost is applied + both when getting off the vehicle and when getting on the vehicle again. + However, these static costs are not applied when getting on a rented vehicle + for the first time or off the vehicle when returning the vehicle. The multiplier + is for how bad walking the vehicle is compared to being in transit for equal + lengths of time. + """ + reluctance: CostFunction + + """" + How long it takes to hop on or off a vehicle when switching to walking the vehicle + or when getting on the vehicle again. However, this is not applied when getting + on a rented vehicle for the first time or off the vehicle when returning the vehicle. + """ + hopOnOrOffTime: Int +} + input WalkPreferences { speed: Float reluctance: Float From 300e21ef7aa320e37f5d856918aa4120d24432ff Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sun, 17 Sep 2023 01:06:29 +0300 Subject: [PATCH 025/165] Add rental time/cost parameters --- src/ext/resources/gtfsgraphqlapi/schema.graphqls | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index 00146831478..b46f1e34bc8 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -856,6 +856,8 @@ type Cluster implements Node { stops: [Stop!] } +scalar Cost + scalar CostFunction type Coordinates { @@ -3443,6 +3445,10 @@ input RentalPreferences { keepingVehicleAtDestinationCost: Int allowedNetworks: [String!] bannedNetworks: [String!] + pickupTime: Duration + pickupCost: Cost + dropOffTime: Duration + dropOffCost: Cost } """ From 8cd2a010acc68f29ba90dc840d5b10364ce4dabc Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sun, 17 Sep 2023 23:38:35 +0300 Subject: [PATCH 026/165] Use more scalars --- .../resources/gtfsgraphqlapi/schema.graphqls | 76 +++++++++++++------ 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index b46f1e34bc8..8d1570c7cf7 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -280,7 +280,7 @@ enum AlertSeverityLevelType { } input AlightPreferences { - slack: Int + slack: Duration } type OpeningHours { @@ -769,16 +769,16 @@ enum BikesAllowed { } input BikePreferences { - reluctance: Float + reluctance: Reluctance bikeWalking: VehicleWalkPreferences - speed: Float + speed: Speed optimize: OptimizationCriteria - boardCost: Int + boardCost: Cost } input BoardPreferences { - waitReluctance: Float - slack: Int + waitReluctance: Reluctance + slack: Duration } """Car park represents a location where cars can be parked.""" @@ -826,7 +826,7 @@ type CarPark implements Node & PlaceInterface { } input CarPreferences { - reluctance: Float + reluctance: Reluctance } """Cluster is a list of stops grouped by name and proximity""" @@ -860,6 +860,8 @@ scalar Cost scalar CostFunction +scalar Coordinate + type Coordinates { """Latitude (WGS 84)""" lat: Float @@ -1062,9 +1064,9 @@ type StopGeometries { googleEncoded: [Geometry] } -input Coordinate { - latitude: Float! - longitude: Float! +input InputPlanCoordinates { + latitude: Coordinate! + longitude: Coordinate! } input InputBanned { @@ -1190,6 +1192,8 @@ input InputPreferred { otherThanPreferredRoutesPenalty: Int } +scalar Locale + """ Preferences for parking facilities used during the routing. """ @@ -1255,7 +1259,7 @@ input PlanDebugSettings { } input PlanLocation { - coordinate: Coordinate + coordinate: InputPlanCoordinates stop: FeedScopedId label: String } @@ -1302,6 +1306,21 @@ input InputTriangle { timeFactor: Float } +""" +Relative importances of optimization factors. Only effective for bicycling legs. +Invariant: `safety + flatness + time == 1` +""" +input InputTriangleFactors { + """Relative importance of safety""" + safety: WeightingFactor + + """Relative importance of flat terrain""" + flatness: WeightingFactor + + """Relative importance of duration""" + time: WeightingFactor +} + input InputUnpreferred { """A comma-separated list of ids of the routes unpreferred by the user.""" routes: String @@ -2317,7 +2336,7 @@ type BookingInfo { input OptimizationCriteria @oneOf { type: OptimizationType - triangle: InputTriangle + triangle: InputTriangleFactors } enum OptimizationType { @@ -2327,6 +2346,8 @@ enum OptimizationType { GREENWAYS } +scalar Speed + "The board/alight position in between two stops of the pattern of a trip with continuous pickup/drop off." type PositionBetweenStops { "Position of the previous stop in the pattern. Positions are not required to start from 0 or be consecutive." @@ -2528,7 +2549,7 @@ type PlanConnection { } type PlanDebugInformation { - totalTime: Int + totalTime: DurationInSeconds pathCalculationTime: DurationInSeconds searchWindowUsed: Duration } @@ -3400,7 +3421,7 @@ type QueryType { destination: PlanLocation preferences: PlanPreferences debugSettings: PlanDebugSettings - locale: String + locale: Locale before: String after: String first: Int @@ -3440,9 +3461,11 @@ enum RealtimeState { MODIFIED } +scalar Reluctance + input RentalPreferences { allowKeepingVehicleAtDestination: Boolean - keepingVehicleAtDestinationCost: Int + keepingVehicleAtDestinationCost: Cost allowedNetworks: [String!] bannedNetworks: [String!] pickupTime: Duration @@ -3625,11 +3648,11 @@ enum AbsoluteDirection { } input ScooterPreferences { - reluctance: Float + reluctance: Reluctance scooterWalking: VehicleWalkPreferences - speed: Float + speed: Speed optimize: OptimizationCriteria - boardCost: Int + boardCost: Cost } type step { @@ -4093,7 +4116,7 @@ type TicketType implements Node { } input TransferPreferences { - costPenalty: Int + costPenalty: Cost minimumWaitingTime: Duration maximumAdditionalTransfers: Int = 5 maximumTransfers: Int = 12 @@ -4328,7 +4351,7 @@ enum VertexType { input VehicleWalkPreferences { """Speed when walking a vehicle.""" - speed: Float + speed: Speed """ Reluctance in a format of `30s + 2.0 x` where the static cost is applied @@ -4345,16 +4368,19 @@ input VehicleWalkPreferences { or when getting on the vehicle again. However, this is not applied when getting on a rented vehicle for the first time or off the vehicle when returning the vehicle. """ - hopOnOrOffTime: Int + hopOnOrOffTimeSeconds: Int } input WalkPreferences { - speed: Float - reluctance: Float - safetyFactor: Float - boardCost: Int + speed: Speed + reluctance: Reluctance + safetyFactor: WeightingFactor + boardCost: Cost } +"""A fractional multiplier between 0 and 1, for example 0.25.""" +scalar WeightingFactor + enum WheelchairBoarding { """There is no accessibility information for the stop.""" NO_INFORMATION From c88f4fd4ec6c9d14b0c9ee761c2d11fd2c9d0b13 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 18 Sep 2023 01:03:26 +0300 Subject: [PATCH 027/165] Split optimization types, rename them and rename safety --- .../resources/gtfsgraphqlapi/schema.graphqls | 126 +++++++++++++++--- 1 file changed, 109 insertions(+), 17 deletions(-) diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index 8d1570c7cf7..ceafe49987f 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -768,11 +768,11 @@ enum BikesAllowed { NOT_ALLOWED } -input BikePreferences { +input BicyclePreferences { reluctance: Reluctance bikeWalking: VehicleWalkPreferences speed: Speed - optimize: OptimizationCriteria + optimize: InputCyclingOptimizationCriteria boardCost: Cost } @@ -1283,7 +1283,7 @@ input PlanStreetModes { input PlanStreetPreferences { modes: PlanStreetModes - bike: BikePreferences + bicycle: BicyclePreferences scooter: ScooterPreferences car: CarPreferences walk: WalkPreferences @@ -1308,11 +1308,32 @@ input InputTriangle { """ Relative importances of optimization factors. Only effective for bicycling legs. -Invariant: `safety + flatness + time == 1` +Invariant: `cyclingFriendliness + flatness + time == 1` """ -input InputTriangleFactors { - """Relative importance of safety""" - safety: WeightingFactor +input InputTriangleCyclingFactors { + """ + Relative importance of cycling friendliness. Factors that are used to evaluate + cycling friendliness include safety, convenience and general cyclist preferences. + """ + cyclingFriendliness: WeightingFactor + + """Relative importance of flat terrain""" + flatness: WeightingFactor + + """Relative importance of duration""" + time: WeightingFactor +} + +""" +Relative importances of optimization factors. Only effective for scooter legs. +Invariant: `scooterFriendliness + flatness + time == 1` +""" +input InputTriangleScooterFactors { + """ + Relative importance of scoooter friendliness. Factors that are used to evaluate + scoooter friendliness include safety, convenience and general preferences. + """ + scooterFriendliness: WeightingFactor """Relative importance of flat terrain""" flatness: WeightingFactor @@ -2334,16 +2355,87 @@ type BookingInfo { dropOffMessage: String } -input OptimizationCriteria @oneOf { - type: OptimizationType - triangle: InputTriangleFactors +input InputCyclingOptimizationCriteria @oneOf { + type: CyclingOptimizationType + triangle: InputTriangleCyclingFactors } -enum OptimizationType { - QUICK - SAFE - FLAT - GREENWAYS +""" +Predefined optimization alternatives for bicycling routing. For more customization, +one can use the triangle factors. +""" +enum CyclingOptimizationType { + """ + Search for routes with the shortest duration while ignoring the cycling friendliness + of the streets (the routes should still follow local regulations). Routes can include + steep streets, if they are the fastest alternatives. This option was previously called + `QUICK`. + """ + SHORTEST_DURATION + + """ + Emphasize flatness over other aspects of cycling friendliness or duration of the route. + This option was previously called `FLAT`. + """ + FLATTEST_STREETS + + """ + Emphasize cycling friendliness over flatness or duration of the route. Factors that + are used to evaluate for cycling friendliness include safety, convenience and general cyclist + preferences. This option was previously called `SAFE`. + """ + CYCLING_FRIENDLY_STREETS + + """ + Completely ignore the elevation differences and prefer the streets that are evaluated + to be cycling friendliest even more than with the `CYCLING_FRIENDLY_STREETS` option. + Factors that are used to evaluate cycling friendliness include safety, convenience + and general cyclist preferences. This option was previously called `GREENWAYS`. + """ + CYCLING_FRIENDLIEST_STREETS +} + +input InputScooterOptimizationCriteria @oneOf { + type: ScooterOptimizationType + triangle: InputTriangleScooterFactors +} + +""" +Predefined optimization alternatives for scooter routing. For more customization, +one can use the triangle factors. +""" +enum ScooterOptimizationType { + """ + Search for routes with the shortest duration while ignoring the scooter friendliness + of the streets. The routes should still follow local regulations, but currently scooters + are only allowed on the same streets as bicycles which might not be accurate for each country + or with different types of scooters. Routes can include steep streets, if they are + the fastest alternatives. This option was previously called `QUICK`. + """ + SHORTEST_DURATION + + """ + Emphasize flatness over other aspects of scooter friendliness or duration of the route. + This option was previously called `FLAT`. + """ + FLATTEST_STREETS + + """ + Emphasize scooter friendliness over flatness or duration of the route. Factors that + are used to evaluate for scooter friendliness include safety, convenience and general + preferences. Note, currently the same criteria is used both for cycling and scooter travel + to determine how cycling or scooter friendly streets are. This option was previously called `SAFE`. + """ + SCOOTER_FRIENDLY_STREETS + + """ + Completely ignore the elevation differences and prefer the streets that are evaluated + to be scooter friendliest even more than with the `SCOOTER_FRIENDLY_STREETS` option. + Factors that are used to evaluate scooter friendliness include safety, convenience + and general preferences. Note, currently the same criteria is used both for cycling and scooter travel + to determine how cycling or scooter friendly streets are. This option was previously called `GREENWAYS`. + """ + SCOOTER_FRIENDLIEST_STREETS } scalar Speed @@ -3651,7 +3743,7 @@ input ScooterPreferences { reluctance: Reluctance scooterWalking: VehicleWalkPreferences speed: Speed - optimize: OptimizationCriteria + optimize: InputScooterOptimizationCriteria boardCost: Cost } @@ -4374,7 +4466,7 @@ input VehicleWalkPreferences { input WalkPreferences { speed: Speed reluctance: Reluctance - safetyFactor: WeightingFactor + walkFriendlinessFactor: WeightingFactor boardCost: Cost } From e608dc368aa8d9dc9a6db39b4d91e45b08d67dbe Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 18 Sep 2023 23:11:47 +0300 Subject: [PATCH 028/165] Improve readibility slightly --- src/ext/resources/gtfsgraphqlapi/schema.graphqls | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index ceafe49987f..0cca646ff6a 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -2387,8 +2387,8 @@ enum CyclingOptimizationType { CYCLING_FRIENDLY_STREETS """ - Completely ignore the elevation differences and prefer the streets that are evaluated - to be cycling friendliest even more than with the `CYCLING_FRIENDLY_STREETS` option. + Completely ignore the elevation differences and prefer the streets, that are evaluated + to be cycling friendliest, even more than with the `CYCLING_FRIENDLY_STREETS` option. Factors that are used to evaluate cycling friendliness include safety, convenience and general cyclist preferences. This option was previously called `GREENWAYS`. """ @@ -2429,8 +2429,8 @@ enum ScooterOptimizationType { SCOOTER_FRIENDLY_STREETS """ - Completely ignore the elevation differences and prefer the streets that are evaluated - to be scooter friendliest even more than with the `SCOOTER_FRIENDLY_STREETS` option. + Completely ignore the elevation differences and prefer the streets, that are evaluated + to be scooter friendliest, even more than with the `SCOOTER_FRIENDLY_STREETS` option. Factors that are used to evaluate scooter friendliness include safety, convenience and general preferences. Note, currently the same criteria is used both for cycling and scooter travel to determine how cycling or scooter friendly streets are. This option was previously called `GREENWAYS`. From 77bccc103d17d5aff31ef6f5ed48e60b6570e0a9 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 18 Sep 2023 23:18:19 +0300 Subject: [PATCH 029/165] Use Input suffix on input types --- .../resources/gtfsgraphqlapi/schema.graphqls | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index 0cca646ff6a..5f41c780df0 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -14,7 +14,7 @@ schema { query: QueryType } -input AccessibilityPreferences { +input InputAccessibilityPreferences { wheelchair: WheelchairPreferences } @@ -279,7 +279,7 @@ enum AlertSeverityLevelType { SEVERE } -input AlightPreferences { +input InputAlightPreferences { slack: Duration } @@ -768,7 +768,7 @@ enum BikesAllowed { NOT_ALLOWED } -input BicyclePreferences { +input InputBicyclePreferences { reluctance: Reluctance bikeWalking: VehicleWalkPreferences speed: Speed @@ -776,7 +776,7 @@ input BicyclePreferences { boardCost: Cost } -input BoardPreferences { +input InputBoardPreferences { waitReluctance: Reluctance slack: Duration } @@ -825,7 +825,7 @@ type CarPark implements Node & PlaceInterface { openingHours: OpeningHours } -input CarPreferences { +input InputCarPreferences { reluctance: Reluctance } @@ -870,18 +870,18 @@ type Coordinates { lon: Float } -input PlanDateTimeInput @oneOf { +input InputPlanDateTimeInput @oneOf { earliestDeparture: DateTimeWithOffset latestArrival: DateTimeWithOffset } -input SearchWindowInput { +input InputSearchWindowInput { duration: Duration numberOfItineraries: Int gracePeriod: SearchWindowGracePeriodInput } -input SearchWindowGracePeriodInput { +input InputSearchWindowGracePeriodInput { duration: Duration numberOfItineraries: Int } @@ -1254,23 +1254,23 @@ input ParkingFilter { select: [ParkingFilterOperation!] } -input PlanDebugSettings { +input InputPlanDebugSettings { itineraryFilterDebugProfile: ItineraryFilterDebugProfile = OFF } -input PlanLocation { +input InputPlanLocation { coordinate: InputPlanCoordinates stop: FeedScopedId label: String } -input PlanPreferences { +input InputPlanPreferences { street: PlanStreetPreferences transit: TransitPreferences accessibility: AccessibilityPreferences } -input PlanStreetModes { +input InputPlanStreetModes { """Selection of only one allowed for now.""" direct: [PlanDirectMode!] """Selection of only one allowed for now.""" @@ -1281,7 +1281,7 @@ input PlanStreetModes { transfer: [PlanTransferMode!] } -input PlanStreetPreferences { +input InputPlanStreetPreferences { modes: PlanStreetModes bicycle: BicyclePreferences scooter: ScooterPreferences @@ -3555,7 +3555,7 @@ enum RealtimeState { scalar Reluctance -input RentalPreferences { +input InputRentalPreferences { allowKeepingVehicleAtDestination: Boolean keepingVehicleAtDestinationCost: Cost allowedNetworks: [String!] @@ -3739,7 +3739,7 @@ enum AbsoluteDirection { NORTHWEST } -input ScooterPreferences { +input InputScooterPreferences { reluctance: Reluctance scooterWalking: VehicleWalkPreferences speed: Speed @@ -4207,19 +4207,19 @@ type TicketType implements Node { zones: [String!] } -input TransferPreferences { +input InputTransferPreferences { costPenalty: Cost minimumWaitingTime: Duration maximumAdditionalTransfers: Int = 5 maximumTransfers: Int = 12 } -input TransitModePreferences { +input InputTransitModePreferences { mode: TransitMode! reluctance: CostFunction } -input TransitPreferences { +input InputTransitPreferences { """TODO should we reuse this or make a new design""" unpreferred: InputUnpreferred """TODO should we reuse this or make a new design""" @@ -4441,7 +4441,7 @@ enum VertexType { PARKANDRIDE } -input VehicleWalkPreferences { +input InputVehicleWalkPreferences { """Speed when walking a vehicle.""" speed: Speed @@ -4463,7 +4463,7 @@ input VehicleWalkPreferences { hopOnOrOffTimeSeconds: Int } -input WalkPreferences { +input InputWalkPreferences { speed: Speed reluctance: Reluctance walkFriendlinessFactor: WeightingFactor @@ -4486,6 +4486,6 @@ enum WheelchairBoarding { NOT_POSSIBLE } -input WheelchairPreferences { +input InputWheelchairPreferences { enabled: Boolean } From 2ad6ffacb15cdc4bd756f3547eb2378b79a2bcd8 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 29 Sep 2023 22:35:41 +0300 Subject: [PATCH 030/165] Continue renaming and use an input type for timetable preferences --- .../resources/gtfsgraphqlapi/schema.graphqls | 88 ++++++++++++------- 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index 5f41c780df0..af183ceae21 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -15,7 +15,7 @@ schema { } input InputAccessibilityPreferences { - wheelchair: WheelchairPreferences + wheelchair: InputWheelchairPreferences } """A public transport agency""" @@ -770,7 +770,7 @@ enum BikesAllowed { input InputBicyclePreferences { reluctance: Reluctance - bikeWalking: VehicleWalkPreferences + bikeWalking: InputVehicleWalkPreferences speed: Speed optimize: InputCyclingOptimizationCriteria boardCost: Cost @@ -870,18 +870,18 @@ type Coordinates { lon: Float } -input InputPlanDateTimeInput @oneOf { +input InputPlanDateTime @oneOf { earliestDeparture: DateTimeWithOffset latestArrival: DateTimeWithOffset } -input InputSearchWindowInput { +input InputSearchWindow { duration: Duration numberOfItineraries: Int - gracePeriod: SearchWindowGracePeriodInput + gracePeriod: InputSearchWindowGracePeriod } -input InputSearchWindowGracePeriodInput { +input InputSearchWindowGracePeriod { duration: Duration numberOfItineraries: Int } @@ -1265,9 +1265,9 @@ input InputPlanLocation { } input InputPlanPreferences { - street: PlanStreetPreferences - transit: TransitPreferences - accessibility: AccessibilityPreferences + street: InputPlanStreetPreferences + transit: InputTransitPreferences + accessibility: InputAccessibilityPreferences } input InputPlanStreetModes { @@ -1282,12 +1282,12 @@ input InputPlanStreetModes { } input InputPlanStreetPreferences { - modes: PlanStreetModes - bicycle: BicyclePreferences - scooter: ScooterPreferences - car: CarPreferences - walk: WalkPreferences - rental: RentalPreferences + modes: InputPlanStreetModes + bicycle: InputBicyclePreferences + scooter: InputScooterPreferences + car: InputCarPreferences + walk: InputWalkPreferences + rental: InputRentalPreferences parking: VehicleParkingInput } @@ -3507,12 +3507,12 @@ type QueryType { ): Plan @async planConnection( - dateTime: PlanDateTimeInput - searchWindow: SearchWindowInput - origin: PlanLocation - destination: PlanLocation - preferences: PlanPreferences - debugSettings: PlanDebugSettings + dateTime: InputPlanDateTime + searchWindow: InputSearchWindow + origin: InputPlanLocation + destination: InputPlanLocation + preferences: InputPlanPreferences + debugSettings: InputPlanDebugSettings locale: Locale before: String after: String @@ -3521,12 +3521,6 @@ type QueryType { ): PlanConnection @async } -enum RealtimePreference { - USE_EVERYTHING - RETURN_NO_CANCELLATIONS - IGNORE_EVERYTHING -} - enum RealtimeState { """ The trip information comes from the GTFS feed, i.e. no real-time update has been applied. @@ -3741,7 +3735,7 @@ enum AbsoluteDirection { input InputScooterPreferences { reluctance: Reluctance - scooterWalking: VehicleWalkPreferences + scooterWalking: InputVehicleWalkPreferences speed: Speed optimize: InputScooterOptimizationCriteria boardCost: Cost @@ -4207,6 +4201,34 @@ type TicketType implements Node { zones: [String!] } +input InputTimetablePreferences { + """ + When true, realtime updates are considered during the routing. + In practice, when this option is set as false, some of the suggestions might not be + realistic as the transfers could be invalid due to delays, + trips can be cancelled or stops can be skipped. + """ + includeRealtimeUpdates: Boolean = true + + """ + When true, departures that have been cancelled ahead of time will be + included during the routing. This means that an itinerary can include + a cancelled departure while some other alternative that contains no cancellations + could be filtered out as the alternative containing a cancellation would normally + be better. + """ + includePlannedCancellations: Boolean = false + + """ + When true, departures that have been cancelled through a realtime feed will be + included during the routing. This means that an itinerary can include + a cancelled departure while some other alternative that contains no cancellations + could be filtered out as the alternative containing a cancellation would normally + be better. This option can't be set to true while `includeRealtimeUpdates` is false. + """ + includeRealtimeCancellations: Boolean = false +} + input InputTransferPreferences { costPenalty: Cost minimumWaitingTime: Duration @@ -4224,11 +4246,11 @@ input InputTransitPreferences { unpreferred: InputUnpreferred """TODO should we reuse this or make a new design""" banned: InputBanned - board: BoardPreferences - alight: AlightPreferences - transfer: TransferPreferences - realtime: RealtimePreference = USE_EVERYTHING - modes: [TransitModePreference!] + board: InputBoardPreferences + alight: InputAlightPreferences + transfer: InputTransferPreferences + timetable: InputTimetablePreferences + modes: [InputTransitModePreference!] } """Text with language""" From eade1f7c4e0680e36801007eec0603812d1a10f9 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 10 Oct 2023 17:40:44 +0300 Subject: [PATCH 031/165] Remove some parameters that don't need to be exposed now --- src/ext/resources/gtfsgraphqlapi/schema.graphqls | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index af183ceae21..8468755c0e3 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -3554,10 +3554,6 @@ input InputRentalPreferences { keepingVehicleAtDestinationCost: Cost allowedNetworks: [String!] bannedNetworks: [String!] - pickupTime: Duration - pickupCost: Cost - dropOffTime: Duration - dropOffCost: Cost } """ @@ -4244,8 +4240,6 @@ input InputTransitModePreferences { input InputTransitPreferences { """TODO should we reuse this or make a new design""" unpreferred: InputUnpreferred - """TODO should we reuse this or make a new design""" - banned: InputBanned board: InputBoardPreferences alight: InputAlightPreferences transfer: InputTransferPreferences From 9099707b71014c2b9a3daf22f6d069c5dc927a8e Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 10 Oct 2023 18:25:11 +0300 Subject: [PATCH 032/165] Use safety instead of friendliness again --- .../resources/gtfsgraphqlapi/schema.graphqls | 67 ++++++++++--------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index 8468755c0e3..610ec33b53c 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -1308,14 +1308,15 @@ input InputTriangle { """ Relative importances of optimization factors. Only effective for bicycling legs. -Invariant: `cyclingFriendliness + flatness + time == 1` +Invariant: `cyclingSafety + flatness + time == 1` """ input InputTriangleCyclingFactors { """ - Relative importance of cycling friendliness. Factors that are used to evaluate - cycling friendliness include safety, convenience and general cyclist preferences. + Relative importance of cycling safety, but this factor can also include other + concerns such as convenience and general cyclist preferences by taking into account + road surface etc. """ - cyclingFriendliness: WeightingFactor + cyclingSafety: WeightingFactor """Relative importance of flat terrain""" flatness: WeightingFactor @@ -1326,14 +1327,15 @@ input InputTriangleCyclingFactors { """ Relative importances of optimization factors. Only effective for scooter legs. -Invariant: `scooterFriendliness + flatness + time == 1` +Invariant: `scooterSafety + flatness + time == 1` """ input InputTriangleScooterFactors { """ - Relative importance of scoooter friendliness. Factors that are used to evaluate - scoooter friendliness include safety, convenience and general preferences. + Relative importance of scooter safety, but this factor can also include other + concerns such as convenience and general scooter preferences by taking into account + road surface etc. """ - scooterFriendliness: WeightingFactor + scooterSafety: WeightingFactor """Relative importance of flat terrain""" flatness: WeightingFactor @@ -2366,7 +2368,7 @@ one can use the triangle factors. """ enum CyclingOptimizationType { """ - Search for routes with the shortest duration while ignoring the cycling friendliness + Search for routes with the shortest duration while ignoring the cycling safety of the streets (the routes should still follow local regulations). Routes can include steep streets, if they are the fastest alternatives. This option was previously called `QUICK`. @@ -2374,25 +2376,24 @@ enum CyclingOptimizationType { SHORTEST_DURATION """ - Emphasize flatness over other aspects of cycling friendliness or duration of the route. - This option was previously called `FLAT`. + Emphasize flatness over safety or duration of the route. This option was previously called `FLAT`. """ FLATTEST_STREETS """ - Emphasize cycling friendliness over flatness or duration of the route. Factors that - are used to evaluate for cycling friendliness include safety, convenience and general cyclist - preferences. This option was previously called `SAFE`. + Emphasize cycling safety over flatness or duration of the route. Safety can also include other + concerns such as convenience and general cyclist preferences by taking into account + road surface etc. This option was previously called `SAFE`. """ - CYCLING_FRIENDLY_STREETS + SAFE_STREETS """ Completely ignore the elevation differences and prefer the streets, that are evaluated - to be cycling friendliest, even more than with the `CYCLING_FRIENDLY_STREETS` option. - Factors that are used to evaluate cycling friendliness include safety, convenience - and general cyclist preferences. This option was previously called `GREENWAYS`. + to be the safest, even more than with the `SAFE_STREETS` option. + Safety can also include other concerns such as convenience and general cyclist preferences + by taking into account road surface etc. This option was previously called `GREENWAYS`. """ - CYCLING_FRIENDLIEST_STREETS + SAFEST_STREETS } input InputScooterOptimizationCriteria @oneOf { @@ -2406,7 +2407,7 @@ one can use the triangle factors. """ enum ScooterOptimizationType { """ - Search for routes with the shortest duration while ignoring the scooter friendliness + Search for routes with the shortest duration while ignoring the scooter safety of the streets. The routes should still follow local regulations, but currently scooters are only allowed on the same streets as bicycles which might not be accurate for each country or with different types of scooters. Routes can include steep streets, if they are @@ -2415,27 +2416,27 @@ enum ScooterOptimizationType { SHORTEST_DURATION """ - Emphasize flatness over other aspects of scooter friendliness or duration of the route. - This option was previously called `FLAT`. + Emphasize flatness over safety or duration of the route. This option was previously called `FLAT`. """ FLATTEST_STREETS """ - Emphasize scooter friendliness over flatness or duration of the route. Factors that - are used to evaluate for scooter friendliness include safety, convenience and general - preferences. Note, currently the same criteria is used both for cycling and scooter travel - to determine how cycling or scooter friendly streets are. This option was previously called `SAFE`. + Emphasize scooter safety over flatness or duration of the route. Safety can also include other + concerns such as convenience and general preferences by taking into account road surface etc. + Note, currently the same criteria is used both for cycling and scooter travel to determine how + safe streets are for cycling or scooter. This option was previously called `SAFE`. """ - SCOOTER_FRIENDLY_STREETS + SAFE_STREETS """ Completely ignore the elevation differences and prefer the streets, that are evaluated - to be scooter friendliest, even more than with the `SCOOTER_FRIENDLY_STREETS` option. - Factors that are used to evaluate scooter friendliness include safety, convenience - and general preferences. Note, currently the same criteria is used both for cycling and scooter travel - to determine how cycling or scooter friendly streets are. This option was previously called `GREENWAYS`. + to be safest for scooters, even more than with the `SAFE_STREETS` option. + Safety can also include other concerns such as convenience and general preferences by taking + into account road surface etc. Note, currently the same criteria is used both for cycling and + scooter travel to determine how safe streets are for cycling or scooter. + This option was previously called `GREENWAYS`. """ - SCOOTER_FRIENDLIEST_STREETS + SAFEST_STREETS } scalar Speed @@ -4482,7 +4483,7 @@ input InputVehicleWalkPreferences { input InputWalkPreferences { speed: Speed reluctance: Reluctance - walkFriendlinessFactor: WeightingFactor + walkSafetyFactor: WeightingFactor boardCost: Cost } From 184312452c06465280fbfed52ae8ad883d674294 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 19 Oct 2023 23:58:41 +0300 Subject: [PATCH 033/165] Write schema documentation --- .../resources/gtfsgraphqlapi/schema.graphqls | 714 +++++++++++++++++- 1 file changed, 688 insertions(+), 26 deletions(-) diff --git a/src/ext/resources/gtfsgraphqlapi/schema.graphqls b/src/ext/resources/gtfsgraphqlapi/schema.graphqls index 610ec33b53c..66966675147 100644 --- a/src/ext/resources/gtfsgraphqlapi/schema.graphqls +++ b/src/ext/resources/gtfsgraphqlapi/schema.graphqls @@ -8,13 +8,25 @@ This is only worth it when the execution is long running, i.e. more than ~50 mil """ directive @async on FIELD_DEFINITION +""" +Only one of the fields on an input type should be defined. + +This is used when input type parameter fields are alternatives and not meant to be combined together. +""" directive @oneOf on FIELD_DEFINITION schema { query: QueryType } +""" +Plan accessibilty preferences. This can be expanded to contain preferences for various accessibility use cases +in the future. Currently only generic wheelchair preferences are available. +""" input InputAccessibilityPreferences { + """ + Wheelchair related preferences. Note, currently this is the only accessibility mode that is available. + """ wheelchair: InputWheelchairPreferences } @@ -279,7 +291,14 @@ enum AlertSeverityLevelType { SEVERE } +""" +Preferences related to alighting from a transit vehicle. +""" input InputAlightPreferences { + """ + What is the required minimum time alighting from a vehicle. + Invariant: `board slack + alight slack <= transfer minimum total slack` + """ slack: Duration } @@ -768,16 +787,54 @@ enum BikesAllowed { NOT_ALLOWED } +""" +Preferences related to travel with a bicycle. +""" input InputBicyclePreferences { + """ + A multiplier for how bad cycling is compared to being in transit for equal lengths of time. + """ reluctance: Reluctance - bikeWalking: InputVehicleWalkPreferences + + """ + Walking preferences when walking a bicycle. + """ + bicycleWalking: InputVehicleWalkPreferences + + """ + Maximum speed on flat ground while riding a scooter. Note, this speed is higher than + the average speed will be in itineraries as this is the maximum speed but there are + factors that slow down cycling such as crossings, intersections and elevation changes. + """ speed: Speed + + """ + What criteria should be used when optimizing a cycling route. + """ optimize: InputCyclingOptimizationCriteria + + """ + Cost of boarding a vehicle with a bicycle. + """ boardCost: Cost } +""" +Preferences related to boarding a transit vehicle. Note, board costs for each street mode +can be found under the street mode preferences. +""" input InputBoardPreferences { + """ + A multiplier for how bad waiting at a stop is compared to being in transit for equal lengths of time. + """ waitReluctance: Reluctance + + """ + What is the required minimum waiting time at a stop. Setting this value as `0s`, for example, can lead + to passenger missing a connection when the vehicle leaves ahead of time or the passenger arrives to the + stop later than expected. + Invariant: `board slack + alight slack <= transfer minimum total slack` + """ slack: Duration } @@ -825,7 +882,13 @@ type CarPark implements Node & PlaceInterface { openingHours: OpeningHours } +""" +Preferences related to traveling on a car (excluding car travel on transit services such as taxi). +""" input InputCarPreferences { + """ + A multiplier for how bad travelling on car is compared to being in transit for equal lengths of time. + """ reluctance: Reluctance } @@ -856,10 +919,23 @@ type Cluster implements Node { stops: [Stop!] } +""" +A static cost that is applied to a certain event or entity. Cost is a positive integer, +for example `450`. One cost unit should roughly match a one second travel on transit. +""" scalar Cost +""" +A cost function that is applied to a certain event. String must be of the format: +`A + B x`, where A is fixed cost and B is a multiplier of time x. For example: `600 + 2.0 x`. +Positive numbers should be used. One cost unit should roughly match a one second travel +on transit. Either `A` or `B x` can be omitted. +""" scalar CostFunction +""" +Either a latitude or a longitude coordinate as a WGS 84 format number. +""" scalar Coordinate type Coordinates { @@ -870,19 +946,73 @@ type Coordinates { lon: Float } +""" +Plan date time options. Only one of the values should be defined. +""" input InputPlanDateTime @oneOf { + """ + Earliest departure date time. The returned itineraries should not + depart before this instant unless one is using paging to find earlier + itineraries. Note, it is not currently possible to define both + `earliestDeparture` and `latestArrival`. + """ earliestDeparture: DateTimeWithOffset + + """ + Latest arrival time date time. The returned itineraries should not + arrive to the destination after this instant unless one is using + paging to find later itineraries. Note, it is not currently possible + to define both `earliestDeparture` and `latestArrival`. + """ latestArrival: DateTimeWithOffset } +""" +Search window for the plan search. +""" input InputSearchWindow { + """ + Duration of the search window. This either starts at the defined earliest departure + time or ends at the latest arrival time. If this is not provided, a reasonable + search window is automatically generated. When search for earlier or later itineraries + with paging, this search window is no longer used and the new window will be based + on how many suggestions were returned in the previous search. The new search window can be + shorter or longer than the original search window. Note, itineraries are returned faster + with a smaller search window. + """ duration: Duration + + """ + Maximum number of itineraries that will be returned. It's possible that no itineraries are + found, paging for earlier or later itineraries can yield to results in that case. The returned + itineraries are either closest to the defined earliest departure time or to the latest arrival + time. + """ numberOfItineraries: Int - gracePeriod: InputSearchWindowGracePeriod + + """ + Defines a grace period outside of the normal search window (either before the defined earliest + departure time or after the defined latest arrival time) for some which some itineraries can + be returned. + """ + gracePeriod: InputSearchWindowGracePeriod @deprecated(reason: "Not yet implemented.") } +""" +Defines a grace period outside of the normal search window (either before the defined earliest +departure time or after the defined latest arrival time) for some which some itineraries can +be returned. +""" input InputSearchWindowGracePeriod { + """ + Duration of the grace period. Either ending when the normal search window starts if earliest + departure time is defined, or after the latest arrival time. + """ duration: Duration + + """ + Number of itineraries that returned within the grace period. + """ numberOfItineraries: Int } @@ -1064,8 +1194,17 @@ type StopGeometries { googleEncoded: [Geometry] } +""" +A coordinate pair used for a location in a plan query. +""" input InputPlanCoordinates { + """ + Latitude as a WGS 84 format number. + """ latitude: Coordinate! + """ + Longitude as a WGS 84 format number. + """ longitude: Coordinate! } @@ -1192,6 +1331,9 @@ input InputPreferred { otherThanPreferredRoutesPenalty: Int } +""" +TODO add documentation. +""" scalar Locale """ @@ -1254,40 +1396,132 @@ input ParkingFilter { select: [ParkingFilterOperation!] } +""" +Settings related to debugging why certain itineraries are returned. +""" input InputPlanDebugSettings { + """ + Itinerary filter debug profile used to control the behaviour of itinerary filters. + """ itineraryFilterDebugProfile: ItineraryFilterDebugProfile = OFF } +""" +Plan location settings. Either coordinates or a stop should be defined. Label is optional +and can be used together with the other fields. +""" input InputPlanLocation { - coordinate: InputPlanCoordinates + """ + Coordinates of the location. Note, either coordinates or a stop should be defined. + """ + coordinates: InputPlanCoordinates + + """ + Stop that should be used as a location for the search. The expected value is the stop's + feedscoped GTFS ID, for example `feedId:stopId`. Note, either coordinates or a stop should + be defined. + """ stop: FeedScopedId + + """ + A label that can be attached to the location. This label is then returned with the location + in the itineraries. + """ label: String } +""" +Wrapper type for different types of preferences related to plan query. +""" input InputPlanPreferences { + """ + Street routing preferences used for ingress, egress and transfers. These do not directly affect + the transit legs but can change how preferable walking or cycling, for example, is compared to + transit. + """ street: InputPlanStreetPreferences + + """ + Transit routing preferences used for transit legs. + """ transit: InputTransitPreferences + + """ + Accessibility preferences that affect both the street and transit routing. + """ accessibility: InputAccessibilityPreferences } +""" +Street mode options for different phases of an itinerary. +""" input InputPlanStreetModes { - """Selection of only one allowed for now.""" + """ + Street mode that is used when searching for itineraries that don't use any transit. + Selection of only one allowed for now. + """ direct: [PlanDirectMode!] - """Selection of only one allowed for now.""" + + """ + Street mode that is used when searching for access to transit network from origin. + Selection of only one allowed for now. + """ access: [PlanAccessEgressMode!] - """Selection of only one allowed for now.""" + + """ + Street mode that is used when searching for egress to destination from transit network. + Selection of only one allowed for now. + """ egress: [PlanAccessEgressMode!] - """Selection of only one allowed for now.""" + + """ + Street mode that is used when searching for transfers. + Selection of only one allowed for now. + """ transfer: [PlanTransferMode!] } +""" +Street routing preferences used for ingress, egress and transfers. These do not directly affect +the transit legs but can change how preferable walking or cycling, for example, is compared to +transit. +""" input InputPlanStreetPreferences { + """ + Which modes of travel can be used for street routing at different stages. + """ modes: InputPlanStreetModes + + """ + Cycling related preferences. + """ bicycle: InputBicyclePreferences + + """ + Scooter (kick or electrical) related preferences. + """ scooter: InputScooterPreferences + + """ + Car related preferences. These are not used for car travel as part of transit, such as + taxi travel. + """ car: InputCarPreferences + + """ + Walk related preferences. These are not used when walking a bicycle or a scooter as they + have their own preferences. + """ walk: InputWalkPreferences + + """ + Rental related preferences. + """ rental: InputRentalPreferences + + """ + Parking related preferences. + """ parking: VehicleParkingInput } @@ -1695,6 +1929,10 @@ String representation of a duration in seconds. Examples of the format: `300s`, """ scalar DurationInSeconds +""" +Feedscoped ID. These follow the format of `feed:entityId`, The feedId should be unique system or a data set identifier +and the entityId should be unique within that system or data set. +""" scalar FeedScopedId type RideHailingProvider { @@ -2001,35 +2239,45 @@ enum Mode { MONORAIL } +""" +Transit modes include modes that are used within organized transportation networks +run by public transportation authorities, taxi companies etc. +Equivalent to GTFS route_type or to NeTEx TransportMode. +""" enum TransitMode { - """AIRPLANE""" AIRPLANE - """BUS""" + """ + Most busses or coaches use this mode but sometimes the other bus-like modes such + `COACH` or `TROLLEYBUS` are used. + """ BUS - """CABLE_CAR""" CABLE_CAR - """COACH""" + """ + Sometimes coaches use this mode but sometimes they are modelled to use `BUS` mode + instead. + """ COACH - """FERRY""" FERRY - """FUNICULAR""" FUNICULAR - """GONDOLA""" GONDOLA - """RAIL""" + """ + This includes long or short distance trains. Tram, subway, monorail and funicular have + their own modes but it's possible that sometimes they use this mode instead. + """ RAIL - """SUBWAY""" + """ + Subway or metro, depending on the local terminology. + """ SUBWAY - """TRAM""" TRAM """Private car trips shared with others.""" @@ -2088,59 +2336,174 @@ type PageInfo { endCursor: String } +""" +Street modes that can be used for access to transit network or for +egress from transit network to destination. +""" enum PlanAccessEgressMode { + """ + Cycling to or from a stop and boarding a vehicle with the bicycle. + Note, this can include walking when it's needed to walk the bicycle. + Access and egress can use cycling only if the mode used for transfers + is also cycling. + """ BICYCLE + """ + Bicycle rental can use either station based systems or "floating" + vehicles which are not linked to a rental station. Note, this + can include walking before picking up or after dropping off the + bicycle or when it's needed to walk the bicycle. + """ BICYCLE_RENTAL + """ + Starting the itinerary with a bicycle and parking the bicycle to + a parking location. Note, this can include walking after parking + the bicycle or when it's needed to walk the bicycle. + """ BICYCLE_PARKING - CAR - + """ + Car rental can use either station based systems or "floating" + vehicles which are not linked to a rental station. Note, this + can include walking before picking up or after dropping off the + car. + """ CAR_RENTAL + """ + Starting the itinerary with a car and parking the car to a parking location. + Note, this can include walking after parking the car. + """ CAR_PARKING + """ + Getting picked up by a car from a location that is accessible with a car. + Note, this can include walking before the pickup. + """ CAR_PICKUP + """ + Getting dropped off by a car to a location that is accessible with a car. + Note, this can include walking after the drop-off. + """ CAR_DROPOFF + """ + Flexible transit. This can include different forms of flexible transit that + can be defined in GTFS-Flex or in Netex. Note, this can include walking before + or after the flexible transit leg. + """ FLEX + """ + Riding a scooter to or from a stop and boarding a vehicle with the scooter. + Note, this can include walking when it's needed to walk the scooter. + """ SCOOTER + """ + Scooter rental can use either station based systems or "floating" + vehicles which are not linked to a rental station. Note, this + can include walking before picking up or after dropping off the + scooter or when it's needed to walk the scooter. + """ SCOOTER_RENTAL + """ + Walking to or from a stop. + """ WALK } enum PlanDirectMode { + """ + Cycling from the origin to the destination. Note, this can include walking + when it's needed to walk the bicycle. + """ BICYCLE + """ + Bicycle rental can use either station based systems or "floating" + vehicles which are not linked to a rental station. Note, this + can include walking before picking up or after dropping off the + bicycle or when it's needed to walk the bicycle. + """ BICYCLE_RENTAL + """ + Starting the itinerary with a bicycle and parking the bicycle to + a parking location. Note, this can include walking after parking + the bicycle or when it's needed to walk the bicycle. + """ BICYCLE_PARKING + """ + Driving a car from the origin to the destination. + """ CAR + """ + Car rental can use either station based systems or "floating" + vehicles which are not linked to a rental station. Note, this + can include walking before picking up or after dropping off the + car. + """ CAR_RENTAL + """ + Starting the itinerary with a car and parking the car to a parking location. + Note, this can include walking after parking the car. + """ CAR_PARKING + """ + Flexible transit. This can include different forms of flexible transit that + can be defined in GTFS-Flex or in Netex. Note, this can include walking before + or after the flexible transit leg. + """ FLEX + """ + Riding a scooter from the origin to the destination. Note, this can include walking + when it's needed to walk the scooter. + """ SCOOTER + """ + Scooter rental can use either station based systems or "floating" + vehicles which are not linked to a rental station. Note, this + can include walking before picking up or after dropping off the + scooter or when it's needed to walk the scooter. + """ SCOOTER_RENTAL + """ + Walking from the origin to the destination. Note, this can include walking + when it's needed to walk the bicycle. + """ WALK } enum PlanTransferMode { + """ + Cycling between transit vehicles (typically between stops). Note, this can + include walking when it's needed to walk the bicycle. Transfers can only use + cycling if the mode used for access and egress is also `BICYCLE`. + """ BICYCLE + """ + Riding a scooter between transit vehicles (typically between stops). Note, this can + include walking when it's needed to walk the scooter. Transfers can only use + scooter if the mode used for access and egress is also `SCOOTER`. + """ SCOOTER + """ + Walking between transit vehicles (typically between stops). + """ WALK } @@ -2357,8 +2720,18 @@ type BookingInfo { dropOffMessage: String } +""" +What criteria should be used when optimizing a cycling route. +""" input InputCyclingOptimizationCriteria @oneOf { + """ + Use one of the predefined optimization types. + """ type: CyclingOptimizationType + + """ + Define optimization by weighing three criteria. + """ triangle: InputTriangleCyclingFactors } @@ -2396,8 +2769,18 @@ enum CyclingOptimizationType { SAFEST_STREETS } +""" +What criteria should be used when optimizing a scooter route. +""" input InputScooterOptimizationCriteria @oneOf { + """ + Use one of the predefined optimization types. + """ type: ScooterOptimizationType + + """ + Define optimization by weighing three criteria. + """ triangle: InputTriangleScooterFactors } @@ -2439,6 +2822,9 @@ enum ScooterOptimizationType { SAFEST_STREETS } +""" +Speed in meters per seconds. Values are positive floating point numbers (for example, 2.34). +""" scalar Speed "The board/alight position in between two stops of the pattern of a trip with continuous pickup/drop off." @@ -2627,33 +3013,94 @@ type Plan { debugOutput: debugOutput! } +""" +Plan (result of an itinerary search) that follows +[GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). +""" type PlanConnection { + """ + What was the starting point for the itinerary search. + """ searchDateTime: DateTimeWithOffset + """ + Origin and destination of the search. + """ locations: PlanPlaces + """ + Errors faced during the routing search. + """ routingErrors: [RoutingError!]! + """ + Debug information about the search. + """ debugInformation: PlanDebugInformation! + """ + Edges which contain the itineraries. Part of the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ edges: [PlanEdge] + """ + Contains cursors to continue the search and the information if there are more itineraries available. + Part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ pageInfo: PageInfo! } +""" +Debug information about an itinerary search. +""" type PlanDebugInformation { + """ + Total time taken for the route request. + """ totalTime: DurationInSeconds + + """ + Time taken in the transit router (including access/egress street router). + """ pathCalculationTime: DurationInSeconds + + """ + The search window that was used for the search. + """ searchWindowUsed: Duration } +""" +Edge outputted by a plan search. Part of the +[GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). +""" type PlanEdge { + """ + An itinerary suggestion. Part of the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ node: Itinerary + + """ + The cursor of the edge. Part of the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ cursor: String! } +""" +Origin and destination of an itinerary search. +""" type PlanPlaces { + """ + Origin of the itinerary search. + """ from: Place! + + """ + Destination of the itinerary search. + """ to: Place! } @@ -3507,17 +3954,82 @@ type QueryType { startTransitTripId: String @deprecated(reason: "Not implemented in OTP2") ): Plan @async + """ + Plan (itinerary) search that follows + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + """ planConnection( + """ + Datetime of the search. It's possible to either define the earliest departure time + or the latest arrival time. + """ dateTime: InputPlanDateTime + + """ + The search window(s) of the search. It also allows to limit how many itineraries are returned. + """ searchWindow: InputSearchWindow + + """ + The origin where the search starts. Usually coordinates but can also a be a stop. + """ origin: InputPlanLocation + + """ + The destination where the search ends. Usually coordinates but can also a be a stop. + """ destination: InputPlanLocation + + """ + Preferences that affect what itineraries are returned. Preferences are split into categories. + """ preferences: InputPlanPreferences + + """ + Settings meant for debugging purposes that control the search and the returned itineraries. + """ debugSettings: InputPlanDebugSettings + + """ + Locale used for translations. Note, there might not necessarily be translations available. + It's possible and recommended to use the ´accept-language´ header instead of this parameter. + """ locale: Locale - before: String + + """ + Takes in cursor from a previous search. Used for forward pagination. If earliest departure time + is used in the original query, the new search then returns itineraries that depart after + the start time of the last itinerary that was returned, or at the same time if there are multiple + itinerary options that can depart at that moment in time. + If latest arrival time is defined, the new search returns itineraries that arrive before the end + time of the last itinerary that was returned in the previous search, or at the same time if there + are multiple itinerary options that can arrive at that moment in time. This parameter is part of + the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) and + should be used together with the `first` parameter. + """ after: String + + """ + How many new itineraries should at maximum be returned in forward pagination. This parameter is + part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) + and should be used together with the `after` parameter. + """ first: Int + + """ + Takes in cursor from a previous search. Used for backwards pagination. If earliest departure time + is used in the original query, the new search then returns itineraries that depart before that time. + If latest arrival time is defined, the new search returns itineraries that arrive after that time. + This parameter is part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) + and should be used together with the `last` parameter. + """ + before: String + + """ + How many new itineraries should at maximum be returned in backwards pagination. This parameter is + part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) + and should be used together with the `before` parameter. + """ last: Int ): PlanConnection @async } @@ -3548,12 +4060,37 @@ enum RealtimeState { MODIFIED } +""" +A cost multiplier for how bad something is compared to being in transit for equal lengths of time. +Value should be a float and bigger than equal to 1.0. +""" scalar Reluctance +""" +Preferences related to rental (station based or floating vehicle rental). +""" input InputRentalPreferences { + """ + Is it possible to arrive to the destination without dropping off the rental + vehicle first. + """ allowKeepingVehicleAtDestination: Boolean + + """ + Cost associated with arriving to the destination with a rented vehicle. + No cost is applied if arriving to the destination after dropping off the rented + vehicle. + """ keepingVehicleAtDestinationCost: Cost + + """ + Rental networks which can be potentially used as part of an itinerary. + """ allowedNetworks: [String!] + + """ + Rental networks which cannot be used as part of an itinerary. + """ bannedNetworks: [String!] } @@ -3730,11 +4267,36 @@ enum AbsoluteDirection { NORTHWEST } +""" +Preferences related to travel with a scooter (kick or e-scooter). +""" input InputScooterPreferences { + """ + A multiplier for how bad riding a scooter is compared to being in transit + for equal lengths of time. + """ reluctance: Reluctance + + """ + Walking preferences when walking a scooter. + """ scooterWalking: InputVehicleWalkPreferences + + """ + Maximum speed on flat ground while riding a scooter. Note, this speed is higher than + the average speed will be in itineraries as this is the maximum speed but there are + factors that slow down the travel such as crossings, intersections and elevation changes. + """ speed: Speed + + """ + What criteria should be used when optimizing a scooter route. + """ optimize: InputScooterOptimizationCriteria + + """ + Cost of boarding a vehicle with a scooter. + """ boardCost: Cost } @@ -4226,26 +4788,90 @@ input InputTimetablePreferences { includeRealtimeCancellations: Boolean = false } +""" +Preferences related to transfers between transit vehicles (typically between stops). +""" input InputTransferPreferences { + """ + A static cost that is added for each transfer on top of other costs. + """ costPenalty: Cost - minimumWaitingTime: Duration - maximumAdditionalTransfers: Int = 5 - maximumTransfers: Int = 12 + + """ + A global minimum transfer time (in seconds) that specifies the minimum amount of time + that must pass between exiting one transit vehicle and boarding another. This time is + in addition to time it might take to walk between transit stops. Setting this value + as `0s`, for example, can lead to passenger missing a connection when the vehicle leaves + ahead of time or the passenger arrives to the stop later than expected. + Invariant: `board slack + alight slack <= transfer minimum total slack` + """ + minimumTotalSlack: Duration + + """ + How many additional transfers there can be at maximum compared to the itinerary with the + least number of transfers. + """ + maximumAdditionalTransfers: Int + + """ + How many transfers there can be at maximum in an itinerary. + """ + maximumTransfers: Int } +""" +Transit mode and a reluctance associated with it. +""" input InputTransitModePreferences { + """ + Transit mode that could be (but doesn't have to be) used in an itinerary. + """ mode: TransitMode! + + """ + Reluctance in a format of a cost function of this transit mode compared to an ideal transit + mode. String must be of the format: `A + B x`, where A is fixed cost and B is a multiplier + of time x. For example `30 + 2.0 x`, where 30 cost units would be added per leg that + uses this transit mode and the cost of travel for each second would be multiplied by 2. + Either `A` or `B x` can be omitted. + """ reluctance: CostFunction } +""" +Transit routing preferences used for transit legs. +""" input InputTransitPreferences { """TODO should we reuse this or make a new design""" unpreferred: InputUnpreferred + + """ + Preferences related to boarding a transit vehicle. Note, board costs for each street mode + can be found under the street mode preferences. + """ board: InputBoardPreferences + + """ + Preferences related to alighting from a transit vehicle. + """ alight: InputAlightPreferences + + """ + Preferences related to transfers between transit vehicles (typically between stops). + """ transfer: InputTransferPreferences + + """ + Preferences related to cancellations and realtime. + """ timetable: InputTimetablePreferences - modes: [InputTransitModePreference!] + + """ + Transit modes and reluctancies associated with them. Each defined mode can be used in + an itinerary but doesn't have to be. If a direct mode is set under street preferences, + an itinerary without any transit modes can be returned. + """ + modes: [InputTransitModePreference!]! } """Text with language""" @@ -4458,12 +5084,19 @@ enum VertexType { PARKANDRIDE } +""" +Preferences for walking a vehicle (scooter or bicycle). +""" input InputVehicleWalkPreferences { - """Speed when walking a vehicle.""" + """ + Maximum walk speed on flat ground. Note, this speed is higher than the average speed + will be in itineraries as this is the maximum speed but there are + factors that slow down walking such as crossings, intersections and elevation changes. + """ speed: Speed """ - Reluctance in a format of `30s + 2.0 x` where the static cost is applied + Reluctance in a format of `30 + 2.0 x` where the static cost is applied both when getting off the vehicle and when getting on the vehicle again. However, these static costs are not applied when getting on a rented vehicle for the first time or off the vehicle when returning the vehicle. The multiplier @@ -4480,10 +5113,31 @@ input InputVehicleWalkPreferences { hopOnOrOffTimeSeconds: Int } +""" +Preferences related to walking (excluding walking a bicycle or a scooter). +""" input InputWalkPreferences { + """ + Maximum walk speed on flat ground. Note, this speed is higher than the average speed + will be in itineraries as this is the maximum speed but there are + factors that slow down walking such as crossings, intersections and elevation changes. + """ speed: Speed + + """ + A multiplier for how bad walking is compared to being in transit for equal lengths of time. + """ reluctance: Reluctance + + """ + Factor for how much the walk safety is considered in routing. Value should be between 0 and 1. + If the value is set to be 0, safety is ignored. + """ walkSafetyFactor: WeightingFactor + + """ + The cost of boarding a vehicle while walking. + """ boardCost: Cost } @@ -4503,6 +5157,14 @@ enum WheelchairBoarding { NOT_POSSIBLE } +""" +Wheelchair related preferences. Note, this is the only from of accessibilty available +currently and is sometimes is used for other accessibility needs as well. +""" input InputWheelchairPreferences { + """ + Is wheelchair accessibility considered in routing. Note, this does not guarantee + that the itineraries are wheelchair accessible as there can be data issues. + """ enabled: Boolean } From 907b156f1e933f362e67fc9282cc0c3c853409a3 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 20 Oct 2023 11:44:01 +0300 Subject: [PATCH 034/165] Make new input type for unpreferred --- .../opentripplanner/apis/gtfs/schema.graphqls | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 41fd1d1dd23..9a65808a328 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1603,6 +1603,26 @@ input InputUnpreferred { useUnpreferredRoutesPenalty: Int @deprecated(reason: "Use unpreferredCost instead") } +""" +Unpreferred entities and an extra cost attached to using them in routing. +""" +input InputPlanUnpreferred { + """List of feedscoped route IDs unpreferred by the user.""" + routes: [FeedScopedId!] + + """List of feedscoped agency IDs unpreferred by the user.""" + agencies: [FeedScopedId!] + + """ + An cost function used to calculate penalty for an unpreferred route/agency. Function should return + number of seconds that we are willing to wait for unpreferred route/agency. + String must be of the format: + `A + B x`, where A is fixed penalty and B is a multiplier of transit leg travel time x. + For example: `600 + 2.0 x` + """ + unpreferredCost: CostFunction +} + enum RoutingErrorCode { """ No transit connection was found between the origin and destination within the operating day or @@ -4902,8 +4922,10 @@ input InputTransitModePreferences { Transit routing preferences used for transit legs. """ input InputTransitPreferences { - """TODO should we reuse this or make a new design""" - unpreferred: InputUnpreferred + """ + Unprefer routes or agencies by giving them extra cost. + """ + unpreferred: InputPlanUnpreferred """ Preferences related to boarding a transit vehicle. Note, board costs for each street mode From b4addbd7709cf367e0a191a7209c9478c91e80f0 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 20 Oct 2023 11:48:55 +0300 Subject: [PATCH 035/165] Create own enum for input parameter so ANY mode can be added --- .../opentripplanner/apis/gtfs/schema.graphqls | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 9a65808a328..838a889d11a 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2313,6 +2313,66 @@ enum TransitMode { MONORAIL } +""" +Transit modes include modes that are used within organized transportation networks +run by public transportation authorities, taxi companies etc. +Equivalent to GTFS route_type or to NeTEx TransportMode with an addition of `ANY` +mode that enables all modes. +""" +enum PlanTransitMode { + AIRPLANE + + """ + Selecting this enables all modes to usable in routing. + """ + ANY + + """ + Most busses or coaches use this mode but sometimes the other bus-like modes such + `COACH` or `TROLLEYBUS` are used. + """ + BUS + + CABLE_CAR + + """ + Sometimes coaches use this mode but sometimes they are modelled to use `BUS` mode + instead. + """ + COACH + + FERRY + + FUNICULAR + + GONDOLA + + """ + This includes long or short distance trains. Tram, subway, monorail and funicular have + their own modes but it's possible that sometimes they use this mode instead. + """ + RAIL + + """ + Subway or metro, depending on the local terminology. + """ + SUBWAY + + TRAM + + """Private car trips shared with others.""" + CARPOOL + + """A taxi, possibly operated by a public transport agency.""" + TAXI + + "Electric buses that draw power from overhead wires using poles." + TROLLEYBUS + + "Railway in which the track consists of a single rail or a beam." + MONORAIL +} + """An object with an ID""" interface Node { """The ID of an object""" @@ -4906,7 +4966,7 @@ input InputTransitModePreferences { """ Transit mode that could be (but doesn't have to be) used in an itinerary. """ - mode: TransitMode! + mode: PlanTransitMode! """ Reluctance in a format of a cost function of this transit mode compared to an ideal transit From c73477501ada1fa94d03959df5a1eedd534ae545 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 20 Oct 2023 12:07:36 +0300 Subject: [PATCH 036/165] Split access and egress modes and improve documentation --- .../opentripplanner/apis/gtfs/schema.graphqls | 115 ++++++++++++++---- 1 file changed, 91 insertions(+), 24 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 838a889d11a..a917fc86f0f 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1466,13 +1466,13 @@ input InputPlanStreetModes { Street mode that is used when searching for access to transit network from origin. Selection of only one allowed for now. """ - access: [PlanAccessEgressMode!] + access: [PlanAccessMode!] """ Street mode that is used when searching for egress to destination from transit network. Selection of only one allowed for now. """ - egress: [PlanAccessEgressMode!] + egress: [PlanEgressMode!] """ Street mode that is used when searching for transfers. @@ -2417,21 +2417,21 @@ type PageInfo { } """ -Street modes that can be used for access to transit network or for -egress from transit network to destination. +Street modes that can be used for access to transit network from origin. """ -enum PlanAccessEgressMode { +enum PlanAccessMode { """ - Cycling to or from a stop and boarding a vehicle with the bicycle. + Cycling to a stop and boarding a vehicle with the bicycle. Note, this can include walking when it's needed to walk the bicycle. - Access and egress can use cycling only if the mode used for transfers - is also cycling. + Access can use cycling only if the mode used for transfers + and egress is also `BICYCLE`. """ BICYCLE """ Bicycle rental can use either station based systems or "floating" - vehicles which are not linked to a rental station. Note, this + vehicles which are not linked to a rental station. Note, if there are no + rental options available, access will include only walking. Also, this can include walking before picking up or after dropping off the bicycle or when it's needed to walk the bicycle. """ @@ -2446,7 +2446,8 @@ enum PlanAccessEgressMode { """ Car rental can use either station based systems or "floating" - vehicles which are not linked to a rental station. Note, this + vehicles which are not linked to a rental station. Note, if there are no + rental options available, access will include only walking. Also, this can include walking before picking up or after dropping off the car. """ @@ -2458,12 +2459,6 @@ enum PlanAccessEgressMode { """ CAR_PARKING - """ - Getting picked up by a car from a location that is accessible with a car. - Note, this can include walking before the pickup. - """ - CAR_PICKUP - """ Getting dropped off by a car to a location that is accessible with a car. Note, this can include walking after the drop-off. @@ -2478,25 +2473,30 @@ enum PlanAccessEgressMode { FLEX """ - Riding a scooter to or from a stop and boarding a vehicle with the scooter. - Note, this can include walking when it's needed to walk the scooter. + Riding a scooter to a stop and boarding a vehicle with the scooter. + Note, this can include walking when it's needed to walk the scooter. Access + can use scooter only if the mode used for transfers and egress is also `SCOOTER`. """ SCOOTER """ Scooter rental can use either station based systems or "floating" - vehicles which are not linked to a rental station. Note, this + vehicles which are not linked to a rental station. Note, if there are no + rental options available, access will include only walking. Also, this can include walking before picking up or after dropping off the scooter or when it's needed to walk the scooter. """ SCOOTER_RENTAL """ - Walking to or from a stop. + Walking to a stop. """ WALK } +""" +Street mode that is used when searching for itineraries that don't use any transit. +""" enum PlanDirectMode { """ Cycling from the origin to the destination. Note, this can include walking @@ -2506,8 +2506,9 @@ enum PlanDirectMode { """ Bicycle rental can use either station based systems or "floating" - vehicles which are not linked to a rental station. Note, this - can include walking before picking up or after dropping off the + vehicles which are not linked to a rental station. Note, if there are no + rental options available, itinerary will include only walking. + Also, it can include walking before picking up or after dropping off the bicycle or when it's needed to walk the bicycle. """ BICYCLE_RENTAL @@ -2526,7 +2527,8 @@ enum PlanDirectMode { """ Car rental can use either station based systems or "floating" - vehicles which are not linked to a rental station. Note, this + vehicles which are not linked to a rental station. Note, if there are no + rental options available, itinerary will include only walking. Also, this can include walking before picking up or after dropping off the car. """ @@ -2553,7 +2555,8 @@ enum PlanDirectMode { """ Scooter rental can use either station based systems or "floating" - vehicles which are not linked to a rental station. Note, this + vehicles which are not linked to a rental station. Note, if there are no + rental options available, itinerary will include only walking. Also, this can include walking before picking up or after dropping off the scooter or when it's needed to walk the scooter. """ @@ -2566,6 +2569,70 @@ enum PlanDirectMode { WALK } +""" +Street modes that can be used for egress from transit network to destination. +""" +enum PlanEgressMode { + """ + Cycling from a stop to the destination. Note, this can include walking when + it's needed to walk the bicycle. Egress can use cycling only if the mode used + for access and transfers is also `BICYCLE`. + """ + BICYCLE + + """ + Bicycle rental can use either station based systems or "floating" + vehicles which are not linked to a rental station. Note, if there are no + rental options available, egress will include only walking. Also, this + can include walking before picking up or after dropping off the + bicycle or when it's needed to walk the bicycle. + """ + BICYCLE_RENTAL + + """ + Car rental can use either station based systems or "floating" + vehicles which are not linked to a rental station. Note, if there are no + rental options available, egress will include only walking. Also, this + can include walking before picking up or after dropping off the + car. + """ + CAR_RENTAL + + """ + Getting picked up by a car from a location that is accessible with a car. + Note, this can include walking before the pickup. + """ + CAR_PICKUP + + """ + Flexible transit. This can include different forms of flexible transit that + can be defined in GTFS-Flex or in Netex. Note, this can include walking before + or after the flexible transit leg. + """ + FLEX + + """ + Riding a scooter from a stop to the destination. Note, this can include walking + when it's needed to walk the scooter. Egress can use scooter only if the mode + used for transfers and egress is also `SCOOTER`. + """ + SCOOTER + + """ + Scooter rental can use either station based systems or "floating" + vehicles which are not linked to a rental station. Note, if there are no + rental options available, egress will include only walking. Also, this + can include walking before picking up or after dropping off the + scooter or when it's needed to walk the scooter. + """ + SCOOTER_RENTAL + + """ + Walking from a stop to the destination. + """ + WALK +} + enum PlanTransferMode { """ Cycling between transit vehicles (typically between stops). Note, this can From f53a20809e27de05c827b4be5c9977e8c5266bed Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 20 Oct 2023 12:17:07 +0300 Subject: [PATCH 037/165] Add another wrapper for location --- .../opentripplanner/apis/gtfs/schema.graphqls | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index a917fc86f0f..2c3791f8095 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1407,10 +1407,26 @@ input InputPlanDebugSettings { } """ -Plan location settings. Either coordinates or a stop should be defined. Label is optional -and can be used together with the other fields. +Plan location settings. Location must be set. Label is optional +and used for naming the location. """ -input InputPlanLocation { +input InputPlanLabeledLocation { + """ + A location that has to be used in an itinerary. + """ + location: InputPlanLocation! + + """ + A label that can be attached to the location. This label is then returned with the location + in the itineraries. + """ + label: String +} + +""" +Plan location. Either coordinates or a stop should be defined. +""" +input InputPlanLocation @oneOf { """ Coordinates of the location. Note, either coordinates or a stop should be defined. """ @@ -1422,12 +1438,6 @@ input InputPlanLocation { be defined. """ stop: FeedScopedId - - """ - A label that can be attached to the location. This label is then returned with the location - in the itineraries. - """ - label: String } """ @@ -4120,12 +4130,12 @@ type QueryType { """ The origin where the search starts. Usually coordinates but can also a be a stop. """ - origin: InputPlanLocation + origin: InputPlanLabeledLocation """ The destination where the search ends. Usually coordinates but can also a be a stop. """ - destination: InputPlanLocation + destination: InputPlanLabeledLocation """ Preferences that affect what itineraries are returned. Preferences are split into categories. From befb25006ed7993eb0482c4a678f7440d1bed1dd Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 20 Oct 2023 12:29:30 +0300 Subject: [PATCH 038/165] Add documentation for locale --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 2c3791f8095..bd66bd6487f 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1332,7 +1332,7 @@ input InputPreferred { } """ -TODO add documentation. +Locale in string format. For example, `en` or `en-US`. """ scalar Locale From df9b225ce0a74a3f1fa3556d835bfa91615bb9c5 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 20 Oct 2023 12:35:59 +0300 Subject: [PATCH 039/165] Remove oneOf directive as it should now be in graphql-java --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index bd66bd6487f..488e2d90fe6 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -8,13 +8,6 @@ This is only worth it when the execution is long running, i.e. more than ~50 mil """ directive @async on FIELD_DEFINITION -""" -Only one of the fields on an input type should be defined. - -This is used when input type parameter fields are alternatives and not meant to be combined together. -""" -directive @oneOf on FIELD_DEFINITION - schema { query: QueryType } From 76159f7682349640b2be7fb35bb33f8e6b102871 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 20 Oct 2023 13:58:03 +0300 Subject: [PATCH 040/165] Fix typo --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 488e2d90fe6..76e095ba8bd 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -5032,7 +5032,7 @@ input InputTransferPreferences { """ Transit mode and a reluctance associated with it. """ -input InputTransitModePreferences { +input InputTransitModePreference { """ Transit mode that could be (but doesn't have to be) used in an itinerary. """ From 8c3de15177c06702335219a2b2c13ac497da7880 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sun, 22 Oct 2023 01:13:03 +0300 Subject: [PATCH 041/165] Actually use input suffix in names --- .../opentripplanner/apis/gtfs/schema.graphqls | 120 +++++++++--------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 76e095ba8bd..b4696f36f39 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -16,11 +16,11 @@ schema { Plan accessibilty preferences. This can be expanded to contain preferences for various accessibility use cases in the future. Currently only generic wheelchair preferences are available. """ -input InputAccessibilityPreferences { +input AccessibilityPreferencesInput { """ Wheelchair related preferences. Note, currently this is the only accessibility mode that is available. """ - wheelchair: InputWheelchairPreferences + wheelchair: WheelchairPreferencesInput } """A public transport agency""" @@ -287,7 +287,7 @@ enum AlertSeverityLevelType { """ Preferences related to alighting from a transit vehicle. """ -input InputAlightPreferences { +input AlightPreferencesInput { """ What is the required minimum time alighting from a vehicle. Invariant: `board slack + alight slack <= transfer minimum total slack` @@ -783,7 +783,7 @@ enum BikesAllowed { """ Preferences related to travel with a bicycle. """ -input InputBicyclePreferences { +input BicyclePreferencesInput { """ A multiplier for how bad cycling is compared to being in transit for equal lengths of time. """ @@ -792,7 +792,7 @@ input InputBicyclePreferences { """ Walking preferences when walking a bicycle. """ - bicycleWalking: InputVehicleWalkPreferences + bicycleWalking: VehicleWalkPreferencesInput """ Maximum speed on flat ground while riding a scooter. Note, this speed is higher than @@ -804,7 +804,7 @@ input InputBicyclePreferences { """ What criteria should be used when optimizing a cycling route. """ - optimize: InputCyclingOptimizationCriteria + optimize: CyclingOptimizationCriteriaInput """ Cost of boarding a vehicle with a bicycle. @@ -816,7 +816,7 @@ input InputBicyclePreferences { Preferences related to boarding a transit vehicle. Note, board costs for each street mode can be found under the street mode preferences. """ -input InputBoardPreferences { +input BoardPreferencesInput { """ A multiplier for how bad waiting at a stop is compared to being in transit for equal lengths of time. """ @@ -878,7 +878,7 @@ type CarPark implements Node & PlaceInterface { """ Preferences related to traveling on a car (excluding car travel on transit services such as taxi). """ -input InputCarPreferences { +input CarPreferencesInput { """ A multiplier for how bad travelling on car is compared to being in transit for equal lengths of time. """ @@ -942,7 +942,7 @@ type Coordinates { """ Plan date time options. Only one of the values should be defined. """ -input InputPlanDateTime @oneOf { +input PlanDateTimeInput @oneOf { """ Earliest departure date time. The returned itineraries should not depart before this instant unless one is using paging to find earlier @@ -963,7 +963,7 @@ input InputPlanDateTime @oneOf { """ Search window for the plan search. """ -input InputSearchWindow { +input SearchWindowInput { """ Duration of the search window. This either starts at the defined earliest departure time or ends at the latest arrival time. If this is not provided, a reasonable @@ -988,7 +988,7 @@ input InputSearchWindow { departure time or after the defined latest arrival time) for some which some itineraries can be returned. """ - gracePeriod: InputSearchWindowGracePeriod @deprecated(reason: "Not yet implemented.") + gracePeriod: SearchWindowGracePeriodInput @deprecated(reason: "Not yet implemented.") } """ @@ -996,7 +996,7 @@ Defines a grace period outside of the normal search window (either before the de departure time or after the defined latest arrival time) for some which some itineraries can be returned. """ -input InputSearchWindowGracePeriod { +input SearchWindowGracePeriodInput { """ Duration of the grace period. Either ending when the normal search window starts if earliest departure time is defined, or after the latest arrival time. @@ -1190,7 +1190,7 @@ type StopGeometries { """ A coordinate pair used for a location in a plan query. """ -input InputPlanCoordinates { +input PlanCoordinatesInput { """ Latitude as a WGS 84 format number. """ @@ -1392,7 +1392,7 @@ input ParkingFilter { """ Settings related to debugging why certain itineraries are returned. """ -input InputPlanDebugSettings { +input PlanDebugSettingsInput { """ Itinerary filter debug profile used to control the behaviour of itinerary filters. """ @@ -1403,11 +1403,11 @@ input InputPlanDebugSettings { Plan location settings. Location must be set. Label is optional and used for naming the location. """ -input InputPlanLabeledLocation { +input PlanLabeledLocationInput { """ A location that has to be used in an itinerary. """ - location: InputPlanLocation! + location: PlanLocationInput! """ A label that can be attached to the location. This label is then returned with the location @@ -1419,11 +1419,11 @@ input InputPlanLabeledLocation { """ Plan location. Either coordinates or a stop should be defined. """ -input InputPlanLocation @oneOf { +input PlanLocationInput @oneOf { """ Coordinates of the location. Note, either coordinates or a stop should be defined. """ - coordinates: InputPlanCoordinates + coordinates: PlanCoordinatesInput """ Stop that should be used as a location for the search. The expected value is the stop's @@ -1436,29 +1436,29 @@ input InputPlanLocation @oneOf { """ Wrapper type for different types of preferences related to plan query. """ -input InputPlanPreferences { +input PlanPreferencesInput { """ Street routing preferences used for ingress, egress and transfers. These do not directly affect the transit legs but can change how preferable walking or cycling, for example, is compared to transit. """ - street: InputPlanStreetPreferences + street: PlanStreetPreferencesInput """ Transit routing preferences used for transit legs. """ - transit: InputTransitPreferences + transit: TransitPreferencesInput """ Accessibility preferences that affect both the street and transit routing. """ - accessibility: InputAccessibilityPreferences + accessibility: AccessibilityPreferencesInput } """ Street mode options for different phases of an itinerary. """ -input InputPlanStreetModes { +input PlanStreetModesInput { """ Street mode that is used when searching for itineraries that don't use any transit. Selection of only one allowed for now. @@ -1489,38 +1489,38 @@ Street routing preferences used for ingress, egress and transfers. These do not the transit legs but can change how preferable walking or cycling, for example, is compared to transit. """ -input InputPlanStreetPreferences { +input PlanStreetPreferencesInput { """ Which modes of travel can be used for street routing at different stages. """ - modes: InputPlanStreetModes + modes: PlanStreetModesInput """ Cycling related preferences. """ - bicycle: InputBicyclePreferences + bicycle: BicyclePreferencesInput """ Scooter (kick or electrical) related preferences. """ - scooter: InputScooterPreferences + scooter: ScooterPreferencesInput """ Car related preferences. These are not used for car travel as part of transit, such as taxi travel. """ - car: InputCarPreferences + car: CarPreferencesInput """ Walk related preferences. These are not used when walking a bicycle or a scooter as they have their own preferences. """ - walk: InputWalkPreferences + walk: WalkPreferencesInput """ Rental related preferences. """ - rental: InputRentalPreferences + rental: RentalPreferencesInput """ Parking related preferences. @@ -1547,7 +1547,7 @@ input InputTriangle { Relative importances of optimization factors. Only effective for bicycling legs. Invariant: `cyclingSafety + flatness + time == 1` """ -input InputTriangleCyclingFactors { +input TriangleCyclingFactorsInput { """ Relative importance of cycling safety, but this factor can also include other concerns such as convenience and general cyclist preferences by taking into account @@ -1566,7 +1566,7 @@ input InputTriangleCyclingFactors { Relative importances of optimization factors. Only effective for scooter legs. Invariant: `scooterSafety + flatness + time == 1` """ -input InputTriangleScooterFactors { +input TriangleScooterFactorsInput { """ Relative importance of scooter safety, but this factor can also include other concerns such as convenience and general scooter preferences by taking into account @@ -1609,7 +1609,7 @@ input InputUnpreferred { """ Unpreferred entities and an extra cost attached to using them in routing. """ -input InputPlanUnpreferred { +input PlanUnpreferredInput { """List of feedscoped route IDs unpreferred by the user.""" routes: [FeedScopedId!] @@ -2873,7 +2873,7 @@ type BookingInfo { """ What criteria should be used when optimizing a cycling route. """ -input InputCyclingOptimizationCriteria @oneOf { +input CyclingOptimizationCriteriaInput @oneOf { """ Use one of the predefined optimization types. """ @@ -2882,7 +2882,7 @@ input InputCyclingOptimizationCriteria @oneOf { """ Define optimization by weighing three criteria. """ - triangle: InputTriangleCyclingFactors + triangle: TriangleCyclingFactorsInput } """ @@ -2922,7 +2922,7 @@ enum CyclingOptimizationType { """ What criteria should be used when optimizing a scooter route. """ -input InputScooterOptimizationCriteria @oneOf { +input ScooterOptimizationCriteriaInput @oneOf { """ Use one of the predefined optimization types. """ @@ -2931,7 +2931,7 @@ input InputScooterOptimizationCriteria @oneOf { """ Define optimization by weighing three criteria. """ - triangle: InputTriangleScooterFactors + triangle: TriangleScooterFactorsInput } """ @@ -4113,32 +4113,32 @@ type QueryType { Datetime of the search. It's possible to either define the earliest departure time or the latest arrival time. """ - dateTime: InputPlanDateTime + dateTime: PlanDateTimeInput """ The search window(s) of the search. It also allows to limit how many itineraries are returned. """ - searchWindow: InputSearchWindow + searchWindow: SearchWindowInput """ The origin where the search starts. Usually coordinates but can also a be a stop. """ - origin: InputPlanLabeledLocation + origin: PlanLabeledLocationInput """ The destination where the search ends. Usually coordinates but can also a be a stop. """ - destination: InputPlanLabeledLocation + destination: PlanLabeledLocationInput """ Preferences that affect what itineraries are returned. Preferences are split into categories. """ - preferences: InputPlanPreferences + preferences: PlanPreferencesInput """ Settings meant for debugging purposes that control the search and the returned itineraries. """ - debugSettings: InputPlanDebugSettings + debugSettings: PlanDebugSettingsInput """ Locale used for translations. Note, there might not necessarily be translations available. @@ -4219,7 +4219,7 @@ scalar Reluctance """ Preferences related to rental (station based or floating vehicle rental). """ -input InputRentalPreferences { +input RentalPreferencesInput { """ Is it possible to arrive to the destination without dropping off the rental vehicle first. @@ -4480,7 +4480,7 @@ enum OccupancyStatus { """ Preferences related to travel with a scooter (kick or e-scooter). """ -input InputScooterPreferences { +input ScooterPreferencesInput { """ A multiplier for how bad riding a scooter is compared to being in transit for equal lengths of time. @@ -4490,7 +4490,7 @@ input InputScooterPreferences { """ Walking preferences when walking a scooter. """ - scooterWalking: InputVehicleWalkPreferences + scooterWalking: VehicleWalkPreferencesInput """ Maximum speed on flat ground while riding a scooter. Note, this speed is higher than @@ -4502,7 +4502,7 @@ input InputScooterPreferences { """ What criteria should be used when optimizing a scooter route. """ - optimize: InputScooterOptimizationCriteria + optimize: ScooterOptimizationCriteriaInput """ Cost of boarding a vehicle with a scooter. @@ -4970,7 +4970,7 @@ type TicketType implements Node { zones: [String!] } -input InputTimetablePreferences { +input TimetablePreferencesInput { """ When true, realtime updates are considered during the routing. In practice, when this option is set as false, some of the suggestions might not be @@ -5001,7 +5001,7 @@ input InputTimetablePreferences { """ Preferences related to transfers between transit vehicles (typically between stops). """ -input InputTransferPreferences { +input TransferPreferencesInput { """ A static cost that is added for each transfer on top of other costs. """ @@ -5032,7 +5032,7 @@ input InputTransferPreferences { """ Transit mode and a reluctance associated with it. """ -input InputTransitModePreference { +input TransitModePreferenceInput { """ Transit mode that could be (but doesn't have to be) used in an itinerary. """ @@ -5051,39 +5051,39 @@ input InputTransitModePreference { """ Transit routing preferences used for transit legs. """ -input InputTransitPreferences { +input TransitPreferencesInput { """ Unprefer routes or agencies by giving them extra cost. """ - unpreferred: InputPlanUnpreferred + unpreferred: PlanUnpreferredInput """ Preferences related to boarding a transit vehicle. Note, board costs for each street mode can be found under the street mode preferences. """ - board: InputBoardPreferences + board: BoardPreferencesInput """ Preferences related to alighting from a transit vehicle. """ - alight: InputAlightPreferences + alight: AlightPreferencesInput """ Preferences related to transfers between transit vehicles (typically between stops). """ - transfer: InputTransferPreferences + transfer: TransferPreferencesInput """ Preferences related to cancellations and realtime. """ - timetable: InputTimetablePreferences + timetable: TimetablePreferencesInput """ Transit modes and reluctancies associated with them. Each defined mode can be used in an itinerary but doesn't have to be. If a direct mode is set under street preferences, an itinerary without any transit modes can be returned. """ - modes: [InputTransitModePreference!]! + modes: [TransitModePreferenceInput!]! } """Text with language""" @@ -5316,7 +5316,7 @@ enum VertexType { """ Preferences for walking a vehicle (scooter or bicycle). """ -input InputVehicleWalkPreferences { +input VehicleWalkPreferencesInput { """ Maximum walk speed on flat ground. Note, this speed is higher than the average speed will be in itineraries as this is the maximum speed but there are @@ -5345,7 +5345,7 @@ input InputVehicleWalkPreferences { """ Preferences related to walking (excluding walking a bicycle or a scooter). """ -input InputWalkPreferences { +input WalkPreferencesInput { """ Maximum walk speed on flat ground. Note, this speed is higher than the average speed will be in itineraries as this is the maximum speed but there are @@ -5390,7 +5390,7 @@ enum WheelchairBoarding { Wheelchair related preferences. Note, this is the only from of accessibilty available currently and is sometimes is used for other accessibility needs as well. """ -input InputWheelchairPreferences { +input WheelchairPreferencesInput { """ Is wheelchair accessibility considered in routing. Note, this does not guarantee that the itineraries are wheelchair accessible as there can be data issues. From 18854884de340bcb19334e6f4a9a8e8bef537fd0 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 23 Oct 2023 00:17:30 +0300 Subject: [PATCH 042/165] Fix examples --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index b4696f36f39..8d0b082e0a8 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -823,7 +823,7 @@ input BoardPreferencesInput { waitReluctance: Reluctance """ - What is the required minimum waiting time at a stop. Setting this value as `0s`, for example, can lead + What is the required minimum waiting time at a stop. Setting this value as `PT0S`, for example, can lead to passenger missing a connection when the vehicle leaves ahead of time or the passenger arrives to the stop later than expected. Invariant: `board slack + alight slack <= transfer minimum total slack` @@ -5011,7 +5011,7 @@ input TransferPreferencesInput { A global minimum transfer time (in seconds) that specifies the minimum amount of time that must pass between exiting one transit vehicle and boarding another. This time is in addition to time it might take to walk between transit stops. Setting this value - as `0s`, for example, can lead to passenger missing a connection when the vehicle leaves + as `PT0S`, for example, can lead to passenger missing a connection when the vehicle leaves ahead of time or the passenger arrives to the stop later than expected. Invariant: `board slack + alight slack <= transfer minimum total slack` """ From 551cc319307ec2af254b649247311443cadbf7f7 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 23 Oct 2023 00:32:21 +0300 Subject: [PATCH 043/165] Make locations mandatory --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 8d0b082e0a8..c93702dac6a 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3176,7 +3176,7 @@ type PlanConnection { """ Origin and destination of the search. """ - locations: PlanPlaces + locations: PlanPlaces! """ Errors faced during the routing search. From d75464b76cbe6792f6b85c950f67febeaf227502 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 23 Oct 2023 22:10:29 +0300 Subject: [PATCH 044/165] Rename DateTimeWithOffset -> OffsetDateTime --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index c93702dac6a..3df9e38b042 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -949,7 +949,7 @@ input PlanDateTimeInput @oneOf { itineraries. Note, it is not currently possible to define both `earliestDeparture` and `latestArrival`. """ - earliestDeparture: DateTimeWithOffset + earliestDeparture: OffsetDateTime """ Latest arrival time date time. The returned itineraries should not @@ -957,7 +957,7 @@ input PlanDateTimeInput @oneOf { paging to find later itineraries. Note, it is not currently possible to define both `earliestDeparture` and `latestArrival`. """ - latestArrival: DateTimeWithOffset + latestArrival: OffsetDateTime } """ @@ -1940,7 +1940,7 @@ type Money { """ An ISO-8601-formatted datetime with offset, i.e. `2023-06-13T14:30+03:00` for 2:30pm on June 13th 2023 at Helsinki's offset from UTC at that time. """ -scalar DateTimeWithOffset +scalar OffsetDateTime """ An ISO-8601-formatted duration, i.e. `PT2H30M` for 2 hours and 30 minutes. @@ -3171,7 +3171,7 @@ type PlanConnection { """ What was the starting point for the itinerary search. """ - searchDateTime: DateTimeWithOffset + searchDateTime: OffsetDateTime """ Origin and destination of the search. From 154fa83573601a6656e1d63290523ab6defb0892 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 23 Oct 2023 22:23:41 +0300 Subject: [PATCH 045/165] Improve documentation --- .../opentripplanner/apis/gtfs/schema.graphqls | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 3df9e38b042..76a85276c75 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -927,7 +927,7 @@ on transit. Either `A` or `B x` can be omitted. scalar CostFunction """ -Either a latitude or a longitude coordinate as a WGS 84 format number. +Either a latitude or a longitude coordinate as a WGS 84 format floating point number. """ scalar Coordinate @@ -961,17 +961,26 @@ input PlanDateTimeInput @oneOf { } """ -Search window for the plan search. +Search window for the plan search. Search window duration limitation is done mainly for +performance reasons. It can make sense to search for more itineraries that what is immediately +needed if there is a possibility that more itineraries are used later on (limiting the number +of returned itineraries does not affect the performance by a lot but can affect the network +bandwidth usage). """ input SearchWindowInput { """ Duration of the search window. This either starts at the defined earliest departure time or ends at the latest arrival time. If this is not provided, a reasonable - search window is automatically generated. When search for earlier or later itineraries + search window is automatically generated. When searching for earlier or later itineraries with paging, this search window is no longer used and the new window will be based on how many suggestions were returned in the previous search. The new search window can be shorter or longer than the original search window. Note, itineraries are returned faster with a smaller search window. + + Setting this parameter makes especially sense if the transportation network is as sparse or dense + in the whole area that can be searched for itineraries. Otherwise, letting the system decide what + is the search window is in combination of using paging can lead to better performance and to + getting a more consistent number of itineraries in each search. """ duration: Duration @@ -4111,7 +4120,7 @@ type QueryType { planConnection( """ Datetime of the search. It's possible to either define the earliest departure time - or the latest arrival time. + or the latest arrival time. By default, earliest departure time is set as now. """ dateTime: PlanDateTimeInput From a2c0391da3d73571c01ae1998651994bd4fa9259 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 23 Oct 2023 22:30:12 +0300 Subject: [PATCH 046/165] Remove grace period, move search window fields to top level --- .../opentripplanner/apis/gtfs/schema.graphqls | 83 +++++-------------- 1 file changed, 23 insertions(+), 60 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 76a85276c75..1d5b2faabd9 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -960,64 +960,6 @@ input PlanDateTimeInput @oneOf { latestArrival: OffsetDateTime } -""" -Search window for the plan search. Search window duration limitation is done mainly for -performance reasons. It can make sense to search for more itineraries that what is immediately -needed if there is a possibility that more itineraries are used later on (limiting the number -of returned itineraries does not affect the performance by a lot but can affect the network -bandwidth usage). -""" -input SearchWindowInput { - """ - Duration of the search window. This either starts at the defined earliest departure - time or ends at the latest arrival time. If this is not provided, a reasonable - search window is automatically generated. When searching for earlier or later itineraries - with paging, this search window is no longer used and the new window will be based - on how many suggestions were returned in the previous search. The new search window can be - shorter or longer than the original search window. Note, itineraries are returned faster - with a smaller search window. - - Setting this parameter makes especially sense if the transportation network is as sparse or dense - in the whole area that can be searched for itineraries. Otherwise, letting the system decide what - is the search window is in combination of using paging can lead to better performance and to - getting a more consistent number of itineraries in each search. - """ - duration: Duration - - """ - Maximum number of itineraries that will be returned. It's possible that no itineraries are - found, paging for earlier or later itineraries can yield to results in that case. The returned - itineraries are either closest to the defined earliest departure time or to the latest arrival - time. - """ - numberOfItineraries: Int - - """ - Defines a grace period outside of the normal search window (either before the defined earliest - departure time or after the defined latest arrival time) for some which some itineraries can - be returned. - """ - gracePeriod: SearchWindowGracePeriodInput @deprecated(reason: "Not yet implemented.") -} - -""" -Defines a grace period outside of the normal search window (either before the defined earliest -departure time or after the defined latest arrival time) for some which some itineraries can -be returned. -""" -input SearchWindowGracePeriodInput { - """ - Duration of the grace period. Either ending when the normal search window starts if earliest - departure time is defined, or after the latest arrival time. - """ - duration: Duration - - """ - Number of itineraries that returned within the grace period. - """ - numberOfItineraries: Int -} - type debugOutput { totalTime: Long pathCalculationTime: Long @@ -4125,9 +4067,30 @@ type QueryType { dateTime: PlanDateTimeInput """ - The search window(s) of the search. It also allows to limit how many itineraries are returned. + Duration of the search window. This either starts at the defined earliest departure + time or ends at the latest arrival time. If this is not provided, a reasonable + search window is automatically generated. When searching for earlier or later itineraries + with paging, this search window is no longer used and the new window will be based + on how many suggestions were returned in the previous search. The new search window can be + shorter or longer than the original search window. Note, itineraries are returned faster + with a smaller search window and search window limitation is done mainly for performance reasons. + + Setting this parameter makes especially sense if the transportation network is as sparse or dense + in the whole itinerary search area. Otherwise, letting the system decide what is the search window + is in combination of using paging can lead to better performance and to getting a more consistent + number of itineraries in each search. + """ + searchWindow: Duration + + """ + Maximum number of itineraries that will be returned. It's possible that no itineraries are + found, paging for earlier or later itineraries can yield to results in that case. The returned + itineraries are either closest to the defined earliest departure time or to the latest arrival + time. It can make sense to search for more itineraries that what is immediately needed if there + is a possibility that more itineraries are used later on (limiting the number of returned itineraries + does not affect the performance by a lot but can affect the network bandwidth usage). """ - searchWindow: SearchWindowInput + numberOfItineraries: Int """ The origin where the search starts. Usually coordinates but can also a be a stop. From fbcf032efd5501d1da9148f5df8254c4e7eef099 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 23 Oct 2023 23:29:17 +0300 Subject: [PATCH 047/165] Redesign stop as a location --- .../opentripplanner/apis/gtfs/schema.graphqls | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 1d5b2faabd9..da28824d1e3 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1368,20 +1368,24 @@ input PlanLabeledLocationInput { } """ -Plan location. Either coordinates or a stop should be defined. +Plan location. Either coordinates or a stop location should be defined. """ input PlanLocationInput @oneOf { """ - Coordinates of the location. Note, either coordinates or a stop should be defined. + Coordinates of the location. Note, either coordinates or a stop location should be defined. """ coordinates: PlanCoordinatesInput """ - Stop that should be used as a location for the search. The expected value is the stop's - feedscoped GTFS ID, for example `feedId:stopId`. Note, either coordinates or a stop should - be defined. + Stop, station, a group of stop places or multimodal stop place that should be used as + a location for the search. The stop place can be used in two ways: + 1. As a location where one should depart from or arrive to on transit depending on if + its used as an origin or a destination + 2. As coordinates where the search can start or end but it's not required to use this stop + place for a direct transit connnection. + Note, either coordinates or a stop location should be defined. """ - stop: FeedScopedId + stopLocation: PlanStopLocationInput } """ @@ -1406,6 +1410,28 @@ input PlanPreferencesInput { accessibility: AccessibilityPreferencesInput } +""" +Stop, station, a group of stop places or multimodal stop place that should be used as +a location for the search. The stop place can be used in two ways: +1. As a location where one should depart from or arrive to on transit depending on if + its used as an origin or a destination +2. As coordinates where the search can start or end but it's not required to use this stop + place for a direct transit connnection. +""" +input PlanStopLocationInput { + """ + ID of the stop, station, a group of stop places or multimodal stop place. Format + should be `FeedId:StopLocationId`. + """ + stopLocationId: String + + """ + If set as true, one must either board or alight from this stop location. Otherwise, + it's optional and alternatively one can just walk to or from this location. + """ + transitConnectionRequired: Boolean +} + """ Street mode options for different phases of an itinerary. """ @@ -4093,12 +4119,12 @@ type QueryType { numberOfItineraries: Int """ - The origin where the search starts. Usually coordinates but can also a be a stop. + The origin where the search starts. Usually coordinates but can also be a stop location. """ origin: PlanLabeledLocationInput """ - The destination where the search ends. Usually coordinates but can also a be a stop. + The destination where the search ends. Usually coordinates but can also be a stop location. """ destination: PlanLabeledLocationInput From 373144f7d11939b2b43b82e28c18978975b66266 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 23 Oct 2023 23:32:11 +0300 Subject: [PATCH 048/165] Remove FeedScopedId scalar to leave room for unscoped ids --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index da28824d1e3..1163a5156a7 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1587,11 +1587,11 @@ input InputUnpreferred { Unpreferred entities and an extra cost attached to using them in routing. """ input PlanUnpreferredInput { - """List of feedscoped route IDs unpreferred by the user.""" - routes: [FeedScopedId!] + """List of feedscoped route IDs (format is `FeedId:RouteId`) unpreferred by the user.""" + routes: [String!] - """List of feedscoped agency IDs unpreferred by the user.""" - agencies: [FeedScopedId!] + """List of feedscoped agency IDs (format is `FeedId:AgencyId`) unpreferred by the user.""" + agencies: [String!] """ An cost function used to calculate penalty for an unpreferred route/agency. Function should return @@ -1929,12 +1929,6 @@ String representation of a duration in seconds. Examples of the format: `300s`, """ scalar DurationInSeconds -""" -Feedscoped ID. These follow the format of `feed:entityId`, The feedId should be unique system or a data set identifier -and the entityId should be unique within that system or data set. -""" -scalar FeedScopedId - type RideHailingProvider { "The ID of the ride hailing provider." id: String! From 10b7cca7ec9ca33ccebe57b88a1090fff5feb2de Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 24 Oct 2023 16:01:23 +0300 Subject: [PATCH 049/165] Do minor renaming --- .../opentripplanner/apis/gtfs/schema.graphqls | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 1163a5156a7..f60de309f7b 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -791,11 +791,12 @@ input BicyclePreferencesInput { """ Walking preferences when walking a bicycle. + TODO: review with @t2gran """ bicycleWalking: VehicleWalkPreferencesInput """ - Maximum speed on flat ground while riding a scooter. Note, this speed is higher than + Maximum speed on flat ground while riding a bicycle. Note, this speed is higher than the average speed will be in itineraries as this is the maximum speed but there are factors that slow down cycling such as crossings, intersections and elevation changes. """ @@ -804,7 +805,7 @@ input BicyclePreferencesInput { """ What criteria should be used when optimizing a cycling route. """ - optimize: CyclingOptimizationCriteriaInput + optimization: CyclingOptimizationCriteriaInput """ Cost of boarding a vehicle with a bicycle. @@ -1521,8 +1522,8 @@ input InputTriangle { } """ -Relative importances of optimization factors. Only effective for bicycling legs. -Invariant: `cyclingSafety + flatness + time == 1` +Relative importance of optimization factors. Only effective for bicycling legs. +Invariant: `safety + flatness + time == 1` """ input TriangleCyclingFactorsInput { """ @@ -1530,7 +1531,7 @@ input TriangleCyclingFactorsInput { concerns such as convenience and general cyclist preferences by taking into account road surface etc. """ - cyclingSafety: WeightingFactor + safety: WeightingFactor """Relative importance of flat terrain""" flatness: WeightingFactor @@ -1540,8 +1541,8 @@ input TriangleCyclingFactorsInput { } """ -Relative importances of optimization factors. Only effective for scooter legs. -Invariant: `scooterSafety + flatness + time == 1` +Relative importance of optimization factors. Only effective for scooter legs. +Invariant: `safety + flatness + time == 1` """ input TriangleScooterFactorsInput { """ @@ -1549,7 +1550,7 @@ input TriangleScooterFactorsInput { concerns such as convenience and general scooter preferences by taking into account road surface etc. """ - scooterSafety: WeightingFactor + safety: WeightingFactor """Relative importance of flat terrain""" flatness: WeightingFactor @@ -2872,7 +2873,7 @@ enum CyclingOptimizationType { """ Emphasize flatness over safety or duration of the route. This option was previously called `FLAT`. """ - FLATTEST_STREETS + FLAT_STREETS """ Emphasize cycling safety over flatness or duration of the route. Safety can also include other @@ -2922,7 +2923,7 @@ enum ScooterOptimizationType { """ Emphasize flatness over safety or duration of the route. This option was previously called `FLAT`. """ - FLATTEST_STREETS + FLAT_STREETS """ Emphasize scooter safety over flatness or duration of the route. Safety can also include other @@ -4494,7 +4495,7 @@ input ScooterPreferencesInput { """ What criteria should be used when optimizing a scooter route. """ - optimize: ScooterOptimizationCriteriaInput + optimization: ScooterOptimizationCriteriaInput """ Cost of boarding a vehicle with a scooter. @@ -5331,7 +5332,7 @@ input VehicleWalkPreferencesInput { or when getting on the vehicle again. However, this is not applied when getting on a rented vehicle for the first time or off the vehicle when returning the vehicle. """ - hopOnOrOffTimeSeconds: Int + hopOnOrOffTime: Duration } """ From 4bac478578f379d0e3084c7d6ef43e8901588a08 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 2 Nov 2023 15:06:11 +0200 Subject: [PATCH 050/165] Use Duration instead of DurationInSeconds and update reluctance doc --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index f60de309f7b..a6b8400536e 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1925,11 +1925,6 @@ An ISO-8601-formatted duration, i.e. `PT2H30M` for 2 hours and 30 minutes. """ scalar Duration -""" -String representation of a duration in seconds. Examples of the format: `300s`, `1s`, `2.5s`, `3.14s` or `15.233s`. -""" -scalar DurationInSeconds - type RideHailingProvider { "The ID of the ride hailing provider." id: String! @@ -3180,12 +3175,12 @@ type PlanDebugInformation { """ Total time taken for the route request. """ - totalTime: DurationInSeconds + totalTime: Duration """ Time taken in the transit router (including access/egress street router). """ - pathCalculationTime: DurationInSeconds + pathCalculationTime: Duration """ The search window that was used for the search. @@ -4205,7 +4200,7 @@ enum RealtimeState { """ A cost multiplier for how bad something is compared to being in transit for equal lengths of time. -Value should be a float and bigger than equal to 1.0. +The value should be over 0. """ scalar Reluctance From 8c1450a2358eaa0df35932d2da5283e129bfa17b Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 2 Nov 2023 15:06:50 +0200 Subject: [PATCH 051/165] Use cost instead of cost penalty in naming --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index a6b8400536e..6ee1a31df98 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4993,7 +4993,7 @@ input TransferPreferencesInput { """ A static cost that is added for each transfer on top of other costs. """ - costPenalty: Cost + cost: Cost """ A global minimum transfer time (in seconds) that specifies the minimum amount of time From 4beccfeef2ac3a8049fded01d2ac3cb85358494f Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 2 Nov 2023 15:38:21 +0200 Subject: [PATCH 052/165] Remove CostFunction --- .../opentripplanner/apis/gtfs/schema.graphqls | 81 ++++++++++++------- 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 6ee1a31df98..de4b2eea149 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -919,14 +919,6 @@ for example `450`. One cost unit should roughly match a one second travel on tra """ scalar Cost -""" -A cost function that is applied to a certain event. String must be of the format: -`A + B x`, where A is fixed cost and B is a multiplier of time x. For example: `600 + 2.0 x`. -Positive numbers should be used. One cost unit should roughly match a one second travel -on transit. Either `A` or `B x` can be omitted. -""" -scalar CostFunction - """ Either a latitude or a longitude coordinate as a WGS 84 format floating point number. """ @@ -1595,13 +1587,24 @@ input PlanUnpreferredInput { agencies: [String!] """ - An cost function used to calculate penalty for an unpreferred route/agency. Function should return - number of seconds that we are willing to wait for unpreferred route/agency. - String must be of the format: - `A + B x`, where A is fixed penalty and B is a multiplier of transit leg travel time x. - For example: `600 + 2.0 x` + Costs related to using unpreferred entities. + """ + cost: PlanUnpreferredCostInput +} + +""" +Costs related to using unpreferred entities. +""" +input PlanUnpreferredCostInput { + """ + A static cost that is added each time something that is unpreferred is used. + """ + cost: Cost + + """ + A cost multiplier of transit leg travel time. """ - unpreferredCost: CostFunction + reluctance: Reluctance } enum RoutingErrorCode { @@ -5027,13 +5030,24 @@ input TransitModePreferenceInput { mode: PlanTransitMode! """ - Reluctance in a format of a cost function of this transit mode compared to an ideal transit - mode. String must be of the format: `A + B x`, where A is fixed cost and B is a multiplier - of time x. For example `30 + 2.0 x`, where 30 cost units would be added per leg that - uses this transit mode and the cost of travel for each second would be multiplied by 2. - Either `A` or `B x` can be omitted. + Costs related to using a transit mode. + """ + cost: TransitModePreferenceCostInput +} + +""" +Costs related to using a transit mode. +""" +input TransitModePreferenceCostInput { + """ + A static cost that is added each time a transit mode is used. + """ + cost: Cost + + """ + A cost multiplier of transit leg travel time. """ - reluctance: CostFunction + reluctance: Reluctance } """ @@ -5313,14 +5327,9 @@ input VehicleWalkPreferencesInput { speed: Speed """ - Reluctance in a format of `30 + 2.0 x` where the static cost is applied - both when getting off the vehicle and when getting on the vehicle again. - However, these static costs are not applied when getting on a rented vehicle - for the first time or off the vehicle when returning the vehicle. The multiplier - is for how bad walking the vehicle is compared to being in transit for equal - lengths of time. + Costs related to walking a vehicle. """ - reluctance: CostFunction + cost: VehicleWalkPreferencesCostInput """" How long it takes to hop on or off a vehicle when switching to walking the vehicle @@ -5330,6 +5339,24 @@ input VehicleWalkPreferencesInput { hopOnOrOffTime: Duration } +""" +Costs related to walking a vehicle. +""" +input VehicleWalkPreferencesCostInput { + """ + A static cost that is added each time hopping on or off a vehicle to start or end + vehicle walking. However, this cost is not applied when getting on a rented vehicle + for the first time or when getting off the vehicle when returning the vehicle. + """ + hopOnOrOffCost: Cost + + """ + A cost multiplier of vehicle walking travel time. The multiplier is for how bad + walking the vehicle is compared to being in transit for equal lengths of time. + """ + reluctance: Reluctance +} + """ Preferences related to walking (excluding walking a bicycle or a scooter). """ From e0b5e700aea79ba35b3ca531bb72287cd2acc502 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 3 Nov 2023 10:32:52 +0200 Subject: [PATCH 053/165] Add input type for destination vehicle policy --- .../opentripplanner/apis/gtfs/schema.graphqls | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index de4b2eea149..7e59337be15 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -355,6 +355,25 @@ type BikePark implements Node & PlaceInterface { openingHours: OpeningHours } +""" +Is it possible to arrive to the destination with a rented vehicle and does it +come with an extra cost. +""" +input DestinationVehiclePolicyInput { + """ + Is it possible to arrive to the destination without dropping off the rental + vehicle first. + """ + allowKeepingVehicleAtDestination: Boolean + + """ + Cost associated with arriving to the destination with a rented vehicle. + No cost is applied if arriving to the destination after dropping off the rented + vehicle. + """ + keepingVehicleAtDestinationCost: Cost +} + """Vehicle parking represents a location where bicycles or cars can be parked.""" type VehicleParking implements Node & PlaceInterface { """ @@ -4212,17 +4231,10 @@ Preferences related to rental (station based or floating vehicle rental). """ input RentalPreferencesInput { """ - Is it possible to arrive to the destination without dropping off the rental - vehicle first. + Is it possible to arrive to the destination with a rented vehicle and does it + come with an extra cost. """ - allowKeepingVehicleAtDestination: Boolean - - """ - Cost associated with arriving to the destination with a rented vehicle. - No cost is applied if arriving to the destination after dropping off the rented - vehicle. - """ - keepingVehicleAtDestinationCost: Cost + destinationVehiclePolicy: DestinationVehiclePolicyInput """ Rental networks which can be potentially used as part of an itinerary. From 96a0b5f2cb49dbc953abf71fbff96cb89cd3d5a6 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 6 Nov 2023 17:05:07 +0200 Subject: [PATCH 054/165] Update src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls Co-authored-by: Leonard Ehrenfried --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 7e59337be15..5306c424fc4 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4222,7 +4222,7 @@ enum RealtimeState { """ A cost multiplier for how bad something is compared to being in transit for equal lengths of time. -The value should be over 0. +The value should be greater than 0. """ scalar Reluctance From fc12682b1cb6e0f89d1f36279f1c0ee9c870846e Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 9 Nov 2023 22:04:33 +0200 Subject: [PATCH 055/165] Remove outdated information about invariant and simplify field name --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 5306c424fc4..8709a92f2f3 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -290,7 +290,6 @@ Preferences related to alighting from a transit vehicle. input AlightPreferencesInput { """ What is the required minimum time alighting from a vehicle. - Invariant: `board slack + alight slack <= transfer minimum total slack` """ slack: Duration } @@ -846,7 +845,6 @@ input BoardPreferencesInput { What is the required minimum waiting time at a stop. Setting this value as `PT0S`, for example, can lead to passenger missing a connection when the vehicle leaves ahead of time or the passenger arrives to the stop later than expected. - Invariant: `board slack + alight slack <= transfer minimum total slack` """ slack: Duration } @@ -5016,9 +5014,8 @@ input TransferPreferencesInput { in addition to time it might take to walk between transit stops. Setting this value as `PT0S`, for example, can lead to passenger missing a connection when the vehicle leaves ahead of time or the passenger arrives to the stop later than expected. - Invariant: `board slack + alight slack <= transfer minimum total slack` """ - minimumTotalSlack: Duration + slack: Duration """ How many additional transfers there can be at maximum compared to the itinerary with the From 21839ec92aa1df7d556d579914c6619bd709228a Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 9 Nov 2023 22:05:41 +0200 Subject: [PATCH 056/165] Rename includeRealtimeUpdates -> excludeRealtimeUpdates --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 8709a92f2f3..ba61eeba91e 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4973,12 +4973,12 @@ type TicketType implements Node { input TimetablePreferencesInput { """ - When true, realtime updates are considered during the routing. - In practice, when this option is set as false, some of the suggestions might not be + When false, realtime updates are considered during the routing. + In practice, when this option is set as true, some of the suggestions might not be realistic as the transfers could be invalid due to delays, trips can be cancelled or stops can be skipped. """ - includeRealtimeUpdates: Boolean = true + excludeRealtimeUpdates: Boolean = false """ When true, departures that have been cancelled ahead of time will be From de7d9d51e66f23db8feb69a706e369ef5e014fd0 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 9 Nov 2023 22:06:12 +0200 Subject: [PATCH 057/165] Make stopLocationId mandatory --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index ba61eeba91e..07ce1e893f5 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1433,7 +1433,7 @@ input PlanStopLocationInput { ID of the stop, station, a group of stop places or multimodal stop place. Format should be `FeedId:StopLocationId`. """ - stopLocationId: String + stopLocationId: String! """ If set as true, one must either board or alight from this stop location. Otherwise, From 8f9b3c8c76726e831a4f5719229636e51c993d48 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 10 Nov 2023 09:58:29 +0200 Subject: [PATCH 058/165] Update src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 07ce1e893f5..1734c01fa0b 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2313,7 +2313,7 @@ enum PlanTransitMode { AIRPLANE """ - Selecting this enables all modes to usable in routing. + Selecting this enables all modes to be usable in routing. """ ANY From 3a87b88eede4deb5f65f30cf9bff9df52f955611 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 16 Nov 2023 12:37:37 +0200 Subject: [PATCH 059/165] Simplify optimization input names --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index dfa22cbe141..6669caa3424 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -849,7 +849,7 @@ input BicyclePreferencesInput { """ What criteria should be used when optimizing a cycling route. """ - optimization: CyclingOptimizationCriteriaInput + optimization: CyclingOptimizationInput """ Cost of boarding a vehicle with a bicycle. @@ -2898,7 +2898,7 @@ type BookingInfo { """ What criteria should be used when optimizing a cycling route. """ -input CyclingOptimizationCriteriaInput @oneOf { +input CyclingOptimizationInput @oneOf { """ Use one of the predefined optimization types. """ @@ -2947,7 +2947,7 @@ enum CyclingOptimizationType { """ What criteria should be used when optimizing a scooter route. """ -input ScooterOptimizationCriteriaInput @oneOf { +input ScooterOptimizationInput @oneOf { """ Use one of the predefined optimization types. """ @@ -4541,7 +4541,7 @@ input ScooterPreferencesInput { """ What criteria should be used when optimizing a scooter route. """ - optimization: ScooterOptimizationCriteriaInput + optimization: ScooterOptimizationInput """ Cost of boarding a vehicle with a scooter. From 81bd78ffe96a93b90d9008a45e7d4e9e8b3ccf0e Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 16 Nov 2023 12:38:51 +0200 Subject: [PATCH 060/165] scooter/bicycleWalking -> walking --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 6669caa3424..10642e311ad 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -835,9 +835,8 @@ input BicyclePreferencesInput { """ Walking preferences when walking a bicycle. - TODO: review with @t2gran """ - bicycleWalking: VehicleWalkPreferencesInput + walking: VehicleWalkPreferencesInput """ Maximum speed on flat ground while riding a bicycle. Note, this speed is higher than @@ -4529,7 +4528,7 @@ input ScooterPreferencesInput { """ Walking preferences when walking a scooter. """ - scooterWalking: VehicleWalkPreferencesInput + walking: VehicleWalkPreferencesInput """ Maximum speed on flat ground while riding a scooter. Note, this speed is higher than From f1c02160245b29d484c708aa4c1435ae05647f7b Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 16 Nov 2023 12:39:43 +0200 Subject: [PATCH 061/165] Rename transitConnectionRequired -> strict and set default --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 10642e311ad..39141e45c8d 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1473,7 +1473,7 @@ input PlanStopLocationInput { If set as true, one must either board or alight from this stop location. Otherwise, it's optional and alternatively one can just walk to or from this location. """ - transitConnectionRequired: Boolean + strict: Boolean = false } """ From 45c4128cddf026e7d5a4697e5d0ba9e50285b34d Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 16 Nov 2023 12:40:58 +0200 Subject: [PATCH 062/165] Remove unpreferred from first version to leave room for improvement --- .../opentripplanner/apis/gtfs/schema.graphqls | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 39141e45c8d..70070204c53 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1627,37 +1627,6 @@ input InputUnpreferred { useUnpreferredRoutesPenalty: Int @deprecated(reason: "Use unpreferredCost instead") } -""" -Unpreferred entities and an extra cost attached to using them in routing. -""" -input PlanUnpreferredInput { - """List of feedscoped route IDs (format is `FeedId:RouteId`) unpreferred by the user.""" - routes: [String!] - - """List of feedscoped agency IDs (format is `FeedId:AgencyId`) unpreferred by the user.""" - agencies: [String!] - - """ - Costs related to using unpreferred entities. - """ - cost: PlanUnpreferredCostInput -} - -""" -Costs related to using unpreferred entities. -""" -input PlanUnpreferredCostInput { - """ - A static cost that is added each time something that is unpreferred is used. - """ - cost: Cost - - """ - A cost multiplier of transit leg travel time. - """ - reluctance: Reluctance -} - enum RoutingErrorCode { """ No transit connection was found between the origin and destination within the operating day or @@ -5100,11 +5069,6 @@ input TransitModePreferenceCostInput { Transit routing preferences used for transit legs. """ input TransitPreferencesInput { - """ - Unprefer routes or agencies by giving them extra cost. - """ - unpreferred: PlanUnpreferredInput - """ Preferences related to boarding a transit vehicle. Note, board costs for each street mode can be found under the street mode preferences. From 5cc82db479068bbc60ab105fc583dca3c08cd8a2 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 16 Nov 2023 12:43:58 +0200 Subject: [PATCH 063/165] Fix enum spelling --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 70070204c53..325a43f23f6 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2459,7 +2459,7 @@ enum PlanAccessMode { Getting dropped off by a car to a location that is accessible with a car. Note, this can include walking after the drop-off. """ - CAR_DROPOFF + CAR_DROP_OFF """ Flexible transit. This can include different forms of flexible transit that From 0bc718b50bd4c218606eb0c0df49dea05c6da2a2 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 16 Nov 2023 14:09:47 +0200 Subject: [PATCH 064/165] Include more itinerary filtering settings --- .../opentripplanner/apis/gtfs/schema.graphqls | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 325a43f23f6..49f065a2fe6 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1385,13 +1385,36 @@ input ParkingFilter { } """ -Settings related to debugging why certain itineraries are returned. +Settings that control the behavior of itinerary filtering. These settings are meant mainly +for debugging purposes. """ -input PlanDebugSettingsInput { +input PlanItineraryFilterSettingsInput { """ Itinerary filter debug profile used to control the behaviour of itinerary filters. """ itineraryFilterDebugProfile: ItineraryFilterDebugProfile = OFF + + """ + Pick one itinerary from each group after putting itineraries that are `85%` similar together, + if the given value is `0.85`, for example. Itineraries are grouped together based on relative + the distance of travel that is identical between the itineraries. + """ + groupSimilarityKeepOne: Float = 0.85 + + """ + Pick three itineraries from each group after putting itineraries that are `68%` similar together, + if the given value is `0.68`, for example. The itineraries are grouped by similar legs + (on board same journey). The value must be at least `0.5`. + """ + groupSimilarityKeepThree: Float = 0.68 + + """ + Of the itineraries grouped to maximum of three itineraries, how much worse can the non-grouped + legs be compared to the lowest cost. `2.0` means that they can be double the cost, and any + itineraries having a higher cost will be filtered away. Use a value lower than `1.0` to turn the + grouping off. + """ + groupedOtherThanSameLegsMaxCostMultiplier: Float = 2.0 } """ @@ -4150,9 +4173,10 @@ type QueryType { preferences: PlanPreferencesInput """ - Settings meant for debugging purposes that control the search and the returned itineraries. + Settings that control the behavior of itinerary filtering. These settings are meant mainly + for debugging purposes. """ - debugSettings: PlanDebugSettingsInput + itineraryFilterSettings: PlanItineraryFilterSettingsInput """ Locale used for translations. Note, there might not necessarily be translations available. From 9484f2dddd2be169d299f612039bc0ebf631d1db Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 16 Nov 2023 14:34:02 +0200 Subject: [PATCH 065/165] Move rental and parking preferences under mode specific preferences --- .../opentripplanner/apis/gtfs/schema.graphqls | 133 ++++++++++++++++-- 1 file changed, 119 insertions(+), 14 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 49f065a2fe6..0639d1b0b19 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -854,6 +854,16 @@ input BicyclePreferencesInput { Cost of boarding a vehicle with a bicycle. """ boardCost: Cost + + """ + Bicycle rental related preferences. + """ + rental: BicycleRentalPreferencesInput + + """ + Bicycle parking related preferences. + """ + parking: BicycleParkingPreferencesInput } """ @@ -926,6 +936,16 @@ input CarPreferencesInput { A multiplier for how bad travelling on car is compared to being in transit for equal lengths of time. """ reluctance: Reluctance + + """ + Car rental related preferences. + """ + rental: CarRentalPreferencesInput + + """ + Car parking related preferences. + """ + parking: CarParkingPreferencesInput } """Cluster is a list of stops grouped by name and proximity""" @@ -1324,6 +1344,54 @@ Locale in string format. For example, `en` or `en-US`. """ scalar Locale +""" +Preferences for car parking facilities used during the routing. +""" +input CarParkingPreferencesInput { + """ + Selection filters to include or exclude parking facilities. + An empty list will include all facilities in the routing search. + """ + filters: [ParkingFilter!] + + """ + If `preferred` is non-empty, using a parking facility that doesn't contain + at least one of the preferred conditions, will receive this extra cost and therefore avoided if + preferred options are available. + """ + unpreferredCost: Int + + """ + If non-empty every parking facility that doesn't match this set of conditions will + receive an extra cost (defined by `unpreferredCost`) and therefore avoided. + """ + preferred: [ParkingFilter!] +} + +""" +Preferences for bicycle parking facilities used during the routing. +""" +input BicycleParkingPreferencesInput { + """ + Selection filters to include or exclude parking facilities. + An empty list will include all facilities in the routing search. + """ + filters: [ParkingFilter!] + + """ + If `preferred` is non-empty, using a parking facility that doesn't contain + at least one of the preferred conditions, will receive this extra cost and therefore avoided if + preferred options are available. + """ + unpreferredCost: Int + + """ + If non-empty every parking facility that doesn't match this set of conditions will + receive an extra cost (defined by `unpreferredCost`) and therefore avoided. + """ + preferred: [ParkingFilter!] +} + """ Preferences for parking facilities used during the routing. """ @@ -1560,16 +1628,6 @@ input PlanStreetPreferencesInput { have their own preferences. """ walk: WalkPreferencesInput - - """ - Rental related preferences. - """ - rental: RentalPreferencesInput - - """ - Parking related preferences. - """ - parking: VehicleParkingInput } """ @@ -4255,14 +4313,56 @@ The value should be greater than 0. scalar Reluctance """ -Preferences related to rental (station based or floating vehicle rental). +Preferences related to scooter rental (station based or floating scooter rental). """ -input RentalPreferencesInput { +input ScooterRentalPreferencesInput { """ - Is it possible to arrive to the destination with a rented vehicle and does it + Is it possible to arrive to the destination with a rented scooter and does it come with an extra cost. """ - destinationVehiclePolicy: DestinationVehiclePolicyInput + destinationScooterPolicy: DestinationVehiclePolicyInput + + """ + Rental networks which can be potentially used as part of an itinerary. + """ + allowedNetworks: [String!] + + """ + Rental networks which cannot be used as part of an itinerary. + """ + bannedNetworks: [String!] +} + +""" +Preferences related to car rental (station based or floating car rental). +""" +input CarRentalPreferencesInput { + """ + Is it possible to arrive to the destination with a rented car and does it + come with an extra cost. + """ + destinationCarPolicy: DestinationVehiclePolicyInput + + """ + Rental networks which can be potentially used as part of an itinerary. + """ + allowedNetworks: [String!] + + """ + Rental networks which cannot be used as part of an itinerary. + """ + bannedNetworks: [String!] +} + +""" +Preferences related to bicycle rental (station based or floating bicycle rental). +""" +input BicycleRentalPreferencesInput { + """ + Is it possible to arrive to the destination with a rented bicycle and does it + come with an extra cost. + """ + destinationBicyclePolicy: DestinationVehiclePolicyInput """ Rental networks which can be potentially used as part of an itinerary. @@ -4539,6 +4639,11 @@ input ScooterPreferencesInput { Cost of boarding a vehicle with a scooter. """ boardCost: Cost + + """ + Scooter rental related preferences. + """ + rental: ScooterRentalPreferencesInput } type step { From 34c45b37dc28ddb7bf5bfca7d25dca697c392349 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 16 Nov 2023 14:44:18 +0200 Subject: [PATCH 066/165] walking -> walk --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 0639d1b0b19..d7868840a45 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -836,7 +836,7 @@ input BicyclePreferencesInput { """ Walking preferences when walking a bicycle. """ - walking: VehicleWalkPreferencesInput + walk: VehicleWalkPreferencesInput """ Maximum speed on flat ground while riding a bicycle. Note, this speed is higher than @@ -4621,7 +4621,7 @@ input ScooterPreferencesInput { """ Walking preferences when walking a scooter. """ - walking: VehicleWalkPreferencesInput + walk: VehicleWalkPreferencesInput """ Maximum speed on flat ground while riding a scooter. Note, this speed is higher than From 1ef07da90bfc5b4ba8bdc5fbb84eb9072f114312 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 16 Nov 2023 15:56:05 +0200 Subject: [PATCH 067/165] Refactor modes --- .../opentripplanner/apis/gtfs/schema.graphqls | 83 ++++++++++++++----- 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index d7868840a45..6e00ca3ad7c 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1568,32 +1568,73 @@ input PlanStopLocationInput { } """ -Street mode options for different phases of an itinerary. +Mode selections for the plan search. """ -input PlanStreetModesInput { +input PlanModesInput { + """ + Should only the direct search without any transit be done. + """ + directOnly: Boolean = false + + """ + Should only the transit search be done and never suggest itineraries that don't + contain any transit legs. + """ + transitOnly: Boolean = false + """ Street mode that is used when searching for itineraries that don't use any transit. - Selection of only one allowed for now. + If more than one mode is selected, at least one of them must be used but not necessarily all. + There are modes that automatically also use walking such as the rental modes. To force rental + to be used, this should only include the rental mode and not `WALK` in addition. + The default access mode is `WALK`. """ direct: [PlanDirectMode!] """ - Street mode that is used when searching for access to transit network from origin. - Selection of only one allowed for now. + Modes for different phases of an itinerary when transit is included. Also + includes street mode selections related to connecting to the transit network + and transfers. By default, all transit modes are usable and `WALK` is used for + access, egress and transfers. + """ + transit: PlanTransitModesInput +} + +""" +Modes for different phases of an itinerary when transit is included. Also includes street +mode selections related to connecting to the transit network and transfers. +""" +input PlanTransitModesInput { + """ + Street mode that is used when searching for access to the transit network from origin. + If more than one mode is selected, at least one of them must be used but not necessarily all. + There are modes that automatically also use walking such as the rental modes. To force rental + to be used, this should only include the rental mode and not `WALK` in addition. + The default access mode is `WALK`. """ access: [PlanAccessMode!] """ - Street mode that is used when searching for egress to destination from transit network. - Selection of only one allowed for now. + Street mode that is used when searching for egress to destination from the transit network. + If more than one mode is selected, at least one of them must be used but not necessarily all. + There are modes that automatically also use walking such as the rental modes. To force rental + to be used, this should only include the rental mode and not `WALK` in addition. + The default access mode is `WALK`. """ egress: [PlanEgressMode!] """ - Street mode that is used when searching for transfers. - Selection of only one allowed for now. + Street mode that is used when searching for transfers. Selection of only one allowed for now. + The default transfer mode is `WALK`. """ transfer: [PlanTransferMode!] + + """ + Transit modes and reluctancies associated with them. Each defined mode can be used in + an itinerary but doesn't have to be. If direct search is not disabled, there can be an + itinerary without any transit legs. By default, all transit modes are usable. + """ + transit: [TransitModePreferenceInput!] } """ @@ -1602,11 +1643,6 @@ the transit legs but can change how preferable walking or cycling, for example, transit. """ input PlanStreetPreferencesInput { - """ - Which modes of travel can be used for street routing at different stages. - """ - modes: PlanStreetModesInput - """ Cycling related preferences. """ @@ -2494,7 +2530,7 @@ type PageInfo { } """ -Street modes that can be used for access to transit network from origin. +Street modes that can be used for access to the transit network from origin. """ enum PlanAccessMode { """ @@ -2647,7 +2683,7 @@ enum PlanDirectMode { } """ -Street modes that can be used for egress from transit network to destination. +Street modes that can be used for egress from the transit network to destination. """ enum PlanEgressMode { """ @@ -4225,6 +4261,14 @@ type QueryType { """ destination: PlanLabeledLocationInput + """ + Street and transit modes used during the search. This also includes options to only return + an itinerary that contains no transit legs or force transit to be used in all itineraries. + By default, all transit modes are usable and `WALK` is used for direct street suggestions, + access, egress and transfers. + """ + modes: PlanModesInput + """ Preferences that affect what itineraries are returned. Preferences are split into categories. """ @@ -5218,13 +5262,6 @@ input TransitPreferencesInput { Preferences related to cancellations and realtime. """ timetable: TimetablePreferencesInput - - """ - Transit modes and reluctancies associated with them. Each defined mode can be used in - an itinerary but doesn't have to be. If a direct mode is set under street preferences, - an itinerary without any transit modes can be returned. - """ - modes: [TransitModePreferenceInput!]! } """Text with language""" From bd15a1c3902d035c4022a438a977cd4e6f6b326a Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 16 Nov 2023 23:39:06 +0200 Subject: [PATCH 068/165] Improve documentatio and replace WeightingFactor with Ratio --- .../opentripplanner/apis/gtfs/schema.graphqls | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 6e00ca3ad7c..720fa6ae9f6 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1465,16 +1465,18 @@ input PlanItineraryFilterSettingsInput { """ Pick one itinerary from each group after putting itineraries that are `85%` similar together, if the given value is `0.85`, for example. Itineraries are grouped together based on relative - the distance of travel that is identical between the itineraries. + the distance of transit travel that is identical between the itineraries (access, egress and + transfers are ignored). The value must be at least `0.5`. """ - groupSimilarityKeepOne: Float = 0.85 + groupSimilarityKeepOne: Ratio = 0.85 """ Pick three itineraries from each group after putting itineraries that are `68%` similar together, - if the given value is `0.68`, for example. The itineraries are grouped by similar legs - (on board same journey). The value must be at least `0.5`. + if the given value is `0.68`, for example. Itineraries are grouped together based on relative + the distance of transit travel that is identical between the itineraries (access, egress and + transfers are ignored). The value must be at least `0.5`. """ - groupSimilarityKeepThree: Float = 0.68 + groupSimilarityKeepThree: Ratio = 0.68 """ Of the itineraries grouped to maximum of three itineraries, how much worse can the non-grouped @@ -1691,13 +1693,13 @@ input TriangleCyclingFactorsInput { concerns such as convenience and general cyclist preferences by taking into account road surface etc. """ - safety: WeightingFactor + safety: Ratio """Relative importance of flat terrain""" - flatness: WeightingFactor + flatness: Ratio """Relative importance of duration""" - time: WeightingFactor + time: Ratio } """ @@ -1710,13 +1712,13 @@ input TriangleScooterFactorsInput { concerns such as convenience and general scooter preferences by taking into account road surface etc. """ - safety: WeightingFactor + safety: Ratio """Relative importance of flat terrain""" - flatness: WeightingFactor + flatness: Ratio """Relative importance of duration""" - time: WeightingFactor + time: Ratio } input InputUnpreferred { @@ -5553,7 +5555,7 @@ input WalkPreferencesInput { Factor for how much the walk safety is considered in routing. Value should be between 0 and 1. If the value is set to be 0, safety is ignored. """ - walkSafetyFactor: WeightingFactor + walkSafetyFactor: Ratio """ The cost of boarding a vehicle while walking. @@ -5561,8 +5563,8 @@ input WalkPreferencesInput { boardCost: Cost } -"""A fractional multiplier between 0 and 1, for example 0.25.""" -scalar WeightingFactor +"""A fractional multiplier between 0 and 1, for example 0.25. 0 means 0% and 1 means 100%.""" +scalar Ratio enum WheelchairBoarding { """There is no accessibility information for the stop.""" From 62fd8f7deea7b7d529490da667302c205aa015d2 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 23 Nov 2023 12:07:02 +0200 Subject: [PATCH 069/165] Rename itinerary filter type/field and improve docs --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 720fa6ae9f6..3be0a360231 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1453,10 +1453,10 @@ input ParkingFilter { } """ -Settings that control the behavior of itinerary filtering. These settings are meant mainly -for debugging purposes. +Settings that control the behavior of itinerary filtering. These are advanced settings and +should not be set by a user through user preferences. """ -input PlanItineraryFilterSettingsInput { +input PlanItineraryFilterInput { """ Itinerary filter debug profile used to control the behaviour of itinerary filters. """ @@ -4277,10 +4277,10 @@ type QueryType { preferences: PlanPreferencesInput """ - Settings that control the behavior of itinerary filtering. These settings are meant mainly - for debugging purposes. + Settings that control the behavior of itinerary filtering. These are advanced settings and + should not be set by a user through user preferences. """ - itineraryFilterSettings: PlanItineraryFilterSettingsInput + itineraryFilter: PlanItineraryFilterInput """ Locale used for translations. Note, there might not necessarily be translations available. From 41b553f2534fb52232373313ac97ced9bf89a373 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 23 Nov 2023 12:07:48 +0200 Subject: [PATCH 070/165] Make itinerary non null --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 3be0a360231..94b3ded3b9b 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3342,7 +3342,7 @@ type PlanEdge { An itinerary suggestion. Part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). """ - node: Itinerary + node: Itinerary! """ The cursor of the edge. Part of the From c6164aa4b92017f479cd95b723a792cbc56d8e37 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 23 Nov 2023 12:09:21 +0200 Subject: [PATCH 071/165] Remove debug settings and move used search window to root --- .../opentripplanner/apis/gtfs/schema.graphqls | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 94b3ded3b9b..78b4c40a8ef 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3296,9 +3296,9 @@ type PlanConnection { routingErrors: [RoutingError!]! """ - Debug information about the search. + The search window that was used for the search. """ - debugInformation: PlanDebugInformation! + searchWindowUsed: Duration """ Edges which contain the itineraries. Part of the @@ -3313,26 +3313,6 @@ type PlanConnection { pageInfo: PageInfo! } -""" -Debug information about an itinerary search. -""" -type PlanDebugInformation { - """ - Total time taken for the route request. - """ - totalTime: Duration - - """ - Time taken in the transit router (including access/egress street router). - """ - pathCalculationTime: Duration - - """ - The search window that was used for the search. - """ - searchWindowUsed: Duration -} - """ Edge outputted by a plan search. Part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). From accad203a26b8640b338ab517d20b096a84a3163 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 23 Nov 2023 13:24:04 +0200 Subject: [PATCH 072/165] Remove locations type and move origin and destination to root --- .../opentripplanner/apis/gtfs/schema.graphqls | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 78b4c40a8ef..64b5db75267 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -3286,9 +3286,16 @@ type PlanConnection { searchDateTime: OffsetDateTime """ - Origin and destination of the search. + Origin of the itinerary search. + TODO figure out the best output type + """ + origin: Place! + + """ + Destination of the itinerary search. + TODO figure out the best output type """ - locations: PlanPlaces! + destination: Place! """ Errors faced during the routing search. @@ -3331,21 +3338,6 @@ type PlanEdge { cursor: String! } -""" -Origin and destination of an itinerary search. -""" -type PlanPlaces { - """ - Origin of the itinerary search. - """ - from: Place! - - """ - Destination of the itinerary search. - """ - to: Place! -} - """ List of coordinates in an encoded polyline format (see https://developers.google.com/maps/documentation/utilities/polylinealgorithm). From a88289645e6d97aeda6de27e7031de137dcc7d68 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 7 Dec 2023 15:41:33 +0200 Subject: [PATCH 073/165] Create output plan location type and refactor coordinates --- .../opentripplanner/apis/gtfs/schema.graphqls | 55 +++++++++++++++---- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 64b5db75267..10e5c75f331 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -982,9 +982,25 @@ for example `450`. One cost unit should roughly match a one second travel on tra scalar Cost """ -Either a latitude or a longitude coordinate as a WGS 84 format floating point number. +Coordinate (often referred as coordinates), which is used to specify a location using in the +WGS84 coordinate system. """ -scalar Coordinate +type Coordinate { + """ + Latitude as a WGS84 format number. + """ + latitude: CoordinateValue! + + """ + Longitude as a WGS84 format number. + """ + longitude: CoordinateValue! +} + +""" +Either a latitude or a longitude as a WGS84 format floating point number. +""" +scalar CoordinateValue type Coordinates { """Latitude (WGS 84)""" @@ -1207,13 +1223,13 @@ A coordinate pair used for a location in a plan query. """ input PlanCoordinatesInput { """ - Latitude as a WGS 84 format number. + Latitude as a WGS84 format number. """ - latitude: Coordinate! + latitude: CoordinateValue! """ - Longitude as a WGS 84 format number. + Longitude as a WGS84 format number. """ - longitude: Coordinate! + longitude: CoordinateValue! } input InputBanned { @@ -1487,6 +1503,22 @@ input PlanItineraryFilterInput { groupedOtherThanSameLegsMaxCostMultiplier: Float = 2.0 } +""" +Location that was used in the itinerary search as a origin or destination. +""" +type PlanLabeledLocation { + """ + A location used in a plan query. This can be null if a stop location ID was specified as an input + location but a stop location with that ID does not exist. + """ + location: PlanLocation + + """ + A label that was attached to the location. + """ + label: String +} + """ Plan location settings. Location must be set. Label is optional and used for naming the location. @@ -1504,6 +1536,11 @@ input PlanLabeledLocationInput { label: String } +""" +Union of locations types that could be used in a plan query. +""" +union PlanLocation = Stop | Coordinate + """ Plan location. Either coordinates or a stop location should be defined. """ @@ -3287,15 +3324,13 @@ type PlanConnection { """ Origin of the itinerary search. - TODO figure out the best output type """ - origin: Place! + origin: PlanLabeledLocation! """ Destination of the itinerary search. - TODO figure out the best output type """ - destination: Place! + destination: PlanLabeledLocation! """ Errors faced during the routing search. From 710b3ca74cc2824faa80e9103c397475e5e74408 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 7 Dec 2023 15:44:22 +0200 Subject: [PATCH 074/165] Field and type renaming --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 10e5c75f331..0cd28e9bcdd 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1219,9 +1219,9 @@ type StopGeometries { } """ -A coordinate pair used for a location in a plan query. +A coordinate used for a location in a plan query. """ -input PlanCoordinatesInput { +input PlanCoordinateInput { """ Latitude as a WGS84 format number. """ @@ -1542,13 +1542,13 @@ Union of locations types that could be used in a plan query. union PlanLocation = Stop | Coordinate """ -Plan location. Either coordinates or a stop location should be defined. +Plan location. Either a coordinate or a stop location should be defined. """ input PlanLocationInput @oneOf { """ - Coordinates of the location. Note, either coordinates or a stop location should be defined. + Coordinate of the location. Note, either a coordinate or a stop location should be defined. """ - coordinates: PlanCoordinatesInput + coordinate: PlanCoordinateInput """ Stop, station, a group of stop places or multimodal stop place that should be used as From 652f35620d18a9741ad76ee320a68c9dd1b83648 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 12 Dec 2023 12:28:44 +0200 Subject: [PATCH 075/165] Apply some review feedback --- .../opentripplanner/apis/gtfs/schema.graphqls | 68 ++----------------- 1 file changed, 4 insertions(+), 64 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 0cd28e9bcdd..693c5a0f872 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -363,14 +363,14 @@ input DestinationVehiclePolicyInput { Is it possible to arrive to the destination without dropping off the rental vehicle first. """ - allowKeepingVehicleAtDestination: Boolean + allowKeeping: Boolean """ Cost associated with arriving to the destination with a rented vehicle. No cost is applied if arriving to the destination after dropping off the rented vehicle. """ - keepingVehicleAtDestinationCost: Cost + keepingCost: Cost } """Vehicle parking represents a location where bicycles or cars can be parked.""" @@ -2465,66 +2465,6 @@ enum TransitMode { MONORAIL } -""" -Transit modes include modes that are used within organized transportation networks -run by public transportation authorities, taxi companies etc. -Equivalent to GTFS route_type or to NeTEx TransportMode with an addition of `ANY` -mode that enables all modes. -""" -enum PlanTransitMode { - AIRPLANE - - """ - Selecting this enables all modes to be usable in routing. - """ - ANY - - """ - Most busses or coaches use this mode but sometimes the other bus-like modes such - `COACH` or `TROLLEYBUS` are used. - """ - BUS - - CABLE_CAR - - """ - Sometimes coaches use this mode but sometimes they are modelled to use `BUS` mode - instead. - """ - COACH - - FERRY - - FUNICULAR - - GONDOLA - - """ - This includes long or short distance trains. Tram, subway, monorail and funicular have - their own modes but it's possible that sometimes they use this mode instead. - """ - RAIL - - """ - Subway or metro, depending on the local terminology. - """ - SUBWAY - - TRAM - - """Private car trips shared with others.""" - CARPOOL - - """A taxi, possibly operated by a public transport agency.""" - TAXI - - "Electric buses that draw power from overhead wires using poles." - TROLLEYBUS - - "Railway in which the track consists of a single rail or a beam." - MONORAIL -} - """An object with an ID""" interface Node { """The ID of an object""" @@ -5224,7 +5164,7 @@ input TransitModePreferenceInput { """ Transit mode that could be (but doesn't have to be) used in an itinerary. """ - mode: PlanTransitMode! + mode: TransitMode! """ Costs related to using a transit mode. @@ -5521,7 +5461,7 @@ input VehicleWalkPreferencesInput { or when getting on the vehicle again. However, this is not applied when getting on a rented vehicle for the first time or off the vehicle when returning the vehicle. """ - hopOnOrOffTime: Duration + hopTime: Duration } """ From 8da714a6748ff9295fe92c900197cc83a7347b40 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 12 Dec 2023 13:26:23 +0200 Subject: [PATCH 076/165] Update comments --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 693c5a0f872..7496c4f4d29 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2419,18 +2419,10 @@ Equivalent to GTFS route_type or to NeTEx TransportMode. enum TransitMode { AIRPLANE - """ - Most busses or coaches use this mode but sometimes the other bus-like modes such - `COACH` or `TROLLEYBUS` are used. - """ BUS CABLE_CAR - """ - Sometimes coaches use this mode but sometimes they are modelled to use `BUS` mode - instead. - """ COACH FERRY @@ -2440,8 +2432,7 @@ enum TransitMode { GONDOLA """ - This includes long or short distance trains. Tram, subway, monorail and funicular have - their own modes but it's possible that sometimes they use this mode instead. + This includes long or short distance trains. """ RAIL @@ -4301,7 +4292,9 @@ enum RealtimeState { """ A cost multiplier for how bad something is compared to being in transit for equal lengths of time. -The value should be greater than 0. +The value should be greater than 0. 1 means neutral and values below 1 mean that something is +preferred over transit. + """ scalar Reluctance From 80561b1e23930c3409b406c3d04590db71500bb9 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 12 Dec 2023 16:13:12 +0200 Subject: [PATCH 077/165] Add useRealtimeAvailability to rental preferences --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 7496c4f4d29..c99129c2499 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4317,6 +4317,11 @@ input ScooterRentalPreferencesInput { Rental networks which cannot be used as part of an itinerary. """ bannedNetworks: [String!] + + """ + It realtime availability of vehicles and spaces checked during routing. + """ + useRealtimeAvailability: Boolean } """ @@ -4338,6 +4343,11 @@ input CarRentalPreferencesInput { Rental networks which cannot be used as part of an itinerary. """ bannedNetworks: [String!] + + """ + It realtime availability of vehicles and spaces checked during routing. + """ + useRealtimeAvailability: Boolean } """ @@ -4359,6 +4369,11 @@ input BicycleRentalPreferencesInput { Rental networks which cannot be used as part of an itinerary. """ bannedNetworks: [String!] + + """ + It realtime availability of vehicles and spaces checked during routing. + """ + useRealtimeAvailability: Boolean } """ From 9c765b8c0d51d3afb2d90b57cecf64e2ddeacfd0 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 14 Dec 2023 13:33:50 +0200 Subject: [PATCH 078/165] Use more consistent spelling/naming --- .../opentripplanner/apis/gtfs/schema.graphqls | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index c99129c2499..1d91fe1cbf4 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2737,7 +2737,7 @@ enum PlanTransferMode { WALK } -"""Realtime vehicle position""" +"""Real-time vehicle position""" type VehiclePosition { """Feed-scoped ID that uniquely identifies the vehicle in the format FeedId:VehicleId""" vehicleId: String, @@ -2855,7 +2855,7 @@ type Pattern implements Node { ): [Alert] """ - Realtime-updated position of vehicles that are serving this pattern. + Real-time updated position of vehicles that are serving this pattern. """ vehiclePositions: [VehiclePosition!] @@ -3110,7 +3110,7 @@ type Place { The purpose of this field is to identify the stop within the pattern so it can be cross-referenced between it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds. - However, it should be noted that realtime updates can change the values, so don't store it for + However, it should be noted that real-time updates can change the values, so don't store it for longer amounts of time. Depending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps @@ -3219,7 +3219,7 @@ type Plan { This is the suggested search time for the "previous page" or time window. Insert it together with the searchWindowUsed in the request to get a new set of trips preceding in the search-window BEFORE the current search. No duplicate trips should be returned, unless a trip - is delayed and new realtime-data is available. + is delayed and new real-time data is available. """ prevDateTime: Long @deprecated(reason: "Use previousPageCursor instead") @@ -3227,7 +3227,7 @@ type Plan { This is the suggested search time for the "next page" or time window. Insert it together with the searchWindowUsed in the request to get a new set of trips following in the search-window AFTER the current search. No duplicate trips should be returned, unless a trip - is delayed and new realtime-data is available. + is delayed and new real-time data is available. """ nextDateTime: Long @deprecated(reason: "Use nextPageCursor instead") @@ -4011,7 +4011,7 @@ type QueryType { omitCanceled: Boolean = true """ - When true, realtime updates are ignored during this search. Default value: false + When true, real-time updates are ignored during this search. Default value: false """ ignoreRealtimeUpdates: Boolean @@ -4319,9 +4319,9 @@ input ScooterRentalPreferencesInput { bannedNetworks: [String!] """ - It realtime availability of vehicles and spaces checked during routing. + Should real-time availability of vehicles and spaces checked during routing. """ - useRealtimeAvailability: Boolean + includeRealTimeAvailability: Boolean } """ @@ -4345,9 +4345,9 @@ input CarRentalPreferencesInput { bannedNetworks: [String!] """ - It realtime availability of vehicles and spaces checked during routing. + Should real-time availability of vehicles and spaces checked during routing. """ - useRealtimeAvailability: Boolean + includeRealTimeAvailability: Boolean } """ @@ -4371,9 +4371,9 @@ input BicycleRentalPreferencesInput { bannedNetworks: [String!] """ - It realtime availability of vehicles and spaces checked during routing. + Should real-time availability of vehicles and spaces checked during routing. """ - useRealtimeAvailability: Boolean + includeRealTimeAvailability: Boolean } """ @@ -4992,7 +4992,7 @@ type Stoptime { The purpose of this field is to identify the stop within the pattern so it can be cross-referenced between it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds. - However, it should be noted that realtime updates can change the values, so don't store it for + However, it should be noted that real-time updates can change the values, so don't store it for longer amounts of time. Depending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps @@ -5006,7 +5006,7 @@ type Stoptime { scheduledArrival: Int """ - Realtime prediction of arrival time. Format: seconds since midnight of the departure date + Real-time prediction of arrival time. Format: seconds since midnight of the departure date """ realtimeArrival: Int @@ -5022,7 +5022,7 @@ type Stoptime { scheduledDeparture: Int """ - Realtime prediction of departure time. Format: seconds since midnight of the departure date + Real-time prediction of departure time. Format: seconds since midnight of the departure date """ realtimeDeparture: Int @@ -5109,12 +5109,12 @@ type TicketType implements Node { input TimetablePreferencesInput { """ - When false, realtime updates are considered during the routing. + When false, real-time updates are considered during the routing. In practice, when this option is set as true, some of the suggestions might not be realistic as the transfers could be invalid due to delays, trips can be cancelled or stops can be skipped. """ - excludeRealtimeUpdates: Boolean = false + excludeRealTimeUpdates: Boolean = false """ When true, departures that have been cancelled ahead of time will be @@ -5126,13 +5126,13 @@ input TimetablePreferencesInput { includePlannedCancellations: Boolean = false """ - When true, departures that have been cancelled through a realtime feed will be + When true, departures that have been cancelled through a real-time feed will be included during the routing. This means that an itinerary can include a cancelled departure while some other alternative that contains no cancellations could be filtered out as the alternative containing a cancellation would normally - be better. This option can't be set to true while `includeRealtimeUpdates` is false. + be better. This option can't be set to true while `includeRealTimeUpdates` is false. """ - includeRealtimeCancellations: Boolean = false + includeRealTimeCancellations: Boolean = false } """ @@ -5216,7 +5216,7 @@ input TransitPreferencesInput { transfer: TransferPreferencesInput """ - Preferences related to cancellations and realtime. + Preferences related to cancellations and real-time. """ timetable: TimetablePreferencesInput } @@ -5341,7 +5341,7 @@ type Trip implements Node { ): [Alert] """ - The latest realtime occupancy information for the latest occurance of this + The latest real-time occupancy information for the latest occurance of this trip. """ occupancy: TripOccupancy From da7b8f41e4e5bed83dee7fd592ee0191f61faff0 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 14 Dec 2023 22:51:54 +0200 Subject: [PATCH 079/165] Remove includeRealTimeAvailability For now it's better to automatically set this based on the search time due to paging until we have a better solution --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 1d91fe1cbf4..d848e933832 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4317,11 +4317,6 @@ input ScooterRentalPreferencesInput { Rental networks which cannot be used as part of an itinerary. """ bannedNetworks: [String!] - - """ - Should real-time availability of vehicles and spaces checked during routing. - """ - includeRealTimeAvailability: Boolean } """ @@ -4343,11 +4338,6 @@ input CarRentalPreferencesInput { Rental networks which cannot be used as part of an itinerary. """ bannedNetworks: [String!] - - """ - Should real-time availability of vehicles and spaces checked during routing. - """ - includeRealTimeAvailability: Boolean } """ @@ -4369,11 +4359,6 @@ input BicycleRentalPreferencesInput { Rental networks which cannot be used as part of an itinerary. """ bannedNetworks: [String!] - - """ - Should real-time availability of vehicles and spaces checked during routing. - """ - includeRealTimeAvailability: Boolean } """ From bb8f166f2b53a1184c375fd0b380c11e3d5c0d59 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 14 Dec 2023 22:58:31 +0200 Subject: [PATCH 080/165] Make own destination policies for each rental type --- .../opentripplanner/apis/gtfs/schema.graphqls | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index d848e933832..b15d91f68a3 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -355,20 +355,58 @@ type BikePark implements Node & PlaceInterface { } """ -Is it possible to arrive to the destination with a rented vehicle and does it +Is it possible to arrive to the destination with a rented bicycle and does it come with an extra cost. """ -input DestinationVehiclePolicyInput { +input DestinationBicyclePolicyInput { """ Is it possible to arrive to the destination without dropping off the rental - vehicle first. + bicycle first. """ allowKeeping: Boolean """ - Cost associated with arriving to the destination with a rented vehicle. + Cost associated with arriving to the destination with a rented bicycle. No cost is applied if arriving to the destination after dropping off the rented - vehicle. + bicycle. + """ + keepingCost: Cost +} + +""" +Is it possible to arrive to the destination with a rented car and does it +come with an extra cost. +""" +input DestinationCarPolicyInput { + """ + Is it possible to arrive to the destination without dropping off the rental + car first. + """ + allowKeeping: Boolean + + """ + Cost associated with arriving to the destination with a rented car. + No cost is applied if arriving to the destination after dropping off the rented + car. + """ + keepingCost: Cost +} + +""" +Is it possible to arrive to the destination with a rented scooter and does it +come with an extra cost. +""" +input DestinationScooterPolicyInput { + """ + Is it possible to arrive to the destination without dropping off the rental + scooter first. + """ + allowKeeping: Boolean + + """ + Cost associated with arriving to the destination with a rented scooter. + No cost is applied if arriving to the destination after dropping off the rented + scooter. """ keepingCost: Cost } @@ -4306,7 +4344,7 @@ input ScooterRentalPreferencesInput { Is it possible to arrive to the destination with a rented scooter and does it come with an extra cost. """ - destinationScooterPolicy: DestinationVehiclePolicyInput + destinationScooterPolicy: DestinationScooterPolicyInput """ Rental networks which can be potentially used as part of an itinerary. @@ -4327,7 +4365,7 @@ input CarRentalPreferencesInput { Is it possible to arrive to the destination with a rented car and does it come with an extra cost. """ - destinationCarPolicy: DestinationVehiclePolicyInput + destinationCarPolicy: DestinationCarPolicyInput """ Rental networks which can be potentially used as part of an itinerary. @@ -4348,7 +4386,7 @@ input BicycleRentalPreferencesInput { Is it possible to arrive to the destination with a rented bicycle and does it come with an extra cost. """ - destinationBicyclePolicy: DestinationVehiclePolicyInput + destinationBicyclePolicy: DestinationBicyclePolicyInput """ Rental networks which can be potentially used as part of an itinerary. From 667035d16c18fad9745f91ff0998c54ece2b7454 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 19 Dec 2023 22:01:36 +0200 Subject: [PATCH 081/165] Create own page info type for plan connection --- .../opentripplanner/apis/gtfs/schema.graphqls | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index b15d91f68a3..94697dbb3c5 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2537,6 +2537,27 @@ type PageInfo { endCursor: String } +""" +Information about pagination in a connection. Part of the +[GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). +""" +type PlanPageInfo { + """When paginating forwards, are there more items?""" + hasNextPage: Boolean! + + """When paginating backwards, are there more items?""" + hasPreviousPage: Boolean! + + """When paginating backwards, the cursor to continue.""" + startCursor: String + + """When paginating forwards, the cursor to continue.""" + endCursor: String + + """The search window that was used for the search in the current page.""" + searchWindowUsed: Duration +} + """ Street modes that can be used for access to the transit network from origin. """ @@ -3306,11 +3327,6 @@ type PlanConnection { """ routingErrors: [RoutingError!]! - """ - The search window that was used for the search. - """ - searchWindowUsed: Duration - """ Edges which contain the itineraries. Part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). @@ -3321,7 +3337,7 @@ type PlanConnection { Contains cursors to continue the search and the information if there are more itineraries available. Part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). """ - pageInfo: PageInfo! + pageInfo: PlanPageInfo! } """ From b01c87c44d8b4dc8632668b865334fb41830971a Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 19 Dec 2023 22:07:01 +0200 Subject: [PATCH 082/165] Prefix type with Plan --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 94697dbb3c5..62f69f4e6e2 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1711,7 +1711,7 @@ input PlanTransitModesInput { an itinerary but doesn't have to be. If direct search is not disabled, there can be an itinerary without any transit legs. By default, all transit modes are usable. """ - transit: [TransitModePreferenceInput!] + transit: [PlanTransitModePreferenceInput!] } """ @@ -5207,7 +5207,7 @@ input TransferPreferencesInput { """ Transit mode and a reluctance associated with it. """ -input TransitModePreferenceInput { +input PlanTransitModePreferenceInput { """ Transit mode that could be (but doesn't have to be) used in an itinerary. """ From dbac6a60a8d98fecd0644d48d072831b31bc34fb Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 19 Dec 2023 22:12:35 +0200 Subject: [PATCH 083/165] Make own vehicle walking input types for bicycle/scooter This is done so we can have different default values --- .../opentripplanner/apis/gtfs/schema.graphqls | 74 +++++++++++++++---- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 62f69f4e6e2..32acd3d4557 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -874,7 +874,7 @@ input BicyclePreferencesInput { """ Walking preferences when walking a bicycle. """ - walk: VehicleWalkPreferencesInput + walk: BicycleWalkPreferencesInput """ Maximum speed on flat ground while riding a bicycle. Note, this speed is higher than @@ -4661,7 +4661,7 @@ input ScooterPreferencesInput { """ Walking preferences when walking a scooter. """ - walk: VehicleWalkPreferencesInput + walk: ScooterWalkPreferencesInput """ Maximum speed on flat ground while riding a scooter. Note, this speed is higher than @@ -5488,9 +5488,9 @@ enum VertexType { } """ -Preferences for walking a vehicle (scooter or bicycle). +Preferences for walking a bicycle. """ -input VehicleWalkPreferencesInput { +input BicycleWalkPreferencesInput { """ Maximum walk speed on flat ground. Note, this speed is higher than the average speed will be in itineraries as this is the maximum speed but there are @@ -5499,32 +5499,74 @@ input VehicleWalkPreferencesInput { speed: Speed """ - Costs related to walking a vehicle. + Costs related to walking a bicycle. """ - cost: VehicleWalkPreferencesCostInput + cost: BicycleWalkPreferencesCostInput """" - How long it takes to hop on or off a vehicle when switching to walking the vehicle - or when getting on the vehicle again. However, this is not applied when getting - on a rented vehicle for the first time or off the vehicle when returning the vehicle. + How long it takes to hop on or off a bicycle when switching to walking the bicycle + or when getting on the bicycle again. However, this is not applied when getting + on a rented bicycle for the first time or off the bicycle when returning the bicycle. """ hopTime: Duration } """ -Costs related to walking a vehicle. +Preferences for walking a scooter. """ -input VehicleWalkPreferencesCostInput { +input ScooterWalkPreferencesInput { """ - A static cost that is added each time hopping on or off a vehicle to start or end - vehicle walking. However, this cost is not applied when getting on a rented vehicle - for the first time or when getting off the vehicle when returning the vehicle. + Maximum walk speed on flat ground. Note, this speed is higher than the average speed + will be in itineraries as this is the maximum speed but there are + factors that slow down walking such as crossings, intersections and elevation changes. + """ + speed: Speed + + """ + Costs related to walking a scooter. + """ + cost: ScooterWalkPreferencesCostInput + + """" + How long it takes to hop on or off a scooter when switching to walking the scooter + or when getting on the scooter again. However, this is not applied when getting + on a rented scooter for the first time or off the scooter when returning the scooter. + """ + hopTime: Duration +} + +""" +Costs related to walking a bicycle. +""" +input BicycleWalkPreferencesCostInput { + """ + A static cost that is added each time hopping on or off a bicycle to start or end + bicycle walking. However, this cost is not applied when getting on a rented bicycle + for the first time or when getting off the bicycle when returning the bicycle. + """ + hopOnOrOffCost: Cost + + """ + A cost multiplier of bicycle walking travel time. The multiplier is for how bad + walking the bicycle is compared to being in transit for equal lengths of time. + """ + reluctance: Reluctance +} + +""" +Costs related to walking a scooter. +""" +input ScooterWalkPreferencesCostInput { + """ + A static cost that is added each time hopping on or off a scooter to start or end + scooter walking. However, this cost is not applied when getting on a rented scooter + for the first time or when getting off the scooter when returning the scooter. """ hopOnOrOffCost: Cost """ - A cost multiplier of vehicle walking travel time. The multiplier is for how bad - walking the vehicle is compared to being in transit for equal lengths of time. + A cost multiplier of scooter walking travel time. The multiplier is for how bad + walking the scooter is compared to being in transit for equal lengths of time. """ reluctance: Reluctance } From 71047d8d905e6563253ccddc53bd8dc21e1472b8 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 2 Jan 2024 15:01:01 +0200 Subject: [PATCH 084/165] hopOnOrOffCost -> hopCost --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 32acd3d4557..0859edd8ab7 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -5544,7 +5544,7 @@ input BicycleWalkPreferencesCostInput { bicycle walking. However, this cost is not applied when getting on a rented bicycle for the first time or when getting off the bicycle when returning the bicycle. """ - hopOnOrOffCost: Cost + hopCost: Cost """ A cost multiplier of bicycle walking travel time. The multiplier is for how bad @@ -5562,7 +5562,7 @@ input ScooterWalkPreferencesCostInput { scooter walking. However, this cost is not applied when getting on a rented scooter for the first time or when getting off the scooter when returning the scooter. """ - hopOnOrOffCost: Cost + hopCost: Cost """ A cost multiplier of scooter walking travel time. The multiplier is for how bad From 7157ff82c0797f7caaa1d55965f19b6495ec951e Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 4 Jan 2024 11:36:14 +0200 Subject: [PATCH 085/165] Remove boardCost from scooter preferences --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 0859edd8ab7..fda86f54add 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4675,11 +4675,6 @@ input ScooterPreferencesInput { """ optimization: ScooterOptimizationInput - """ - Cost of boarding a vehicle with a scooter. - """ - boardCost: Cost - """ Scooter rental related preferences. """ From d3f802173e92f742904e8343a0680f5466597e4e Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 4 Jan 2024 11:43:34 +0200 Subject: [PATCH 086/165] Only allow scooter rental as mode --- .../opentripplanner/apis/gtfs/schema.graphqls | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index fda86f54add..5281632629a 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2614,13 +2614,6 @@ enum PlanAccessMode { """ FLEX - """ - Riding a scooter to a stop and boarding a vehicle with the scooter. - Note, this can include walking when it's needed to walk the scooter. Access - can use scooter only if the mode used for transfers and egress is also `SCOOTER`. - """ - SCOOTER - """ Scooter rental can use either station based systems or "floating" vehicles which are not linked to a rental station. Note, if there are no @@ -2689,12 +2682,6 @@ enum PlanDirectMode { """ FLEX - """ - Riding a scooter from the origin to the destination. Note, this can include walking - when it's needed to walk the scooter. - """ - SCOOTER - """ Scooter rental can use either station based systems or "floating" vehicles which are not linked to a rental station. Note, if there are no @@ -2753,13 +2740,6 @@ enum PlanEgressMode { """ FLEX - """ - Riding a scooter from a stop to the destination. Note, this can include walking - when it's needed to walk the scooter. Egress can use scooter only if the mode - used for transfers and egress is also `SCOOTER`. - """ - SCOOTER - """ Scooter rental can use either station based systems or "floating" vehicles which are not linked to a rental station. Note, if there are no @@ -2783,13 +2763,6 @@ enum PlanTransferMode { """ BICYCLE - """ - Riding a scooter between transit vehicles (typically between stops). Note, this can - include walking when it's needed to walk the scooter. Transfers can only use - scooter if the mode used for access and egress is also `SCOOTER`. - """ - SCOOTER - """ Walking between transit vehicles (typically between stops). """ From e140b3ebfae764548bbc2f08f5f0cbd16ec6fdfb Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 19 Jan 2024 14:43:30 +0200 Subject: [PATCH 087/165] Remove scooter walking --- .../opentripplanner/apis/gtfs/schema.graphqls | 35 ++----------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 5281632629a..005f6854c28 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -2619,7 +2619,7 @@ enum PlanAccessMode { vehicles which are not linked to a rental station. Note, if there are no rental options available, access will include only walking. Also, this can include walking before picking up or after dropping off the - scooter or when it's needed to walk the scooter. + scooter. """ SCOOTER_RENTAL @@ -2687,7 +2687,7 @@ enum PlanDirectMode { vehicles which are not linked to a rental station. Note, if there are no rental options available, itinerary will include only walking. Also, this can include walking before picking up or after dropping off the - scooter or when it's needed to walk the scooter. + scooter. """ SCOOTER_RENTAL @@ -2745,7 +2745,7 @@ enum PlanEgressMode { vehicles which are not linked to a rental station. Note, if there are no rental options available, egress will include only walking. Also, this can include walking before picking up or after dropping off the - scooter or when it's needed to walk the scooter. + scooter. """ SCOOTER_RENTAL @@ -4631,11 +4631,6 @@ input ScooterPreferencesInput { """ reluctance: Reluctance - """ - Walking preferences when walking a scooter. - """ - walk: ScooterWalkPreferencesInput - """ Maximum speed on flat ground while riding a scooter. Note, this speed is higher than the average speed will be in itineraries as this is the maximum speed but there are @@ -5479,30 +5474,6 @@ input BicycleWalkPreferencesInput { hopTime: Duration } -""" -Preferences for walking a scooter. -""" -input ScooterWalkPreferencesInput { - """ - Maximum walk speed on flat ground. Note, this speed is higher than the average speed - will be in itineraries as this is the maximum speed but there are - factors that slow down walking such as crossings, intersections and elevation changes. - """ - speed: Speed - - """ - Costs related to walking a scooter. - """ - cost: ScooterWalkPreferencesCostInput - - """" - How long it takes to hop on or off a scooter when switching to walking the scooter - or when getting on the scooter again. However, this is not applied when getting - on a rented scooter for the first time or off the scooter when returning the scooter. - """ - hopTime: Duration -} - """ Costs related to walking a bicycle. """ From 0a512fdb743c703f81752f670091bf09c38d97a5 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 22 Jan 2024 21:09:08 +0200 Subject: [PATCH 088/165] Apply suggestions from code review Co-authored-by: Leonard Ehrenfried --- .../opentripplanner/apis/gtfs/schema.graphqls | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 005f6854c28..62e0a9fc156 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -360,8 +360,7 @@ come with an extra cost. """ input DestinationBicyclePolicyInput { """ - Is it possible to arrive to the destination without dropping off the rental - bicycle first. + For networks that require station drop-off, should the routing engine offer results that go directly to the destination without dropping off the rental bicycle first. """ allowKeeping: Boolean @@ -379,8 +378,7 @@ come with an extra cost. """ input DestinationCarPolicyInput { """ - Is it possible to arrive to the destination without dropping off the rental - car first. + For networks that require station drop-off, should the routing engine offer results that go directly to the destination without dropping off the rental car first. """ allowKeeping: Boolean @@ -398,8 +396,7 @@ come with an extra cost. """ input DestinationScooterPolicyInput { """ - Is it possible to arrive to the destination without dropping off the rental - scooter first. + For networks that require station drop-off, should the routing engine offer results that go directly to the destination without dropping off the rental scooter first. """ allowKeeping: Boolean @@ -1064,7 +1061,7 @@ input PlanDateTimeInput @oneOf { Latest arrival time date time. The returned itineraries should not arrive to the destination after this instant unless one is using paging to find later itineraries. Note, it is not currently possible - to define both `earliestDeparture` and `latestArrival`. + to define both `earliestDeparture` and `latestArrival`. """ latestArrival: OffsetDateTime } @@ -1394,7 +1391,7 @@ input InputPreferred { } """ -Locale in string format. For example, `en` or `en-US`. +Locale in the format defined in [RFC5646](https://datatracker.ietf.org/doc/html/rfc5646). For example, `en` or `en-US`. """ scalar Locale @@ -1507,8 +1504,8 @@ input ParkingFilter { } """ -Settings that control the behavior of itinerary filtering. These are advanced settings and -should not be set by a user through user preferences. +Settings that control the behavior of itinerary filtering. **These are advanced settings and +should not be set by a user through user preferences.** """ input PlanItineraryFilterInput { """ From 1180b48cf969b81042ff60b9f83feb7d2d3c7db5 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 30 Jan 2024 12:26:07 +0200 Subject: [PATCH 089/165] Remove old type --- .../opentripplanner/apis/gtfs/schema.graphqls | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 62e0a9fc156..12ab3145c14 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -5489,24 +5489,6 @@ input BicycleWalkPreferencesCostInput { reluctance: Reluctance } -""" -Costs related to walking a scooter. -""" -input ScooterWalkPreferencesCostInput { - """ - A static cost that is added each time hopping on or off a scooter to start or end - scooter walking. However, this cost is not applied when getting on a rented scooter - for the first time or when getting off the scooter when returning the scooter. - """ - hopCost: Cost - - """ - A cost multiplier of scooter walking travel time. The multiplier is for how bad - walking the scooter is compared to being in transit for equal lengths of time. - """ - reluctance: Reluctance -} - """ Preferences related to walking (excluding walking a bicycle or a scooter). """ From 2813c8b0becb508d029321d4865a682107b7b19d Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 30 Jan 2024 12:33:04 +0200 Subject: [PATCH 090/165] Rename hopCost/time -> mountDismountCost/Time --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 12ab3145c14..f01f7ae05c4 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -5468,7 +5468,7 @@ input BicycleWalkPreferencesInput { or when getting on the bicycle again. However, this is not applied when getting on a rented bicycle for the first time or off the bicycle when returning the bicycle. """ - hopTime: Duration + mountDismountTime: Duration } """ @@ -5480,7 +5480,7 @@ input BicycleWalkPreferencesCostInput { bicycle walking. However, this cost is not applied when getting on a rented bicycle for the first time or when getting off the bicycle when returning the bicycle. """ - hopCost: Cost + mountDismountCost: Cost """ A cost multiplier of bicycle walking travel time. The multiplier is for how bad From e2e6446942a686e24eba6bcf6e8ee5479ccc3e67 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 9 Feb 2024 16:36:38 +0200 Subject: [PATCH 091/165] Add basic wiring --- .../apis/gtfs/GraphQLScalars.java | 80 + .../apis/gtfs/GtfsGraphQLIndex.java | 24 + .../apis/gtfs/datafetchers/QueryTypeImpl.java | 9 + .../gtfs/generated/GraphQLDataFetchers.java | 78 +- .../apis/gtfs/generated/GraphQLTypes.java | 2707 ++++++++++++++--- .../apis/gtfs/generated/graphql-codegen.yml | 11 + .../apis/gtfs/model/PlanLocation.java | 12 + 7 files changed, 2415 insertions(+), 506 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/model/PlanLocation.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index 6280ac28ac2..2361ef4141b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -10,6 +10,10 @@ import graphql.schema.CoercingParseValueException; import graphql.schema.CoercingSerializeException; import graphql.schema.GraphQLScalarType; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import javax.annotation.Nonnull; import org.locationtech.jts.geom.Geometry; import org.opentripplanner.framework.geometry.GeometryUtils; import org.opentripplanner.framework.graphql.scalar.DurationScalarFactory; @@ -50,6 +54,58 @@ public String parseLiteral(Object input) { ) .build(); + public static GraphQLScalarType offsetDateTimeScalar = GraphQLScalarType + .newScalar() + .name("OffsetDateTime") + .coercing( + new Coercing() { + @Override + public String serialize(@Nonnull Object dataFetcherResult) + throws CoercingSerializeException { + if (dataFetcherResult instanceof ZonedDateTime zdt) { + return zdt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } else if (dataFetcherResult instanceof OffsetDateTime odt) { + return odt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } else return null; + } + + @Override + public OffsetDateTime parseValue(Object input) throws CoercingParseValueException { + return null; + } + + @Override + public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralException { + return null; + } + } + ) + .build(); + + public static GraphQLScalarType coordinateValueScalar = GraphQLScalarType + .newScalar() + .name("CoordinateValue") + .coercing( + new Coercing() { + @Override + public String serialize(@Nonnull Object dataFetcherResult) + throws CoercingSerializeException { + return null; + } + + @Override + public OffsetDateTime parseValue(Object input) throws CoercingParseValueException { + return null; + } + + @Override + public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralException { + return null; + } + } + ) + .build(); + public static GraphQLScalarType geoJsonScalar = GraphQLScalarType .newScalar() .name("GeoJson") @@ -152,4 +208,28 @@ public Grams parseLiteral(Object input) throws CoercingParseLiteralException { } ) .build(); + + public static GraphQLScalarType ratioScalar = GraphQLScalarType + .newScalar() + .name("Ratio") + .coercing( + new Coercing() { + @Override + public String serialize(@Nonnull Object dataFetcherResult) + throws CoercingSerializeException { + return null; + } + + @Override + public OffsetDateTime parseValue(Object input) throws CoercingParseValueException { + return null; + } + + @Override + public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralException { + return null; + } + } + ) + .build(); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index ff3e8681ce8..5e065b8227b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -81,6 +81,7 @@ import org.opentripplanner.apis.gtfs.datafetchers.serviceTimeRangeImpl; import org.opentripplanner.apis.gtfs.datafetchers.stepImpl; import org.opentripplanner.apis.gtfs.datafetchers.stopAtDistanceImpl; +import org.opentripplanner.apis.gtfs.model.PlanLocation; import org.opentripplanner.apis.gtfs.model.StopPosition; import org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation; import org.opentripplanner.framework.application.OTPFeature; @@ -111,12 +112,35 @@ protected static GraphQLSchema buildSchema() { .scalar(GraphQLScalars.geoJsonScalar) .scalar(GraphQLScalars.graphQLIDScalar) .scalar(GraphQLScalars.gramsScalar) + .scalar(GraphQLScalars.offsetDateTimeScalar) + .scalar(GraphQLScalars.ratioScalar) + .scalar(GraphQLScalars.coordinateValueScalar) .scalar(ExtendedScalars.GraphQLLong) + .scalar(ExtendedScalars.Locale) + .scalar( + ExtendedScalars + .newAliasedScalar("Cost") + .aliasedScalar(ExtendedScalars.NonNegativeInt) + .build() + ) + .scalar( + ExtendedScalars + .newAliasedScalar("Speed") + .aliasedScalar(ExtendedScalars.NonNegativeFloat) + .build() + ) + .scalar( + ExtendedScalars + .newAliasedScalar("Reluctance") + .aliasedScalar(ExtendedScalars.NonNegativeFloat) + .build() + ) .type("Node", type -> type.typeResolver(new NodeTypeResolver())) .type("PlaceInterface", type -> type.typeResolver(new PlaceInterfaceTypeResolver())) .type("StopPosition", type -> type.typeResolver(new StopPosition() {})) .type("FareProduct", type -> type.typeResolver(new FareProductTypeResolver())) .type("AlertEntity", type -> type.typeResolver(new AlertEntityTypeResolver())) + .type("PlanLocation", type -> type.typeResolver(new PlanLocation() {})) .type(typeWiring.build(AgencyImpl.class)) .type(typeWiring.build(AlertImpl.class)) .type(typeWiring.build(BikeParkImpl.class)) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index a4f5cb00e2f..b7a85b96c73 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -39,6 +39,7 @@ import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.gtfs.mapping.DirectionMapper; import org.opentripplanner.model.TripTimeOnDate; +import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.routing.alertpatch.EntitySelector; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.api.request.RouteRequest; @@ -490,6 +491,14 @@ public DataFetcher> plan() { }; } + @Override + public DataFetcher> planConnection() { + return environment -> { + List itineraries = List.of(); + return new SimpleListConnection<>(itineraries).get(environment); + }; + } + @Override public DataFetcher rentalVehicle() { return environment -> { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 2f39f7f4030..7ded260c70f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -20,6 +20,7 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLRelativeDirection; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLRoutingErrorCode; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLTransitMode; +import org.opentripplanner.apis.gtfs.model.PlanLocation; import org.opentripplanner.apis.gtfs.model.RideHailingProvider; import org.opentripplanner.apis.gtfs.model.StopPosition; import org.opentripplanner.apis.gtfs.model.TripOccupancy; @@ -284,6 +285,16 @@ public interface GraphQLContactInfo { public DataFetcher phoneNumber(); } + /** + * Coordinate (often referred as coordinates), which is used to specify a location using in the + * WGS84 coordinate system. + */ + public interface GraphQLCoordinate { + public DataFetcher latitude(); + + public DataFetcher longitude(); + } + public interface GraphQLCoordinates { public DataFetcher lat(); @@ -648,6 +659,60 @@ public interface GraphQLPlan { public DataFetcher to(); } + /** + * Plan (result of an itinerary search) that follows + * [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + */ + public interface GraphQLPlanConnection { + public DataFetcher destination(); + + public DataFetcher>> edges(); + + public DataFetcher origin(); + + public DataFetcher pageInfo(); + + public DataFetcher> routingErrors(); + + public DataFetcher searchDateTime(); + } + + /** + * Edge outputted by a plan search. Part of the + * [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + */ + public interface GraphQLPlanEdge { + public DataFetcher cursor(); + + public DataFetcher node(); + } + + /** Location that was used in the itinerary search as a origin or destination. */ + public interface GraphQLPlanLabeledLocation { + public DataFetcher label(); + + public DataFetcher location(); + } + + /** Union of locations types that could be used in a plan query. */ + public interface GraphQLPlanLocation extends TypeResolver {} + + /** + * Information about pagination in a connection. Part of the + * [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). + */ + public interface GraphQLPlanPageInfo { + public DataFetcher endCursor(); + + public DataFetcher hasNextPage(); + + public DataFetcher hasPreviousPage(); + + public DataFetcher searchWindowUsed(); + + public DataFetcher startCursor(); + } + /** Stop position at a specific stop. */ public interface GraphQLPositionAtStop { public DataFetcher position(); @@ -701,6 +766,8 @@ public interface GraphQLQueryType { public DataFetcher> plan(); + public DataFetcher> planConnection(); + public DataFetcher rentalVehicle(); public DataFetcher> rentalVehicles(); @@ -1147,7 +1214,7 @@ public interface GraphQLVehicleParkingSpaces { public DataFetcher wheelchairAccessibleCarSpaces(); } - /** Realtime vehicle position */ + /** Real-time vehicle position */ public interface GraphQLVehiclePosition { public DataFetcher heading(); @@ -1235,6 +1302,10 @@ public interface GraphQLElevationProfileComponent { public DataFetcher elevation(); } + /** + * This type is only here for backwards-compatibility and this API will never return it anymore. + * Please use the leg's `fareProducts` instead. + */ public interface GraphQLFare { public DataFetcher cents(); @@ -1245,7 +1316,10 @@ public interface GraphQLFare { public DataFetcher type(); } - /** Component of the fare (i.e. ticket) for a part of the itinerary */ + /** + * This type is only here for backwards-compatibility and this API will never return it anymore. + * Please use the leg's `fareProducts` instead. + */ public interface GraphQLFareComponent { public DataFetcher cents(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index ecc038fec18..c528f023cdf 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -19,6 +19,26 @@ public enum GraphQLAbsoluteDirection { WEST, } + public static class GraphQLAccessibilityPreferencesInput { + + private GraphQLWheelchairPreferencesInput wheelchair; + + public GraphQLAccessibilityPreferencesInput(Map args) { + if (args != null) { + this.wheelchair = + new GraphQLWheelchairPreferencesInput((Map) args.get("wheelchair")); + } + } + + public GraphQLWheelchairPreferencesInput getGraphQLWheelchair() { + return this.wheelchair; + } + + public void setGraphQLWheelchair(GraphQLWheelchairPreferencesInput wheelchair) { + this.wheelchair = wheelchair; + } + } + public static class GraphQLAgencyAlertsArgs { private List types; @@ -94,826 +114,1904 @@ public enum GraphQLAlertSeverityLevelType { WARNING, } - public static class GraphQLBikeParkNameArgs { + public static class GraphQLAlightPreferencesInput { - private String language; + private java.time.Duration slack; - public GraphQLBikeParkNameArgs(Map args) { + public GraphQLAlightPreferencesInput(Map args) { if (args != null) { - this.language = (String) args.get("language"); + this.slack = (java.time.Duration) args.get("slack"); } } - public String getGraphQLLanguage() { - return this.language; + public java.time.Duration getGraphQLSlack() { + return this.slack; } - public void setGraphQLLanguage(String language) { - this.language = language; + public void setGraphQLSlack(java.time.Duration slack) { + this.slack = slack; } } - public enum GraphQLBikesAllowed { - ALLOWED, - NOT_ALLOWED, - NO_INFORMATION, - } - - public static class GraphQLCarParkNameArgs { + public static class GraphQLBicycleParkingPreferencesInput { - private String language; + private List filters; + private List preferred; + private Integer unpreferredCost; - public GraphQLCarParkNameArgs(Map args) { + public GraphQLBicycleParkingPreferencesInput(Map args) { if (args != null) { - this.language = (String) args.get("language"); + if (args.get("filters") != null) { + this.filters = (List) args.get("filters"); + } + if (args.get("preferred") != null) { + this.preferred = (List) args.get("preferred"); + } + this.unpreferredCost = (Integer) args.get("unpreferredCost"); } } - public String getGraphQLLanguage() { - return this.language; + public List getGraphQLFilters() { + return this.filters; } - public void setGraphQLLanguage(String language) { - this.language = language; + public List getGraphQLPreferred() { + return this.preferred; + } + + public Integer getGraphQLUnpreferredCost() { + return this.unpreferredCost; + } + + public void setGraphQLFilters(List filters) { + this.filters = filters; + } + + public void setGraphQLPreferred(List preferred) { + this.preferred = preferred; + } + + public void setGraphQLUnpreferredCost(Integer unpreferredCost) { + this.unpreferredCost = unpreferredCost; } } - public static class GraphQLDepartureRowStoptimesArgs { + public static class GraphQLBicyclePreferencesInput { - private Integer numberOfDepartures; - private Boolean omitCanceled; - private Boolean omitNonPickups; - private Long startTime; - private Integer timeRange; + private org.opentripplanner.framework.model.Cost boardCost; + private GraphQLCyclingOptimizationInput optimization; + private GraphQLBicycleParkingPreferencesInput parking; + private Double reluctance; + private GraphQLBicycleRentalPreferencesInput rental; + private Double speed; + private GraphQLBicycleWalkPreferencesInput walk; - public GraphQLDepartureRowStoptimesArgs(Map args) { + public GraphQLBicyclePreferencesInput(Map args) { if (args != null) { - this.numberOfDepartures = (Integer) args.get("numberOfDepartures"); - this.omitCanceled = (Boolean) args.get("omitCanceled"); - this.omitNonPickups = (Boolean) args.get("omitNonPickups"); - this.startTime = (Long) args.get("startTime"); - this.timeRange = (Integer) args.get("timeRange"); + this.boardCost = (org.opentripplanner.framework.model.Cost) args.get("boardCost"); + this.optimization = + new GraphQLCyclingOptimizationInput((Map) args.get("optimization")); + this.parking = + new GraphQLBicycleParkingPreferencesInput((Map) args.get("parking")); + this.reluctance = (Double) args.get("reluctance"); + this.rental = + new GraphQLBicycleRentalPreferencesInput((Map) args.get("rental")); + this.speed = (Double) args.get("speed"); + this.walk = new GraphQLBicycleWalkPreferencesInput((Map) args.get("walk")); } } - public Integer getGraphQLNumberOfDepartures() { - return this.numberOfDepartures; + public org.opentripplanner.framework.model.Cost getGraphQLBoardCost() { + return this.boardCost; } - public Boolean getGraphQLOmitCanceled() { - return this.omitCanceled; + public GraphQLCyclingOptimizationInput getGraphQLOptimization() { + return this.optimization; } - public Boolean getGraphQLOmitNonPickups() { - return this.omitNonPickups; + public GraphQLBicycleParkingPreferencesInput getGraphQLParking() { + return this.parking; } - public Long getGraphQLStartTime() { - return this.startTime; + public Double getGraphQLReluctance() { + return this.reluctance; } - public Integer getGraphQLTimeRange() { - return this.timeRange; + public GraphQLBicycleRentalPreferencesInput getGraphQLRental() { + return this.rental; } - public void setGraphQLNumberOfDepartures(Integer numberOfDepartures) { - this.numberOfDepartures = numberOfDepartures; + public Double getGraphQLSpeed() { + return this.speed; } - public void setGraphQLOmitCanceled(Boolean omitCanceled) { - this.omitCanceled = omitCanceled; + public GraphQLBicycleWalkPreferencesInput getGraphQLWalk() { + return this.walk; } - public void setGraphQLOmitNonPickups(Boolean omitNonPickups) { - this.omitNonPickups = omitNonPickups; + public void setGraphQLBoardCost(org.opentripplanner.framework.model.Cost boardCost) { + this.boardCost = boardCost; } - public void setGraphQLStartTime(Long startTime) { - this.startTime = startTime; + public void setGraphQLOptimization(GraphQLCyclingOptimizationInput optimization) { + this.optimization = optimization; } - public void setGraphQLTimeRange(Integer timeRange) { - this.timeRange = timeRange; + public void setGraphQLParking(GraphQLBicycleParkingPreferencesInput parking) { + this.parking = parking; } - } - - public static class GraphQLFeedAlertsArgs { - - private List types; - public GraphQLFeedAlertsArgs(Map args) { - if (args != null) { - if (args.get("types") != null) { - this.types = - ((List) args.get("types")).stream() - .map(item -> - item instanceof GraphQLFeedAlertType - ? item - : GraphQLFeedAlertType.valueOf((String) item) - ) - .map(GraphQLFeedAlertType.class::cast) - .collect(Collectors.toList()); - } - } + public void setGraphQLReluctance(Double reluctance) { + this.reluctance = reluctance; } - public List getGraphQLTypes() { - return this.types; + public void setGraphQLRental(GraphQLBicycleRentalPreferencesInput rental) { + this.rental = rental; } - public void setGraphQLTypes(List types) { - this.types = types; + public void setGraphQLSpeed(Double speed) { + this.speed = speed; } - } - - /** Entities, which are relevant for a feed and can contain alerts */ - public enum GraphQLFeedAlertType { - AGENCIES, - ROUTE_TYPES, - } - - public enum GraphQLFilterPlaceType { - BICYCLE_RENT, - BIKE_PARK, - CAR_PARK, - DEPARTURE_ROW, - STATION, - STOP, - VEHICLE_RENT, - } - public enum GraphQLFormFactor { - BICYCLE, - CAR, - CARGO_BICYCLE, - MOPED, - OTHER, - SCOOTER, - SCOOTER_SEATED, - SCOOTER_STANDING, + public void setGraphQLWalk(GraphQLBicycleWalkPreferencesInput walk) { + this.walk = walk; + } } - public static class GraphQLInputBannedInput { + public static class GraphQLBicycleRentalPreferencesInput { - private String agencies; - private String routes; - private String stops; - private String stopsHard; - private String trips; + private List allowedNetworks; + private List bannedNetworks; + private GraphQLDestinationBicyclePolicyInput destinationBicyclePolicy; - public GraphQLInputBannedInput(Map args) { + public GraphQLBicycleRentalPreferencesInput(Map args) { if (args != null) { - this.agencies = (String) args.get("agencies"); - this.routes = (String) args.get("routes"); - this.stops = (String) args.get("stops"); - this.stopsHard = (String) args.get("stopsHard"); - this.trips = (String) args.get("trips"); + this.allowedNetworks = (List) args.get("allowedNetworks"); + this.bannedNetworks = (List) args.get("bannedNetworks"); + this.destinationBicyclePolicy = + new GraphQLDestinationBicyclePolicyInput( + (Map) args.get("destinationBicyclePolicy") + ); } } - public String getGraphQLAgencies() { - return this.agencies; + public List getGraphQLAllowedNetworks() { + return this.allowedNetworks; } - public String getGraphQLRoutes() { - return this.routes; + public List getGraphQLBannedNetworks() { + return this.bannedNetworks; } - public String getGraphQLStops() { - return this.stops; + public GraphQLDestinationBicyclePolicyInput getGraphQLDestinationBicyclePolicy() { + return this.destinationBicyclePolicy; } - public String getGraphQLStopsHard() { - return this.stopsHard; + public void setGraphQLAllowedNetworks(List allowedNetworks) { + this.allowedNetworks = allowedNetworks; } - public String getGraphQLTrips() { - return this.trips; + public void setGraphQLBannedNetworks(List bannedNetworks) { + this.bannedNetworks = bannedNetworks; } - public void setGraphQLAgencies(String agencies) { - this.agencies = agencies; + public void setGraphQLDestinationBicyclePolicy( + GraphQLDestinationBicyclePolicyInput destinationBicyclePolicy + ) { + this.destinationBicyclePolicy = destinationBicyclePolicy; } + } - public void setGraphQLRoutes(String routes) { - this.routes = routes; + public static class GraphQLBicycleWalkPreferencesCostInput { + + private org.opentripplanner.framework.model.Cost mountDismountCost; + private Double reluctance; + + public GraphQLBicycleWalkPreferencesCostInput(Map args) { + if (args != null) { + this.mountDismountCost = + (org.opentripplanner.framework.model.Cost) args.get("mountDismountCost"); + this.reluctance = (Double) args.get("reluctance"); + } } - public void setGraphQLStops(String stops) { - this.stops = stops; + public org.opentripplanner.framework.model.Cost getGraphQLMountDismountCost() { + return this.mountDismountCost; } - public void setGraphQLStopsHard(String stopsHard) { - this.stopsHard = stopsHard; + public Double getGraphQLReluctance() { + return this.reluctance; } - public void setGraphQLTrips(String trips) { - this.trips = trips; + public void setGraphQLMountDismountCost( + org.opentripplanner.framework.model.Cost mountDismountCost + ) { + this.mountDismountCost = mountDismountCost; + } + + public void setGraphQLReluctance(Double reluctance) { + this.reluctance = reluctance; } } - public static class GraphQLInputCoordinatesInput { + public static class GraphQLBicycleWalkPreferencesInput { - private String address; - private Double lat; - private Integer locationSlack; - private Double lon; + private GraphQLBicycleWalkPreferencesCostInput cost; + private java.time.Duration mountDismountTime; + private Double speed; - public GraphQLInputCoordinatesInput(Map args) { + public GraphQLBicycleWalkPreferencesInput(Map args) { if (args != null) { - this.address = (String) args.get("address"); - this.lat = (Double) args.get("lat"); - this.locationSlack = (Integer) args.get("locationSlack"); - this.lon = (Double) args.get("lon"); + this.cost = + new GraphQLBicycleWalkPreferencesCostInput((Map) args.get("cost")); + this.mountDismountTime = (java.time.Duration) args.get("mountDismountTime"); + this.speed = (Double) args.get("speed"); } } - public String getGraphQLAddress() { - return this.address; + public GraphQLBicycleWalkPreferencesCostInput getGraphQLCost() { + return this.cost; } - public Double getGraphQLLat() { - return this.lat; + public java.time.Duration getGraphQLMountDismountTime() { + return this.mountDismountTime; } - public Integer getGraphQLLocationSlack() { - return this.locationSlack; + public Double getGraphQLSpeed() { + return this.speed; } - public Double getGraphQLLon() { - return this.lon; + public void setGraphQLCost(GraphQLBicycleWalkPreferencesCostInput cost) { + this.cost = cost; } - public void setGraphQLAddress(String address) { - this.address = address; + public void setGraphQLMountDismountTime(java.time.Duration mountDismountTime) { + this.mountDismountTime = mountDismountTime; } - public void setGraphQLLat(Double lat) { - this.lat = lat; + public void setGraphQLSpeed(Double speed) { + this.speed = speed; } + } - public void setGraphQLLocationSlack(Integer locationSlack) { - this.locationSlack = locationSlack; + public static class GraphQLBikeParkNameArgs { + + private String language; + + public GraphQLBikeParkNameArgs(Map args) { + if (args != null) { + this.language = (String) args.get("language"); + } } - public void setGraphQLLon(Double lon) { - this.lon = lon; + public String getGraphQLLanguage() { + return this.language; + } + + public void setGraphQLLanguage(String language) { + this.language = language; } } - public enum GraphQLInputField { - DATE_TIME, - FROM, - TO, + public enum GraphQLBikesAllowed { + ALLOWED, + NOT_ALLOWED, + NO_INFORMATION, } - public static class GraphQLInputFiltersInput { + public static class GraphQLBoardPreferencesInput { - private List bikeParks; - private List bikeRentalStations; - private List carParks; - private List routes; - private List stations; - private List stops; + private java.time.Duration slack; + private Double waitReluctance; - public GraphQLInputFiltersInput(Map args) { + public GraphQLBoardPreferencesInput(Map args) { if (args != null) { - this.bikeParks = (List) args.get("bikeParks"); - this.bikeRentalStations = (List) args.get("bikeRentalStations"); - this.carParks = (List) args.get("carParks"); - this.routes = (List) args.get("routes"); - this.stations = (List) args.get("stations"); - this.stops = (List) args.get("stops"); + this.slack = (java.time.Duration) args.get("slack"); + this.waitReluctance = (Double) args.get("waitReluctance"); } } - public List getGraphQLBikeParks() { - return this.bikeParks; + public java.time.Duration getGraphQLSlack() { + return this.slack; } - public List getGraphQLBikeRentalStations() { - return this.bikeRentalStations; + public Double getGraphQLWaitReluctance() { + return this.waitReluctance; } - public List getGraphQLCarParks() { - return this.carParks; + public void setGraphQLSlack(java.time.Duration slack) { + this.slack = slack; } - public List getGraphQLRoutes() { - return this.routes; + public void setGraphQLWaitReluctance(Double waitReluctance) { + this.waitReluctance = waitReluctance; } + } - public List getGraphQLStations() { - return this.stations; + public static class GraphQLCarParkNameArgs { + + private String language; + + public GraphQLCarParkNameArgs(Map args) { + if (args != null) { + this.language = (String) args.get("language"); + } } - public List getGraphQLStops() { - return this.stops; + public String getGraphQLLanguage() { + return this.language; } - public void setGraphQLBikeParks(List bikeParks) { - this.bikeParks = bikeParks; + public void setGraphQLLanguage(String language) { + this.language = language; } + } - public void setGraphQLBikeRentalStations(List bikeRentalStations) { - this.bikeRentalStations = bikeRentalStations; + public static class GraphQLCarParkingPreferencesInput { + + private List filters; + private List preferred; + private Integer unpreferredCost; + + public GraphQLCarParkingPreferencesInput(Map args) { + if (args != null) { + if (args.get("filters") != null) { + this.filters = (List) args.get("filters"); + } + if (args.get("preferred") != null) { + this.preferred = (List) args.get("preferred"); + } + this.unpreferredCost = (Integer) args.get("unpreferredCost"); + } } - public void setGraphQLCarParks(List carParks) { - this.carParks = carParks; + public List getGraphQLFilters() { + return this.filters; } - public void setGraphQLRoutes(List routes) { - this.routes = routes; + public List getGraphQLPreferred() { + return this.preferred; } - public void setGraphQLStations(List stations) { - this.stations = stations; + public Integer getGraphQLUnpreferredCost() { + return this.unpreferredCost; } - public void setGraphQLStops(List stops) { - this.stops = stops; + public void setGraphQLFilters(List filters) { + this.filters = filters; + } + + public void setGraphQLPreferred(List preferred) { + this.preferred = preferred; + } + + public void setGraphQLUnpreferredCost(Integer unpreferredCost) { + this.unpreferredCost = unpreferredCost; } } - public static class GraphQLInputModeWeightInput { + public static class GraphQLCarPreferencesInput { - private Double AIRPLANE; - private Double BUS; - private Double CABLE_CAR; - private Double FERRY; - private Double FUNICULAR; - private Double GONDOLA; - private Double RAIL; - private Double SUBWAY; - private Double TRAM; + private GraphQLCarParkingPreferencesInput parking; + private Double reluctance; + private GraphQLCarRentalPreferencesInput rental; - public GraphQLInputModeWeightInput(Map args) { + public GraphQLCarPreferencesInput(Map args) { if (args != null) { - this.AIRPLANE = (Double) args.get("AIRPLANE"); - this.BUS = (Double) args.get("BUS"); - this.CABLE_CAR = (Double) args.get("CABLE_CAR"); - this.FERRY = (Double) args.get("FERRY"); - this.FUNICULAR = (Double) args.get("FUNICULAR"); - this.GONDOLA = (Double) args.get("GONDOLA"); - this.RAIL = (Double) args.get("RAIL"); - this.SUBWAY = (Double) args.get("SUBWAY"); - this.TRAM = (Double) args.get("TRAM"); + this.parking = + new GraphQLCarParkingPreferencesInput((Map) args.get("parking")); + this.reluctance = (Double) args.get("reluctance"); + this.rental = + new GraphQLCarRentalPreferencesInput((Map) args.get("rental")); } } - public Double getGraphQLAirplane() { - return this.AIRPLANE; + public GraphQLCarParkingPreferencesInput getGraphQLParking() { + return this.parking; } - public Double getGraphQLBus() { - return this.BUS; + public Double getGraphQLReluctance() { + return this.reluctance; } - public Double getGraphQLCable_Car() { - return this.CABLE_CAR; + public GraphQLCarRentalPreferencesInput getGraphQLRental() { + return this.rental; } - public Double getGraphQLFerry() { - return this.FERRY; + public void setGraphQLParking(GraphQLCarParkingPreferencesInput parking) { + this.parking = parking; } - public Double getGraphQLFunicular() { - return this.FUNICULAR; + public void setGraphQLReluctance(Double reluctance) { + this.reluctance = reluctance; } - public Double getGraphQLGondola() { - return this.GONDOLA; + public void setGraphQLRental(GraphQLCarRentalPreferencesInput rental) { + this.rental = rental; } + } - public Double getGraphQLRail() { - return this.RAIL; + public static class GraphQLCarRentalPreferencesInput { + + private List allowedNetworks; + private List bannedNetworks; + private GraphQLDestinationCarPolicyInput destinationCarPolicy; + + public GraphQLCarRentalPreferencesInput(Map args) { + if (args != null) { + this.allowedNetworks = (List) args.get("allowedNetworks"); + this.bannedNetworks = (List) args.get("bannedNetworks"); + this.destinationCarPolicy = + new GraphQLDestinationCarPolicyInput( + (Map) args.get("destinationCarPolicy") + ); + } } - public Double getGraphQLSubway() { - return this.SUBWAY; + public List getGraphQLAllowedNetworks() { + return this.allowedNetworks; } - public Double getGraphQLTram() { - return this.TRAM; + public List getGraphQLBannedNetworks() { + return this.bannedNetworks; } - public void setGraphQLAirplane(Double AIRPLANE) { - this.AIRPLANE = AIRPLANE; + public GraphQLDestinationCarPolicyInput getGraphQLDestinationCarPolicy() { + return this.destinationCarPolicy; } - public void setGraphQLBus(Double BUS) { - this.BUS = BUS; + public void setGraphQLAllowedNetworks(List allowedNetworks) { + this.allowedNetworks = allowedNetworks; } - public void setGraphQLCable_Car(Double CABLE_CAR) { - this.CABLE_CAR = CABLE_CAR; + public void setGraphQLBannedNetworks(List bannedNetworks) { + this.bannedNetworks = bannedNetworks; } - public void setGraphQLFerry(Double FERRY) { - this.FERRY = FERRY; + public void setGraphQLDestinationCarPolicy( + GraphQLDestinationCarPolicyInput destinationCarPolicy + ) { + this.destinationCarPolicy = destinationCarPolicy; } + } - public void setGraphQLFunicular(Double FUNICULAR) { - this.FUNICULAR = FUNICULAR; + public static class GraphQLCyclingOptimizationInput { + + private GraphQLTriangleCyclingFactorsInput triangle; + private GraphQLCyclingOptimizationType type; + + public GraphQLCyclingOptimizationInput(Map args) { + if (args != null) { + this.triangle = + new GraphQLTriangleCyclingFactorsInput((Map) args.get("triangle")); + if (args.get("type") instanceof GraphQLCyclingOptimizationType) { + this.type = (GraphQLCyclingOptimizationType) args.get("type"); + } else { + this.type = GraphQLCyclingOptimizationType.valueOf((String) args.get("type")); + } + } } - public void setGraphQLGondola(Double GONDOLA) { - this.GONDOLA = GONDOLA; + public GraphQLTriangleCyclingFactorsInput getGraphQLTriangle() { + return this.triangle; } - public void setGraphQLRail(Double RAIL) { - this.RAIL = RAIL; + public GraphQLCyclingOptimizationType getGraphQLType() { + return this.type; } - public void setGraphQLSubway(Double SUBWAY) { - this.SUBWAY = SUBWAY; + public void setGraphQLTriangle(GraphQLTriangleCyclingFactorsInput triangle) { + this.triangle = triangle; } - public void setGraphQLTram(Double TRAM) { - this.TRAM = TRAM; + public void setGraphQLType(GraphQLCyclingOptimizationType type) { + this.type = type; } } - public static class GraphQLInputPreferredInput { + /** + * Predefined optimization alternatives for bicycling routing. For more customization, + * one can use the triangle factors. + */ + public enum GraphQLCyclingOptimizationType { + FLAT_STREETS, + SAFEST_STREETS, + SAFE_STREETS, + SHORTEST_DURATION, + } - private String agencies; - private Integer otherThanPreferredRoutesPenalty; - private String routes; + public static class GraphQLDepartureRowStoptimesArgs { - public GraphQLInputPreferredInput(Map args) { + private Integer numberOfDepartures; + private Boolean omitCanceled; + private Boolean omitNonPickups; + private Long startTime; + private Integer timeRange; + + public GraphQLDepartureRowStoptimesArgs(Map args) { if (args != null) { - this.agencies = (String) args.get("agencies"); - this.otherThanPreferredRoutesPenalty = - (Integer) args.get("otherThanPreferredRoutesPenalty"); - this.routes = (String) args.get("routes"); + this.numberOfDepartures = (Integer) args.get("numberOfDepartures"); + this.omitCanceled = (Boolean) args.get("omitCanceled"); + this.omitNonPickups = (Boolean) args.get("omitNonPickups"); + this.startTime = (Long) args.get("startTime"); + this.timeRange = (Integer) args.get("timeRange"); } } - public String getGraphQLAgencies() { - return this.agencies; + public Integer getGraphQLNumberOfDepartures() { + return this.numberOfDepartures; } - public Integer getGraphQLOtherThanPreferredRoutesPenalty() { - return this.otherThanPreferredRoutesPenalty; + public Boolean getGraphQLOmitCanceled() { + return this.omitCanceled; } - public String getGraphQLRoutes() { - return this.routes; + public Boolean getGraphQLOmitNonPickups() { + return this.omitNonPickups; } - public void setGraphQLAgencies(String agencies) { - this.agencies = agencies; + public Long getGraphQLStartTime() { + return this.startTime; } - public void setGraphQLOtherThanPreferredRoutesPenalty(Integer otherThanPreferredRoutesPenalty) { - this.otherThanPreferredRoutesPenalty = otherThanPreferredRoutesPenalty; + public Integer getGraphQLTimeRange() { + return this.timeRange; } - public void setGraphQLRoutes(String routes) { - this.routes = routes; + public void setGraphQLNumberOfDepartures(Integer numberOfDepartures) { + this.numberOfDepartures = numberOfDepartures; } - } - public static class GraphQLInputTriangleInput { + public void setGraphQLOmitCanceled(Boolean omitCanceled) { + this.omitCanceled = omitCanceled; + } - private Double safetyFactor; - private Double slopeFactor; - private Double timeFactor; + public void setGraphQLOmitNonPickups(Boolean omitNonPickups) { + this.omitNonPickups = omitNonPickups; + } - public GraphQLInputTriangleInput(Map args) { - if (args != null) { - this.safetyFactor = (Double) args.get("safetyFactor"); - this.slopeFactor = (Double) args.get("slopeFactor"); - this.timeFactor = (Double) args.get("timeFactor"); - } + public void setGraphQLStartTime(Long startTime) { + this.startTime = startTime; } - public Double getGraphQLSafetyFactor() { - return this.safetyFactor; + public void setGraphQLTimeRange(Integer timeRange) { + this.timeRange = timeRange; } + } - public Double getGraphQLSlopeFactor() { - return this.slopeFactor; + public static class GraphQLDestinationBicyclePolicyInput { + + private Boolean allowKeeping; + private org.opentripplanner.framework.model.Cost keepingCost; + + public GraphQLDestinationBicyclePolicyInput(Map args) { + if (args != null) { + this.allowKeeping = (Boolean) args.get("allowKeeping"); + this.keepingCost = (org.opentripplanner.framework.model.Cost) args.get("keepingCost"); + } } - public Double getGraphQLTimeFactor() { - return this.timeFactor; + public Boolean getGraphQLAllowKeeping() { + return this.allowKeeping; } - public void setGraphQLSafetyFactor(Double safetyFactor) { - this.safetyFactor = safetyFactor; + public org.opentripplanner.framework.model.Cost getGraphQLKeepingCost() { + return this.keepingCost; } - public void setGraphQLSlopeFactor(Double slopeFactor) { - this.slopeFactor = slopeFactor; + public void setGraphQLAllowKeeping(Boolean allowKeeping) { + this.allowKeeping = allowKeeping; } - public void setGraphQLTimeFactor(Double timeFactor) { - this.timeFactor = timeFactor; + public void setGraphQLKeepingCost(org.opentripplanner.framework.model.Cost keepingCost) { + this.keepingCost = keepingCost; } } - public static class GraphQLInputUnpreferredInput { + public static class GraphQLDestinationCarPolicyInput { - private String agencies; - private String routes; - private String unpreferredCost; - private Integer useUnpreferredRoutesPenalty; + private Boolean allowKeeping; + private org.opentripplanner.framework.model.Cost keepingCost; - public GraphQLInputUnpreferredInput(Map args) { + public GraphQLDestinationCarPolicyInput(Map args) { if (args != null) { - this.agencies = (String) args.get("agencies"); - this.routes = (String) args.get("routes"); - this.unpreferredCost = (String) args.get("unpreferredCost"); - this.useUnpreferredRoutesPenalty = (Integer) args.get("useUnpreferredRoutesPenalty"); + this.allowKeeping = (Boolean) args.get("allowKeeping"); + this.keepingCost = (org.opentripplanner.framework.model.Cost) args.get("keepingCost"); } } - public String getGraphQLAgencies() { - return this.agencies; + public Boolean getGraphQLAllowKeeping() { + return this.allowKeeping; } - public String getGraphQLRoutes() { - return this.routes; + public org.opentripplanner.framework.model.Cost getGraphQLKeepingCost() { + return this.keepingCost; } - public String getGraphQLUnpreferredCost() { - return this.unpreferredCost; + public void setGraphQLAllowKeeping(Boolean allowKeeping) { + this.allowKeeping = allowKeeping; } - public Integer getGraphQLUseUnpreferredRoutesPenalty() { - return this.useUnpreferredRoutesPenalty; + public void setGraphQLKeepingCost(org.opentripplanner.framework.model.Cost keepingCost) { + this.keepingCost = keepingCost; } + } - public void setGraphQLAgencies(String agencies) { - this.agencies = agencies; - } + public static class GraphQLDestinationScooterPolicyInput { - public void setGraphQLRoutes(String routes) { - this.routes = routes; - } + private Boolean allowKeeping; + private org.opentripplanner.framework.model.Cost keepingCost; + + public GraphQLDestinationScooterPolicyInput(Map args) { + if (args != null) { + this.allowKeeping = (Boolean) args.get("allowKeeping"); + this.keepingCost = (org.opentripplanner.framework.model.Cost) args.get("keepingCost"); + } + } + + public Boolean getGraphQLAllowKeeping() { + return this.allowKeeping; + } + + public org.opentripplanner.framework.model.Cost getGraphQLKeepingCost() { + return this.keepingCost; + } + + public void setGraphQLAllowKeeping(Boolean allowKeeping) { + this.allowKeeping = allowKeeping; + } + + public void setGraphQLKeepingCost(org.opentripplanner.framework.model.Cost keepingCost) { + this.keepingCost = keepingCost; + } + } + + public static class GraphQLFeedAlertsArgs { + + private List types; + + public GraphQLFeedAlertsArgs(Map args) { + if (args != null) { + if (args.get("types") != null) { + this.types = + ((List) args.get("types")).stream() + .map(item -> + item instanceof GraphQLFeedAlertType + ? item + : GraphQLFeedAlertType.valueOf((String) item) + ) + .map(GraphQLFeedAlertType.class::cast) + .collect(Collectors.toList()); + } + } + } + + public List getGraphQLTypes() { + return this.types; + } + + public void setGraphQLTypes(List types) { + this.types = types; + } + } + + /** Entities, which are relevant for a feed and can contain alerts */ + public enum GraphQLFeedAlertType { + AGENCIES, + ROUTE_TYPES, + } + + public enum GraphQLFilterPlaceType { + BICYCLE_RENT, + BIKE_PARK, + CAR_PARK, + DEPARTURE_ROW, + STATION, + STOP, + VEHICLE_RENT, + } + + public enum GraphQLFormFactor { + BICYCLE, + CAR, + CARGO_BICYCLE, + MOPED, + OTHER, + SCOOTER, + SCOOTER_SEATED, + SCOOTER_STANDING, + } + + public static class GraphQLInputBannedInput { + + private String agencies; + private String routes; + private String stops; + private String stopsHard; + private String trips; + + public GraphQLInputBannedInput(Map args) { + if (args != null) { + this.agencies = (String) args.get("agencies"); + this.routes = (String) args.get("routes"); + this.stops = (String) args.get("stops"); + this.stopsHard = (String) args.get("stopsHard"); + this.trips = (String) args.get("trips"); + } + } + + public String getGraphQLAgencies() { + return this.agencies; + } + + public String getGraphQLRoutes() { + return this.routes; + } + + public String getGraphQLStops() { + return this.stops; + } + + public String getGraphQLStopsHard() { + return this.stopsHard; + } + + public String getGraphQLTrips() { + return this.trips; + } + + public void setGraphQLAgencies(String agencies) { + this.agencies = agencies; + } + + public void setGraphQLRoutes(String routes) { + this.routes = routes; + } + + public void setGraphQLStops(String stops) { + this.stops = stops; + } + + public void setGraphQLStopsHard(String stopsHard) { + this.stopsHard = stopsHard; + } + + public void setGraphQLTrips(String trips) { + this.trips = trips; + } + } + + public static class GraphQLInputCoordinatesInput { + + private String address; + private Double lat; + private Integer locationSlack; + private Double lon; + + public GraphQLInputCoordinatesInput(Map args) { + if (args != null) { + this.address = (String) args.get("address"); + this.lat = (Double) args.get("lat"); + this.locationSlack = (Integer) args.get("locationSlack"); + this.lon = (Double) args.get("lon"); + } + } + + public String getGraphQLAddress() { + return this.address; + } + + public Double getGraphQLLat() { + return this.lat; + } + + public Integer getGraphQLLocationSlack() { + return this.locationSlack; + } + + public Double getGraphQLLon() { + return this.lon; + } + + public void setGraphQLAddress(String address) { + this.address = address; + } + + public void setGraphQLLat(Double lat) { + this.lat = lat; + } + + public void setGraphQLLocationSlack(Integer locationSlack) { + this.locationSlack = locationSlack; + } + + public void setGraphQLLon(Double lon) { + this.lon = lon; + } + } + + public enum GraphQLInputField { + DATE_TIME, + FROM, + TO, + } + + public static class GraphQLInputFiltersInput { + + private List bikeParks; + private List bikeRentalStations; + private List carParks; + private List routes; + private List stations; + private List stops; + + public GraphQLInputFiltersInput(Map args) { + if (args != null) { + this.bikeParks = (List) args.get("bikeParks"); + this.bikeRentalStations = (List) args.get("bikeRentalStations"); + this.carParks = (List) args.get("carParks"); + this.routes = (List) args.get("routes"); + this.stations = (List) args.get("stations"); + this.stops = (List) args.get("stops"); + } + } + + public List getGraphQLBikeParks() { + return this.bikeParks; + } + + public List getGraphQLBikeRentalStations() { + return this.bikeRentalStations; + } + + public List getGraphQLCarParks() { + return this.carParks; + } + + public List getGraphQLRoutes() { + return this.routes; + } + + public List getGraphQLStations() { + return this.stations; + } + + public List getGraphQLStops() { + return this.stops; + } + + public void setGraphQLBikeParks(List bikeParks) { + this.bikeParks = bikeParks; + } + + public void setGraphQLBikeRentalStations(List bikeRentalStations) { + this.bikeRentalStations = bikeRentalStations; + } + + public void setGraphQLCarParks(List carParks) { + this.carParks = carParks; + } + + public void setGraphQLRoutes(List routes) { + this.routes = routes; + } + + public void setGraphQLStations(List stations) { + this.stations = stations; + } + + public void setGraphQLStops(List stops) { + this.stops = stops; + } + } + + public static class GraphQLInputModeWeightInput { + + private Double AIRPLANE; + private Double BUS; + private Double CABLE_CAR; + private Double FERRY; + private Double FUNICULAR; + private Double GONDOLA; + private Double RAIL; + private Double SUBWAY; + private Double TRAM; + + public GraphQLInputModeWeightInput(Map args) { + if (args != null) { + this.AIRPLANE = (Double) args.get("AIRPLANE"); + this.BUS = (Double) args.get("BUS"); + this.CABLE_CAR = (Double) args.get("CABLE_CAR"); + this.FERRY = (Double) args.get("FERRY"); + this.FUNICULAR = (Double) args.get("FUNICULAR"); + this.GONDOLA = (Double) args.get("GONDOLA"); + this.RAIL = (Double) args.get("RAIL"); + this.SUBWAY = (Double) args.get("SUBWAY"); + this.TRAM = (Double) args.get("TRAM"); + } + } + + public Double getGraphQLAirplane() { + return this.AIRPLANE; + } + + public Double getGraphQLBus() { + return this.BUS; + } + + public Double getGraphQLCable_Car() { + return this.CABLE_CAR; + } + + public Double getGraphQLFerry() { + return this.FERRY; + } + + public Double getGraphQLFunicular() { + return this.FUNICULAR; + } + + public Double getGraphQLGondola() { + return this.GONDOLA; + } + + public Double getGraphQLRail() { + return this.RAIL; + } + + public Double getGraphQLSubway() { + return this.SUBWAY; + } + + public Double getGraphQLTram() { + return this.TRAM; + } + + public void setGraphQLAirplane(Double AIRPLANE) { + this.AIRPLANE = AIRPLANE; + } + + public void setGraphQLBus(Double BUS) { + this.BUS = BUS; + } + + public void setGraphQLCable_Car(Double CABLE_CAR) { + this.CABLE_CAR = CABLE_CAR; + } + + public void setGraphQLFerry(Double FERRY) { + this.FERRY = FERRY; + } + + public void setGraphQLFunicular(Double FUNICULAR) { + this.FUNICULAR = FUNICULAR; + } + + public void setGraphQLGondola(Double GONDOLA) { + this.GONDOLA = GONDOLA; + } + + public void setGraphQLRail(Double RAIL) { + this.RAIL = RAIL; + } + + public void setGraphQLSubway(Double SUBWAY) { + this.SUBWAY = SUBWAY; + } + + public void setGraphQLTram(Double TRAM) { + this.TRAM = TRAM; + } + } + + public static class GraphQLInputPreferredInput { + + private String agencies; + private Integer otherThanPreferredRoutesPenalty; + private String routes; + + public GraphQLInputPreferredInput(Map args) { + if (args != null) { + this.agencies = (String) args.get("agencies"); + this.otherThanPreferredRoutesPenalty = + (Integer) args.get("otherThanPreferredRoutesPenalty"); + this.routes = (String) args.get("routes"); + } + } + + public String getGraphQLAgencies() { + return this.agencies; + } + + public Integer getGraphQLOtherThanPreferredRoutesPenalty() { + return this.otherThanPreferredRoutesPenalty; + } + + public String getGraphQLRoutes() { + return this.routes; + } + + public void setGraphQLAgencies(String agencies) { + this.agencies = agencies; + } + + public void setGraphQLOtherThanPreferredRoutesPenalty(Integer otherThanPreferredRoutesPenalty) { + this.otherThanPreferredRoutesPenalty = otherThanPreferredRoutesPenalty; + } + + public void setGraphQLRoutes(String routes) { + this.routes = routes; + } + } + + public static class GraphQLInputTriangleInput { + + private Double safetyFactor; + private Double slopeFactor; + private Double timeFactor; + + public GraphQLInputTriangleInput(Map args) { + if (args != null) { + this.safetyFactor = (Double) args.get("safetyFactor"); + this.slopeFactor = (Double) args.get("slopeFactor"); + this.timeFactor = (Double) args.get("timeFactor"); + } + } + + public Double getGraphQLSafetyFactor() { + return this.safetyFactor; + } + + public Double getGraphQLSlopeFactor() { + return this.slopeFactor; + } + + public Double getGraphQLTimeFactor() { + return this.timeFactor; + } + + public void setGraphQLSafetyFactor(Double safetyFactor) { + this.safetyFactor = safetyFactor; + } + + public void setGraphQLSlopeFactor(Double slopeFactor) { + this.slopeFactor = slopeFactor; + } + + public void setGraphQLTimeFactor(Double timeFactor) { + this.timeFactor = timeFactor; + } + } + + public static class GraphQLInputUnpreferredInput { + + private String agencies; + private String routes; + private String unpreferredCost; + private Integer useUnpreferredRoutesPenalty; + + public GraphQLInputUnpreferredInput(Map args) { + if (args != null) { + this.agencies = (String) args.get("agencies"); + this.routes = (String) args.get("routes"); + this.unpreferredCost = (String) args.get("unpreferredCost"); + this.useUnpreferredRoutesPenalty = (Integer) args.get("useUnpreferredRoutesPenalty"); + } + } + + public String getGraphQLAgencies() { + return this.agencies; + } + + public String getGraphQLRoutes() { + return this.routes; + } + + public String getGraphQLUnpreferredCost() { + return this.unpreferredCost; + } + + public Integer getGraphQLUseUnpreferredRoutesPenalty() { + return this.useUnpreferredRoutesPenalty; + } + + public void setGraphQLAgencies(String agencies) { + this.agencies = agencies; + } + + public void setGraphQLRoutes(String routes) { + this.routes = routes; + } public void setGraphQLUnpreferredCost(String unpreferredCost) { this.unpreferredCost = unpreferredCost; } - public void setGraphQLUseUnpreferredRoutesPenalty(Integer useUnpreferredRoutesPenalty) { - this.useUnpreferredRoutesPenalty = useUnpreferredRoutesPenalty; + public void setGraphQLUseUnpreferredRoutesPenalty(Integer useUnpreferredRoutesPenalty) { + this.useUnpreferredRoutesPenalty = useUnpreferredRoutesPenalty; + } + } + + /** + * Enable this to attach a system notice to itineraries instead of removing them. This is very + * convenient when tuning the itinerary-filter-chain. + */ + public enum GraphQLItineraryFilterDebugProfile { + LIMIT_TO_NUMBER_OF_ITINERARIES, + LIMIT_TO_SEARCH_WINDOW, + LIST_ALL, + OFF, + } + + public static class GraphQLLegNextLegsArgs { + + private List destinationModesWithParentStation; + private Integer numberOfLegs; + private List originModesWithParentStation; + + public GraphQLLegNextLegsArgs(Map args) { + if (args != null) { + if (args.get("destinationModesWithParentStation") != null) { + this.destinationModesWithParentStation = + ((List) args.get("destinationModesWithParentStation")).stream() + .map(item -> + item instanceof GraphQLTransitMode + ? item + : GraphQLTransitMode.valueOf((String) item) + ) + .map(GraphQLTransitMode.class::cast) + .collect(Collectors.toList()); + } + this.numberOfLegs = (Integer) args.get("numberOfLegs"); + if (args.get("originModesWithParentStation") != null) { + this.originModesWithParentStation = + ((List) args.get("originModesWithParentStation")).stream() + .map(item -> + item instanceof GraphQLTransitMode + ? item + : GraphQLTransitMode.valueOf((String) item) + ) + .map(GraphQLTransitMode.class::cast) + .collect(Collectors.toList()); + } + } + } + + public List getGraphQLDestinationModesWithParentStation() { + return this.destinationModesWithParentStation; + } + + public Integer getGraphQLNumberOfLegs() { + return this.numberOfLegs; + } + + public List getGraphQLOriginModesWithParentStation() { + return this.originModesWithParentStation; + } + + public void setGraphQLDestinationModesWithParentStation( + List destinationModesWithParentStation + ) { + this.destinationModesWithParentStation = destinationModesWithParentStation; + } + + public void setGraphQLNumberOfLegs(Integer numberOfLegs) { + this.numberOfLegs = numberOfLegs; + } + + public void setGraphQLOriginModesWithParentStation( + List originModesWithParentStation + ) { + this.originModesWithParentStation = originModesWithParentStation; + } + } + + /** Identifies whether this stop represents a stop or station. */ + public enum GraphQLLocationType { + ENTRANCE, + STATION, + STOP, + } + + public enum GraphQLMode { + AIRPLANE, + BICYCLE, + BUS, + CABLE_CAR, + CAR, + CARPOOL, + COACH, + FERRY, + FLEX, + FLEXIBLE, + FUNICULAR, + GONDOLA, + LEG_SWITCH, + MONORAIL, + RAIL, + SCOOTER, + SUBWAY, + TAXI, + TRAM, + TRANSIT, + TROLLEYBUS, + WALK, + } + + /** Occupancy status of a vehicle. */ + public enum GraphQLOccupancyStatus { + CRUSHED_STANDING_ROOM_ONLY, + EMPTY, + FEW_SEATS_AVAILABLE, + FULL, + MANY_SEATS_AVAILABLE, + NOT_ACCEPTING_PASSENGERS, + NO_DATA_AVAILABLE, + STANDING_ROOM_ONLY, + } + + public static class GraphQLOpeningHoursDatesArgs { + + private List dates; + + public GraphQLOpeningHoursDatesArgs(Map args) { + if (args != null) { + this.dates = (List) args.get("dates"); + } + } + + public List getGraphQLDates() { + return this.dates; + } + + public void setGraphQLDates(List dates) { + this.dates = dates; + } + } + + /** Optimization type for bicycling legs */ + public enum GraphQLOptimizeType { + FLAT, + GREENWAYS, + QUICK, + SAFE, + TRIANGLE, + } + + public static class GraphQLParkingFilterInput { + + private List not; + private List select; + + public GraphQLParkingFilterInput(Map args) { + if (args != null) { + if (args.get("not") != null) { + this.not = (List) args.get("not"); + } + if (args.get("select") != null) { + this.select = (List) args.get("select"); + } + } + } + + public List getGraphQLNot() { + return this.not; + } + + public List getGraphQLSelect() { + return this.select; + } + + public void setGraphQLNot(List not) { + this.not = not; + } + + public void setGraphQLSelect(List select) { + this.select = select; } } - public static class GraphQLLegNextLegsArgs { + public static class GraphQLParkingFilterOperationInput { - private List destinationModesWithParentStation; - private Integer numberOfLegs; - private List originModesWithParentStation; + private List tags; - public GraphQLLegNextLegsArgs(Map args) { + public GraphQLParkingFilterOperationInput(Map args) { if (args != null) { - if (args.get("destinationModesWithParentStation") != null) { - this.destinationModesWithParentStation = - ((List) args.get("destinationModesWithParentStation")).stream() - .map(item -> - item instanceof GraphQLTransitMode - ? item - : GraphQLTransitMode.valueOf((String) item) - ) - .map(GraphQLTransitMode.class::cast) - .collect(Collectors.toList()); - } - this.numberOfLegs = (Integer) args.get("numberOfLegs"); - if (args.get("originModesWithParentStation") != null) { - this.originModesWithParentStation = - ((List) args.get("originModesWithParentStation")).stream() + this.tags = (List) args.get("tags"); + } + } + + public List getGraphQLTags() { + return this.tags; + } + + public void setGraphQLTags(List tags) { + this.tags = tags; + } + } + + public static class GraphQLPatternAlertsArgs { + + private List types; + + public GraphQLPatternAlertsArgs(Map args) { + if (args != null) { + if (args.get("types") != null) { + this.types = + ((List) args.get("types")).stream() .map(item -> - item instanceof GraphQLTransitMode + item instanceof GraphQLPatternAlertType ? item - : GraphQLTransitMode.valueOf((String) item) + : GraphQLPatternAlertType.valueOf((String) item) ) - .map(GraphQLTransitMode.class::cast) + .map(GraphQLPatternAlertType.class::cast) .collect(Collectors.toList()); } } } - public List getGraphQLDestinationModesWithParentStation() { - return this.destinationModesWithParentStation; + public List getGraphQLTypes() { + return this.types; } - public Integer getGraphQLNumberOfLegs() { - return this.numberOfLegs; + public void setGraphQLTypes(List types) { + this.types = types; } + } - public List getGraphQLOriginModesWithParentStation() { - return this.originModesWithParentStation; + public static class GraphQLPatternTripsForDateArgs { + + private String serviceDate; + + public GraphQLPatternTripsForDateArgs(Map args) { + if (args != null) { + this.serviceDate = (String) args.get("serviceDate"); + } } - public void setGraphQLDestinationModesWithParentStation( - List destinationModesWithParentStation - ) { - this.destinationModesWithParentStation = destinationModesWithParentStation; + public String getGraphQLServiceDate() { + return this.serviceDate; } - public void setGraphQLNumberOfLegs(Integer numberOfLegs) { - this.numberOfLegs = numberOfLegs; + public void setGraphQLServiceDate(String serviceDate) { + this.serviceDate = serviceDate; } + } - public void setGraphQLOriginModesWithParentStation( - List originModesWithParentStation - ) { - this.originModesWithParentStation = originModesWithParentStation; + /** Entities, which are relevant for a pattern and can contain alerts */ + public enum GraphQLPatternAlertType { + AGENCY, + PATTERN, + ROUTE, + ROUTE_TYPE, + STOPS_ON_PATTERN, + STOPS_ON_TRIPS, + TRIPS, + } + + public enum GraphQLPickupDropoffType { + CALL_AGENCY, + COORDINATE_WITH_DRIVER, + NONE, + SCHEDULED, + } + + /** Street modes that can be used for access to the transit network from origin. */ + public enum GraphQLPlanAccessMode { + BICYCLE, + BICYCLE_PARKING, + BICYCLE_RENTAL, + CAR_DROP_OFF, + CAR_PARKING, + CAR_RENTAL, + FLEX, + SCOOTER_RENTAL, + WALK, + } + + public static class GraphQLPlanCoordinateInput { + + private Double latitude; + private Double longitude; + + public GraphQLPlanCoordinateInput(Map args) { + if (args != null) { + this.latitude = (Double) args.get("latitude"); + this.longitude = (Double) args.get("longitude"); + } + } + + public Double getGraphQLLatitude() { + return this.latitude; + } + + public Double getGraphQLLongitude() { + return this.longitude; + } + + public void setGraphQLLatitude(Double latitude) { + this.latitude = latitude; + } + + public void setGraphQLLongitude(Double longitude) { + this.longitude = longitude; } } - /** Identifies whether this stop represents a stop or station. */ - public enum GraphQLLocationType { - ENTRANCE, - STATION, - STOP, + public static class GraphQLPlanDateTimeInput { + + private java.time.OffsetDateTime earliestDeparture; + private java.time.OffsetDateTime latestArrival; + + public GraphQLPlanDateTimeInput(Map args) { + if (args != null) { + this.earliestDeparture = (java.time.OffsetDateTime) args.get("earliestDeparture"); + this.latestArrival = (java.time.OffsetDateTime) args.get("latestArrival"); + } + } + + public java.time.OffsetDateTime getGraphQLEarliestDeparture() { + return this.earliestDeparture; + } + + public java.time.OffsetDateTime getGraphQLLatestArrival() { + return this.latestArrival; + } + + public void setGraphQLEarliestDeparture(java.time.OffsetDateTime earliestDeparture) { + this.earliestDeparture = earliestDeparture; + } + + public void setGraphQLLatestArrival(java.time.OffsetDateTime latestArrival) { + this.latestArrival = latestArrival; + } } - public enum GraphQLMode { - AIRPLANE, + /** Street mode that is used when searching for itineraries that don't use any transit. */ + public enum GraphQLPlanDirectMode { BICYCLE, - BUS, - CABLE_CAR, + BICYCLE_PARKING, + BICYCLE_RENTAL, CAR, - CARPOOL, - COACH, - FERRY, + CAR_PARKING, + CAR_RENTAL, FLEX, - FLEXIBLE, - FUNICULAR, - GONDOLA, - LEG_SWITCH, - MONORAIL, - RAIL, - SCOOTER, - SUBWAY, - TAXI, - TRAM, - TRANSIT, - TROLLEYBUS, + SCOOTER_RENTAL, + WALK, + } + + /** Street modes that can be used for egress from the transit network to destination. */ + public enum GraphQLPlanEgressMode { + BICYCLE, + BICYCLE_RENTAL, + CAR_PICKUP, + CAR_RENTAL, + FLEX, + SCOOTER_RENTAL, WALK, } - /** Occupancy status of a vehicle. */ - public enum GraphQLOccupancyStatus { - CRUSHED_STANDING_ROOM_ONLY, - EMPTY, - FEW_SEATS_AVAILABLE, - FULL, - MANY_SEATS_AVAILABLE, - NOT_ACCEPTING_PASSENGERS, - NO_DATA_AVAILABLE, - STANDING_ROOM_ONLY, + public static class GraphQLPlanItineraryFilterInput { + + private Double groupSimilarityKeepOne; + private Double groupSimilarityKeepThree; + private Double groupedOtherThanSameLegsMaxCostMultiplier; + private GraphQLItineraryFilterDebugProfile itineraryFilterDebugProfile; + + public GraphQLPlanItineraryFilterInput(Map args) { + if (args != null) { + this.groupSimilarityKeepOne = (Double) args.get("groupSimilarityKeepOne"); + this.groupSimilarityKeepThree = (Double) args.get("groupSimilarityKeepThree"); + this.groupedOtherThanSameLegsMaxCostMultiplier = + (Double) args.get("groupedOtherThanSameLegsMaxCostMultiplier"); + if (args.get("itineraryFilterDebugProfile") instanceof GraphQLItineraryFilterDebugProfile) { + this.itineraryFilterDebugProfile = + (GraphQLItineraryFilterDebugProfile) args.get("itineraryFilterDebugProfile"); + } else { + this.itineraryFilterDebugProfile = + GraphQLItineraryFilterDebugProfile.valueOf( + (String) args.get("itineraryFilterDebugProfile") + ); + } + } + } + + public Double getGraphQLGroupSimilarityKeepOne() { + return this.groupSimilarityKeepOne; + } + + public Double getGraphQLGroupSimilarityKeepThree() { + return this.groupSimilarityKeepThree; + } + + public Double getGraphQLGroupedOtherThanSameLegsMaxCostMultiplier() { + return this.groupedOtherThanSameLegsMaxCostMultiplier; + } + + public GraphQLItineraryFilterDebugProfile getGraphQLItineraryFilterDebugProfile() { + return this.itineraryFilterDebugProfile; + } + + public void setGraphQLGroupSimilarityKeepOne(Double groupSimilarityKeepOne) { + this.groupSimilarityKeepOne = groupSimilarityKeepOne; + } + + public void setGraphQLGroupSimilarityKeepThree(Double groupSimilarityKeepThree) { + this.groupSimilarityKeepThree = groupSimilarityKeepThree; + } + + public void setGraphQLGroupedOtherThanSameLegsMaxCostMultiplier( + Double groupedOtherThanSameLegsMaxCostMultiplier + ) { + this.groupedOtherThanSameLegsMaxCostMultiplier = groupedOtherThanSameLegsMaxCostMultiplier; + } + + public void setGraphQLItineraryFilterDebugProfile( + GraphQLItineraryFilterDebugProfile itineraryFilterDebugProfile + ) { + this.itineraryFilterDebugProfile = itineraryFilterDebugProfile; + } + } + + public static class GraphQLPlanLabeledLocationInput { + + private String label; + private GraphQLPlanLocationInput location; + + public GraphQLPlanLabeledLocationInput(Map args) { + if (args != null) { + this.label = (String) args.get("label"); + this.location = new GraphQLPlanLocationInput((Map) args.get("location")); + } + } + + public String getGraphQLLabel() { + return this.label; + } + + public GraphQLPlanLocationInput getGraphQLLocation() { + return this.location; + } + + public void setGraphQLLabel(String label) { + this.label = label; + } + + public void setGraphQLLocation(GraphQLPlanLocationInput location) { + this.location = location; + } + } + + public static class GraphQLPlanLocationInput { + + private GraphQLPlanCoordinateInput coordinate; + private GraphQLPlanStopLocationInput stopLocation; + + public GraphQLPlanLocationInput(Map args) { + if (args != null) { + this.coordinate = + new GraphQLPlanCoordinateInput((Map) args.get("coordinate")); + this.stopLocation = + new GraphQLPlanStopLocationInput((Map) args.get("stopLocation")); + } + } + + public GraphQLPlanCoordinateInput getGraphQLCoordinate() { + return this.coordinate; + } + + public GraphQLPlanStopLocationInput getGraphQLStopLocation() { + return this.stopLocation; + } + + public void setGraphQLCoordinate(GraphQLPlanCoordinateInput coordinate) { + this.coordinate = coordinate; + } + + public void setGraphQLStopLocation(GraphQLPlanStopLocationInput stopLocation) { + this.stopLocation = stopLocation; + } + } + + public static class GraphQLPlanModesInput { + + private List direct; + private Boolean directOnly; + private GraphQLPlanTransitModesInput transit; + private Boolean transitOnly; + + public GraphQLPlanModesInput(Map args) { + if (args != null) { + if (args.get("direct") != null) { + this.direct = + ((List) args.get("direct")).stream() + .map(item -> + item instanceof GraphQLPlanDirectMode + ? item + : GraphQLPlanDirectMode.valueOf((String) item) + ) + .map(GraphQLPlanDirectMode.class::cast) + .collect(Collectors.toList()); + } + this.directOnly = (Boolean) args.get("directOnly"); + this.transit = new GraphQLPlanTransitModesInput((Map) args.get("transit")); + this.transitOnly = (Boolean) args.get("transitOnly"); + } + } + + public List getGraphQLDirect() { + return this.direct; + } + + public Boolean getGraphQLDirectOnly() { + return this.directOnly; + } + + public GraphQLPlanTransitModesInput getGraphQLTransit() { + return this.transit; + } + + public Boolean getGraphQLTransitOnly() { + return this.transitOnly; + } + + public void setGraphQLDirect(List direct) { + this.direct = direct; + } + + public void setGraphQLDirectOnly(Boolean directOnly) { + this.directOnly = directOnly; + } + + public void setGraphQLTransit(GraphQLPlanTransitModesInput transit) { + this.transit = transit; + } + + public void setGraphQLTransitOnly(Boolean transitOnly) { + this.transitOnly = transitOnly; + } } - public static class GraphQLOpeningHoursDatesArgs { + public static class GraphQLPlanPreferencesInput { - private List dates; + private GraphQLAccessibilityPreferencesInput accessibility; + private GraphQLPlanStreetPreferencesInput street; + private GraphQLTransitPreferencesInput transit; - public GraphQLOpeningHoursDatesArgs(Map args) { + public GraphQLPlanPreferencesInput(Map args) { if (args != null) { - this.dates = (List) args.get("dates"); + this.accessibility = + new GraphQLAccessibilityPreferencesInput((Map) args.get("accessibility")); + this.street = + new GraphQLPlanStreetPreferencesInput((Map) args.get("street")); + this.transit = + new GraphQLTransitPreferencesInput((Map) args.get("transit")); } } - public List getGraphQLDates() { - return this.dates; + public GraphQLAccessibilityPreferencesInput getGraphQLAccessibility() { + return this.accessibility; } - public void setGraphQLDates(List dates) { - this.dates = dates; + public GraphQLPlanStreetPreferencesInput getGraphQLStreet() { + return this.street; + } + + public GraphQLTransitPreferencesInput getGraphQLTransit() { + return this.transit; + } + + public void setGraphQLAccessibility(GraphQLAccessibilityPreferencesInput accessibility) { + this.accessibility = accessibility; + } + + public void setGraphQLStreet(GraphQLPlanStreetPreferencesInput street) { + this.street = street; + } + + public void setGraphQLTransit(GraphQLTransitPreferencesInput transit) { + this.transit = transit; } } - /** Optimization type for bicycling legs */ - public enum GraphQLOptimizeType { - FLAT, - GREENWAYS, - QUICK, - SAFE, - TRIANGLE, + public static class GraphQLPlanStopLocationInput { + + private String stopLocationId; + private Boolean strict; + + public GraphQLPlanStopLocationInput(Map args) { + if (args != null) { + this.stopLocationId = (String) args.get("stopLocationId"); + this.strict = (Boolean) args.get("strict"); + } + } + + public String getGraphQLStopLocationId() { + return this.stopLocationId; + } + + public Boolean getGraphQLStrict() { + return this.strict; + } + + public void setGraphQLStopLocationId(String stopLocationId) { + this.stopLocationId = stopLocationId; + } + + public void setGraphQLStrict(Boolean strict) { + this.strict = strict; + } } - public static class GraphQLParkingFilterInput { + public static class GraphQLPlanStreetPreferencesInput { - private List not; - private List select; + private GraphQLBicyclePreferencesInput bicycle; + private GraphQLCarPreferencesInput car; + private GraphQLScooterPreferencesInput scooter; + private GraphQLWalkPreferencesInput walk; - public GraphQLParkingFilterInput(Map args) { + public GraphQLPlanStreetPreferencesInput(Map args) { if (args != null) { - if (args.get("not") != null) { - this.not = (List) args.get("not"); - } - if (args.get("select") != null) { - this.select = (List) args.get("select"); - } + this.bicycle = + new GraphQLBicyclePreferencesInput((Map) args.get("bicycle")); + this.car = new GraphQLCarPreferencesInput((Map) args.get("car")); + this.scooter = + new GraphQLScooterPreferencesInput((Map) args.get("scooter")); + this.walk = new GraphQLWalkPreferencesInput((Map) args.get("walk")); } } - public List getGraphQLNot() { - return this.not; + public GraphQLBicyclePreferencesInput getGraphQLBicycle() { + return this.bicycle; } - public List getGraphQLSelect() { - return this.select; + public GraphQLCarPreferencesInput getGraphQLCar() { + return this.car; } - public void setGraphQLNot(List not) { - this.not = not; + public GraphQLScooterPreferencesInput getGraphQLScooter() { + return this.scooter; } - public void setGraphQLSelect(List select) { - this.select = select; + public GraphQLWalkPreferencesInput getGraphQLWalk() { + return this.walk; + } + + public void setGraphQLBicycle(GraphQLBicyclePreferencesInput bicycle) { + this.bicycle = bicycle; + } + + public void setGraphQLCar(GraphQLCarPreferencesInput car) { + this.car = car; + } + + public void setGraphQLScooter(GraphQLScooterPreferencesInput scooter) { + this.scooter = scooter; + } + + public void setGraphQLWalk(GraphQLWalkPreferencesInput walk) { + this.walk = walk; } } - public static class GraphQLParkingFilterOperationInput { + public enum GraphQLPlanTransferMode { + BICYCLE, + WALK, + } - private List tags; + public static class GraphQLPlanTransitModePreferenceInput { - public GraphQLParkingFilterOperationInput(Map args) { + private GraphQLTransitModePreferenceCostInput cost; + private GraphQLTransitMode mode; + + public GraphQLPlanTransitModePreferenceInput(Map args) { if (args != null) { - this.tags = (List) args.get("tags"); + this.cost = + new GraphQLTransitModePreferenceCostInput((Map) args.get("cost")); + if (args.get("mode") instanceof GraphQLTransitMode) { + this.mode = (GraphQLTransitMode) args.get("mode"); + } else { + this.mode = GraphQLTransitMode.valueOf((String) args.get("mode")); + } } } - public List getGraphQLTags() { - return this.tags; + public GraphQLTransitModePreferenceCostInput getGraphQLCost() { + return this.cost; } - public void setGraphQLTags(List tags) { - this.tags = tags; + public GraphQLTransitMode getGraphQLMode() { + return this.mode; + } + + public void setGraphQLCost(GraphQLTransitModePreferenceCostInput cost) { + this.cost = cost; + } + + public void setGraphQLMode(GraphQLTransitMode mode) { + this.mode = mode; } } - public static class GraphQLPatternAlertsArgs { + public static class GraphQLPlanTransitModesInput { - private List types; + private List access; + private List egress; + private List transfer; + private List transit; - public GraphQLPatternAlertsArgs(Map args) { + public GraphQLPlanTransitModesInput(Map args) { if (args != null) { - if (args.get("types") != null) { - this.types = - ((List) args.get("types")).stream() + if (args.get("access") != null) { + this.access = + ((List) args.get("access")).stream() .map(item -> - item instanceof GraphQLPatternAlertType + item instanceof GraphQLPlanAccessMode ? item - : GraphQLPatternAlertType.valueOf((String) item) + : GraphQLPlanAccessMode.valueOf((String) item) ) - .map(GraphQLPatternAlertType.class::cast) + .map(GraphQLPlanAccessMode.class::cast) + .collect(Collectors.toList()); + } + if (args.get("egress") != null) { + this.egress = + ((List) args.get("egress")).stream() + .map(item -> + item instanceof GraphQLPlanEgressMode + ? item + : GraphQLPlanEgressMode.valueOf((String) item) + ) + .map(GraphQLPlanEgressMode.class::cast) + .collect(Collectors.toList()); + } + if (args.get("transfer") != null) { + this.transfer = + ((List) args.get("transfer")).stream() + .map(item -> + item instanceof GraphQLPlanTransferMode + ? item + : GraphQLPlanTransferMode.valueOf((String) item) + ) + .map(GraphQLPlanTransferMode.class::cast) .collect(Collectors.toList()); } + if (args.get("transit") != null) { + this.transit = (List) args.get("transit"); + } } } - public List getGraphQLTypes() { - return this.types; + public List getGraphQLAccess() { + return this.access; } - public void setGraphQLTypes(List types) { - this.types = types; + public List getGraphQLEgress() { + return this.egress; } - } - - public static class GraphQLPatternTripsForDateArgs { - private String serviceDate; + public List getGraphQLTransfer() { + return this.transfer; + } - public GraphQLPatternTripsForDateArgs(Map args) { - if (args != null) { - this.serviceDate = (String) args.get("serviceDate"); - } + public List getGraphQLTransit() { + return this.transit; } - public String getGraphQLServiceDate() { - return this.serviceDate; + public void setGraphQLAccess(List access) { + this.access = access; } - public void setGraphQLServiceDate(String serviceDate) { - this.serviceDate = serviceDate; + public void setGraphQLEgress(List egress) { + this.egress = egress; } - } - /** Entities, which are relevant for a pattern and can contain alerts */ - public enum GraphQLPatternAlertType { - AGENCY, - PATTERN, - ROUTE, - ROUTE_TYPE, - STOPS_ON_PATTERN, - STOPS_ON_TRIPS, - TRIPS, - } + public void setGraphQLTransfer(List transfer) { + this.transfer = transfer; + } - public enum GraphQLPickupDropoffType { - CALL_AGENCY, - COORDINATE_WITH_DRIVER, - NONE, - SCHEDULED, + public void setGraphQLTransit(List transit) { + this.transit = transit; + } } public enum GraphQLPropulsionType { @@ -2146,44 +3244,186 @@ public void setGraphQLTransportModes(List transportMo this.transportModes = transportModes; } - public void setGraphQLTriangle(GraphQLInputTriangleInput triangle) { - this.triangle = triangle; + public void setGraphQLTriangle(GraphQLInputTriangleInput triangle) { + this.triangle = triangle; + } + + public void setGraphQLUnpreferred(GraphQLInputUnpreferredInput unpreferred) { + this.unpreferred = unpreferred; + } + + public void setGraphQLWaitAtBeginningFactor(Double waitAtBeginningFactor) { + this.waitAtBeginningFactor = waitAtBeginningFactor; + } + + public void setGraphQLWaitReluctance(Double waitReluctance) { + this.waitReluctance = waitReluctance; + } + + public void setGraphQLWalkBoardCost(Integer walkBoardCost) { + this.walkBoardCost = walkBoardCost; + } + + public void setGraphQLWalkOnStreetReluctance(Double walkOnStreetReluctance) { + this.walkOnStreetReluctance = walkOnStreetReluctance; + } + + public void setGraphQLWalkReluctance(Double walkReluctance) { + this.walkReluctance = walkReluctance; + } + + public void setGraphQLWalkSafetyFactor(Double walkSafetyFactor) { + this.walkSafetyFactor = walkSafetyFactor; + } + + public void setGraphQLWalkSpeed(Double walkSpeed) { + this.walkSpeed = walkSpeed; + } + + public void setGraphQLWheelchair(Boolean wheelchair) { + this.wheelchair = wheelchair; + } + } + + public static class GraphQLQueryTypePlanConnectionArgs { + + private String after; + private String before; + private GraphQLPlanDateTimeInput dateTime; + private GraphQLPlanLabeledLocationInput destination; + private Integer first; + private GraphQLPlanItineraryFilterInput itineraryFilter; + private Integer last; + private java.util.Locale locale; + private GraphQLPlanModesInput modes; + private Integer numberOfItineraries; + private GraphQLPlanLabeledLocationInput origin; + private GraphQLPlanPreferencesInput preferences; + private java.time.Duration searchWindow; + + public GraphQLQueryTypePlanConnectionArgs(Map args) { + if (args != null) { + this.after = (String) args.get("after"); + this.before = (String) args.get("before"); + this.dateTime = new GraphQLPlanDateTimeInput((Map) args.get("dateTime")); + this.destination = + new GraphQLPlanLabeledLocationInput((Map) args.get("destination")); + this.first = (Integer) args.get("first"); + this.itineraryFilter = + new GraphQLPlanItineraryFilterInput((Map) args.get("itineraryFilter")); + this.last = (Integer) args.get("last"); + this.locale = (java.util.Locale) args.get("locale"); + this.modes = new GraphQLPlanModesInput((Map) args.get("modes")); + this.numberOfItineraries = (Integer) args.get("numberOfItineraries"); + this.origin = new GraphQLPlanLabeledLocationInput((Map) args.get("origin")); + this.preferences = + new GraphQLPlanPreferencesInput((Map) args.get("preferences")); + this.searchWindow = (java.time.Duration) args.get("searchWindow"); + } + } + + public String getGraphQLAfter() { + return this.after; + } + + public String getGraphQLBefore() { + return this.before; + } + + public GraphQLPlanDateTimeInput getGraphQLDateTime() { + return this.dateTime; + } + + public GraphQLPlanLabeledLocationInput getGraphQLDestination() { + return this.destination; + } + + public Integer getGraphQLFirst() { + return this.first; + } + + public GraphQLPlanItineraryFilterInput getGraphQLItineraryFilter() { + return this.itineraryFilter; + } + + public Integer getGraphQLLast() { + return this.last; + } + + public java.util.Locale getGraphQLLocale() { + return this.locale; + } + + public GraphQLPlanModesInput getGraphQLModes() { + return this.modes; + } + + public Integer getGraphQLNumberOfItineraries() { + return this.numberOfItineraries; + } + + public GraphQLPlanLabeledLocationInput getGraphQLOrigin() { + return this.origin; + } + + public GraphQLPlanPreferencesInput getGraphQLPreferences() { + return this.preferences; + } + + public java.time.Duration getGraphQLSearchWindow() { + return this.searchWindow; + } + + public void setGraphQLAfter(String after) { + this.after = after; + } + + public void setGraphQLBefore(String before) { + this.before = before; + } + + public void setGraphQLDateTime(GraphQLPlanDateTimeInput dateTime) { + this.dateTime = dateTime; + } + + public void setGraphQLDestination(GraphQLPlanLabeledLocationInput destination) { + this.destination = destination; } - public void setGraphQLUnpreferred(GraphQLInputUnpreferredInput unpreferred) { - this.unpreferred = unpreferred; + public void setGraphQLFirst(Integer first) { + this.first = first; } - public void setGraphQLWaitAtBeginningFactor(Double waitAtBeginningFactor) { - this.waitAtBeginningFactor = waitAtBeginningFactor; + public void setGraphQLItineraryFilter(GraphQLPlanItineraryFilterInput itineraryFilter) { + this.itineraryFilter = itineraryFilter; } - public void setGraphQLWaitReluctance(Double waitReluctance) { - this.waitReluctance = waitReluctance; + public void setGraphQLLast(Integer last) { + this.last = last; } - public void setGraphQLWalkBoardCost(Integer walkBoardCost) { - this.walkBoardCost = walkBoardCost; + public void setGraphQLLocale(java.util.Locale locale) { + this.locale = locale; } - public void setGraphQLWalkOnStreetReluctance(Double walkOnStreetReluctance) { - this.walkOnStreetReluctance = walkOnStreetReluctance; + public void setGraphQLModes(GraphQLPlanModesInput modes) { + this.modes = modes; } - public void setGraphQLWalkReluctance(Double walkReluctance) { - this.walkReluctance = walkReluctance; + public void setGraphQLNumberOfItineraries(Integer numberOfItineraries) { + this.numberOfItineraries = numberOfItineraries; } - public void setGraphQLWalkSafetyFactor(Double walkSafetyFactor) { - this.walkSafetyFactor = walkSafetyFactor; + public void setGraphQLOrigin(GraphQLPlanLabeledLocationInput origin) { + this.origin = origin; } - public void setGraphQLWalkSpeed(Double walkSpeed) { - this.walkSpeed = walkSpeed; + public void setGraphQLPreferences(GraphQLPlanPreferencesInput preferences) { + this.preferences = preferences; } - public void setGraphQLWheelchair(Boolean wheelchair) { - this.wheelchair = wheelchair; + public void setGraphQLSearchWindow(java.time.Duration searchWindow) { + this.searchWindow = searchWindow; } } @@ -2772,6 +4012,146 @@ public enum GraphQLRoutingErrorCode { WALKING_BETTER_THAN_TRANSIT, } + public static class GraphQLScooterOptimizationInput { + + private GraphQLTriangleScooterFactorsInput triangle; + private GraphQLScooterOptimizationType type; + + public GraphQLScooterOptimizationInput(Map args) { + if (args != null) { + this.triangle = + new GraphQLTriangleScooterFactorsInput((Map) args.get("triangle")); + if (args.get("type") instanceof GraphQLScooterOptimizationType) { + this.type = (GraphQLScooterOptimizationType) args.get("type"); + } else { + this.type = GraphQLScooterOptimizationType.valueOf((String) args.get("type")); + } + } + } + + public GraphQLTriangleScooterFactorsInput getGraphQLTriangle() { + return this.triangle; + } + + public GraphQLScooterOptimizationType getGraphQLType() { + return this.type; + } + + public void setGraphQLTriangle(GraphQLTriangleScooterFactorsInput triangle) { + this.triangle = triangle; + } + + public void setGraphQLType(GraphQLScooterOptimizationType type) { + this.type = type; + } + } + + /** + * Predefined optimization alternatives for scooter routing. For more customization, + * one can use the triangle factors. + */ + public enum GraphQLScooterOptimizationType { + FLAT_STREETS, + SAFEST_STREETS, + SAFE_STREETS, + SHORTEST_DURATION, + } + + public static class GraphQLScooterPreferencesInput { + + private GraphQLScooterOptimizationInput optimization; + private Double reluctance; + private GraphQLScooterRentalPreferencesInput rental; + private Double speed; + + public GraphQLScooterPreferencesInput(Map args) { + if (args != null) { + this.optimization = + new GraphQLScooterOptimizationInput((Map) args.get("optimization")); + this.reluctance = (Double) args.get("reluctance"); + this.rental = + new GraphQLScooterRentalPreferencesInput((Map) args.get("rental")); + this.speed = (Double) args.get("speed"); + } + } + + public GraphQLScooterOptimizationInput getGraphQLOptimization() { + return this.optimization; + } + + public Double getGraphQLReluctance() { + return this.reluctance; + } + + public GraphQLScooterRentalPreferencesInput getGraphQLRental() { + return this.rental; + } + + public Double getGraphQLSpeed() { + return this.speed; + } + + public void setGraphQLOptimization(GraphQLScooterOptimizationInput optimization) { + this.optimization = optimization; + } + + public void setGraphQLReluctance(Double reluctance) { + this.reluctance = reluctance; + } + + public void setGraphQLRental(GraphQLScooterRentalPreferencesInput rental) { + this.rental = rental; + } + + public void setGraphQLSpeed(Double speed) { + this.speed = speed; + } + } + + public static class GraphQLScooterRentalPreferencesInput { + + private List allowedNetworks; + private List bannedNetworks; + private GraphQLDestinationScooterPolicyInput destinationScooterPolicy; + + public GraphQLScooterRentalPreferencesInput(Map args) { + if (args != null) { + this.allowedNetworks = (List) args.get("allowedNetworks"); + this.bannedNetworks = (List) args.get("bannedNetworks"); + this.destinationScooterPolicy = + new GraphQLDestinationScooterPolicyInput( + (Map) args.get("destinationScooterPolicy") + ); + } + } + + public List getGraphQLAllowedNetworks() { + return this.allowedNetworks; + } + + public List getGraphQLBannedNetworks() { + return this.bannedNetworks; + } + + public GraphQLDestinationScooterPolicyInput getGraphQLDestinationScooterPolicy() { + return this.destinationScooterPolicy; + } + + public void setGraphQLAllowedNetworks(List allowedNetworks) { + this.allowedNetworks = allowedNetworks; + } + + public void setGraphQLBannedNetworks(List bannedNetworks) { + this.bannedNetworks = bannedNetworks; + } + + public void setGraphQLDestinationScooterPolicy( + GraphQLDestinationScooterPolicyInput destinationScooterPolicy + ) { + this.destinationScooterPolicy = destinationScooterPolicy; + } + } + public static class GraphQLStopAlertsArgs { private List types; @@ -3133,6 +4513,99 @@ public void setGraphQLLanguage(String language) { } } + public static class GraphQLTimetablePreferencesInput { + + private Boolean excludeRealTimeUpdates; + private Boolean includePlannedCancellations; + private Boolean includeRealTimeCancellations; + + public GraphQLTimetablePreferencesInput(Map args) { + if (args != null) { + this.excludeRealTimeUpdates = (Boolean) args.get("excludeRealTimeUpdates"); + this.includePlannedCancellations = (Boolean) args.get("includePlannedCancellations"); + this.includeRealTimeCancellations = (Boolean) args.get("includeRealTimeCancellations"); + } + } + + public Boolean getGraphQLExcludeRealTimeUpdates() { + return this.excludeRealTimeUpdates; + } + + public Boolean getGraphQLIncludePlannedCancellations() { + return this.includePlannedCancellations; + } + + public Boolean getGraphQLIncludeRealTimeCancellations() { + return this.includeRealTimeCancellations; + } + + public void setGraphQLExcludeRealTimeUpdates(Boolean excludeRealTimeUpdates) { + this.excludeRealTimeUpdates = excludeRealTimeUpdates; + } + + public void setGraphQLIncludePlannedCancellations(Boolean includePlannedCancellations) { + this.includePlannedCancellations = includePlannedCancellations; + } + + public void setGraphQLIncludeRealTimeCancellations(Boolean includeRealTimeCancellations) { + this.includeRealTimeCancellations = includeRealTimeCancellations; + } + } + + public static class GraphQLTransferPreferencesInput { + + private org.opentripplanner.framework.model.Cost cost; + private Integer maximumAdditionalTransfers; + private Integer maximumTransfers; + private java.time.Duration slack; + + public GraphQLTransferPreferencesInput(Map args) { + if (args != null) { + this.cost = (org.opentripplanner.framework.model.Cost) args.get("cost"); + this.maximumAdditionalTransfers = (Integer) args.get("maximumAdditionalTransfers"); + this.maximumTransfers = (Integer) args.get("maximumTransfers"); + this.slack = (java.time.Duration) args.get("slack"); + } + } + + public org.opentripplanner.framework.model.Cost getGraphQLCost() { + return this.cost; + } + + public Integer getGraphQLMaximumAdditionalTransfers() { + return this.maximumAdditionalTransfers; + } + + public Integer getGraphQLMaximumTransfers() { + return this.maximumTransfers; + } + + public java.time.Duration getGraphQLSlack() { + return this.slack; + } + + public void setGraphQLCost(org.opentripplanner.framework.model.Cost cost) { + this.cost = cost; + } + + public void setGraphQLMaximumAdditionalTransfers(Integer maximumAdditionalTransfers) { + this.maximumAdditionalTransfers = maximumAdditionalTransfers; + } + + public void setGraphQLMaximumTransfers(Integer maximumTransfers) { + this.maximumTransfers = maximumTransfers; + } + + public void setGraphQLSlack(java.time.Duration slack) { + this.slack = slack; + } + } + + /** + * Transit modes include modes that are used within organized transportation networks + * run by public transportation authorities, taxi companies etc. + * Equivalent to GTFS route_type or to NeTEx TransportMode. + */ public enum GraphQLTransitMode { AIRPLANE, BUS, @@ -3150,6 +4623,86 @@ public enum GraphQLTransitMode { TROLLEYBUS, } + public static class GraphQLTransitModePreferenceCostInput { + + private org.opentripplanner.framework.model.Cost cost; + private Double reluctance; + + public GraphQLTransitModePreferenceCostInput(Map args) { + if (args != null) { + this.cost = (org.opentripplanner.framework.model.Cost) args.get("cost"); + this.reluctance = (Double) args.get("reluctance"); + } + } + + public org.opentripplanner.framework.model.Cost getGraphQLCost() { + return this.cost; + } + + public Double getGraphQLReluctance() { + return this.reluctance; + } + + public void setGraphQLCost(org.opentripplanner.framework.model.Cost cost) { + this.cost = cost; + } + + public void setGraphQLReluctance(Double reluctance) { + this.reluctance = reluctance; + } + } + + public static class GraphQLTransitPreferencesInput { + + private GraphQLAlightPreferencesInput alight; + private GraphQLBoardPreferencesInput board; + private GraphQLTimetablePreferencesInput timetable; + private GraphQLTransferPreferencesInput transfer; + + public GraphQLTransitPreferencesInput(Map args) { + if (args != null) { + this.alight = new GraphQLAlightPreferencesInput((Map) args.get("alight")); + this.board = new GraphQLBoardPreferencesInput((Map) args.get("board")); + this.timetable = + new GraphQLTimetablePreferencesInput((Map) args.get("timetable")); + this.transfer = + new GraphQLTransferPreferencesInput((Map) args.get("transfer")); + } + } + + public GraphQLAlightPreferencesInput getGraphQLAlight() { + return this.alight; + } + + public GraphQLBoardPreferencesInput getGraphQLBoard() { + return this.board; + } + + public GraphQLTimetablePreferencesInput getGraphQLTimetable() { + return this.timetable; + } + + public GraphQLTransferPreferencesInput getGraphQLTransfer() { + return this.transfer; + } + + public void setGraphQLAlight(GraphQLAlightPreferencesInput alight) { + this.alight = alight; + } + + public void setGraphQLBoard(GraphQLBoardPreferencesInput board) { + this.board = board; + } + + public void setGraphQLTimetable(GraphQLTimetablePreferencesInput timetable) { + this.timetable = timetable; + } + + public void setGraphQLTransfer(GraphQLTransferPreferencesInput transfer) { + this.transfer = transfer; + } + } + public static class GraphQLTransportModeInput { private GraphQLMode mode; @@ -3187,6 +4740,84 @@ public void setGraphQLQualifier(GraphQLQualifier qualifier) { } } + public static class GraphQLTriangleCyclingFactorsInput { + + private Double flatness; + private Double safety; + private Double time; + + public GraphQLTriangleCyclingFactorsInput(Map args) { + if (args != null) { + this.flatness = (Double) args.get("flatness"); + this.safety = (Double) args.get("safety"); + this.time = (Double) args.get("time"); + } + } + + public Double getGraphQLFlatness() { + return this.flatness; + } + + public Double getGraphQLSafety() { + return this.safety; + } + + public Double getGraphQLTime() { + return this.time; + } + + public void setGraphQLFlatness(Double flatness) { + this.flatness = flatness; + } + + public void setGraphQLSafety(Double safety) { + this.safety = safety; + } + + public void setGraphQLTime(Double time) { + this.time = time; + } + } + + public static class GraphQLTriangleScooterFactorsInput { + + private Double flatness; + private Double safety; + private Double time; + + public GraphQLTriangleScooterFactorsInput(Map args) { + if (args != null) { + this.flatness = (Double) args.get("flatness"); + this.safety = (Double) args.get("safety"); + this.time = (Double) args.get("time"); + } + } + + public Double getGraphQLFlatness() { + return this.flatness; + } + + public Double getGraphQLSafety() { + return this.safety; + } + + public Double getGraphQLTime() { + return this.time; + } + + public void setGraphQLFlatness(Double flatness) { + this.flatness = flatness; + } + + public void setGraphQLSafety(Double safety) { + this.safety = safety; + } + + public void setGraphQLTime(Double time) { + this.time = time; + } + } + public static class GraphQLTripAlertsArgs { private List types; @@ -3408,9 +5039,77 @@ public enum GraphQLVertexType { TRANSIT, } + public static class GraphQLWalkPreferencesInput { + + private org.opentripplanner.framework.model.Cost boardCost; + private Double reluctance; + private Double speed; + private Double walkSafetyFactor; + + public GraphQLWalkPreferencesInput(Map args) { + if (args != null) { + this.boardCost = (org.opentripplanner.framework.model.Cost) args.get("boardCost"); + this.reluctance = (Double) args.get("reluctance"); + this.speed = (Double) args.get("speed"); + this.walkSafetyFactor = (Double) args.get("walkSafetyFactor"); + } + } + + public org.opentripplanner.framework.model.Cost getGraphQLBoardCost() { + return this.boardCost; + } + + public Double getGraphQLReluctance() { + return this.reluctance; + } + + public Double getGraphQLSpeed() { + return this.speed; + } + + public Double getGraphQLWalkSafetyFactor() { + return this.walkSafetyFactor; + } + + public void setGraphQLBoardCost(org.opentripplanner.framework.model.Cost boardCost) { + this.boardCost = boardCost; + } + + public void setGraphQLReluctance(Double reluctance) { + this.reluctance = reluctance; + } + + public void setGraphQLSpeed(Double speed) { + this.speed = speed; + } + + public void setGraphQLWalkSafetyFactor(Double walkSafetyFactor) { + this.walkSafetyFactor = walkSafetyFactor; + } + } + public enum GraphQLWheelchairBoarding { NOT_POSSIBLE, NO_INFORMATION, POSSIBLE, } + + public static class GraphQLWheelchairPreferencesInput { + + private Boolean enabled; + + public GraphQLWheelchairPreferencesInput(Map args) { + if (args != null) { + this.enabled = (Boolean) args.get("enabled"); + } + } + + public Boolean getGraphQLEnabled() { + return this.enabled; + } + + public void setGraphQLEnabled(Boolean enabled) { + this.enabled = enabled; + } + } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml index c141266d2e6..ccc33e2617b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml @@ -27,6 +27,14 @@ config: GeoJson: org.locationtech.jts.geom.Geometry Grams: org.opentripplanner.framework.model.Grams Duration: java.time.Duration + Cost: org.opentripplanner.framework.model.Cost + CoordinateValue: Double + Locale: java.util.Locale + OffsetDateTime: java.time.OffsetDateTime + Speed: Double + Reluctance: Double + Ratio: Double + mappers: AbsoluteDirection: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLAbsoluteDirection#GraphQLAbsoluteDirection Agency: org.opentripplanner.transit.model.organization.Agency#Agency @@ -70,6 +78,9 @@ config: placeAtDistanceConnection: graphql.relay.Connection#Connection placeAtDistanceEdge: graphql.relay.Edge#Edge Plan: graphql.execution.DataFetcherResult + PlanConnection: graphql.relay.Connection#Connection + PlanEdge: graphql.relay.Edge#Edge + PlanLocation: org.opentripplanner.apis.gtfs.model.PlanLocation#PlanLocation RealtimeState: String RelativeDirection: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLRelativeDirection#GraphQLRelativeDirection Route: org.opentripplanner.transit.model.network.Route#Route diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLocation.java b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLocation.java new file mode 100644 index 00000000000..d7d80f12687 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLocation.java @@ -0,0 +1,12 @@ +package org.opentripplanner.apis.gtfs.model; + +import graphql.TypeResolutionEnvironment; +import graphql.schema.GraphQLObjectType; +import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; + +public interface PlanLocation extends GraphQLDataFetchers.GraphQLPlanLocation { + @Override + default GraphQLObjectType getType(TypeResolutionEnvironment env) { + return null; + } +} From 03f9f28d05a79b80c31a8fe571faafb2d46a592e Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 12 Feb 2024 12:54:10 +0200 Subject: [PATCH 092/165] Add @oneOf back to schema and don't use it for optimization inputs @oneOf can't be used if we want to set default values for input fields --- .../apis/gtfs/generated/GraphQLTypes.java | 2 ++ .../opentripplanner/apis/gtfs/schema.graphqls | 31 +++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index c528f023cdf..238490ea304 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -618,6 +618,7 @@ public enum GraphQLCyclingOptimizationType { SAFEST_STREETS, SAFE_STREETS, SHORTEST_DURATION, + TRIANGLE, } public static class GraphQLDepartureRowStoptimesArgs { @@ -4055,6 +4056,7 @@ public enum GraphQLScooterOptimizationType { SAFEST_STREETS, SAFE_STREETS, SHORTEST_DURATION, + TRIANGLE, } public static class GraphQLScooterPreferencesInput { diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 8dcae649bd6..07d8c9a0c30 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -8,6 +8,11 @@ This is only worth it when the execution is long running, i.e. more than ~50 mil """ directive @async on FIELD_DEFINITION +""" +Exactly one of the fields on an input object must be set and non-null while all others are omitted. +""" +directive @oneOf on INPUT_OBJECT + schema { query: QueryType } @@ -3011,14 +3016,15 @@ type BookingInfo { """ What criteria should be used when optimizing a cycling route. """ -input CyclingOptimizationInput @oneOf { +input CyclingOptimizationInput { """ Use one of the predefined optimization types. """ - type: CyclingOptimizationType + type: CyclingOptimizationType! """ - Define optimization by weighing three criteria. + Define optimization by weighing three criteria. This should only be used when + optimization type is `TRIANGLE`. """ triangle: TriangleCyclingFactorsInput } @@ -3055,19 +3061,26 @@ enum CyclingOptimizationType { by taking into account road surface etc. This option was previously called `GREENWAYS`. """ SAFEST_STREETS + + """ + Allows more fine-tuned configuration of how much different optimization criteria are considered in + routing. If this is defined, `triangle` values should be set as well. + """ + TRIANGLE } """ What criteria should be used when optimizing a scooter route. """ -input ScooterOptimizationInput @oneOf { +input ScooterOptimizationInput { """ Use one of the predefined optimization types. """ - type: ScooterOptimizationType + type: ScooterOptimizationType! """ - Define optimization by weighing three criteria. + Define optimization by weighing three criteria. This should only be used when + optimization type is `TRIANGLE`. """ triangle: TriangleScooterFactorsInput } @@ -3108,6 +3121,12 @@ enum ScooterOptimizationType { This option was previously called `GREENWAYS`. """ SAFEST_STREETS + + """ + Allows more fine-tuned configuration of how much different optimization criteria are considered in + routing. If this is defined, `triangle` values should be set as well. + """ + TRIANGLE } """ From 38d00209ed99bf7d6a355fd3bcc65bb515847397 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 12 Feb 2024 17:38:31 +0200 Subject: [PATCH 093/165] Update magidoc and remove @oneOf definition from schema --- .github/workflows/cibuild.yml | 2 +- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index 28a84953af2..df994a7af73 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -119,7 +119,7 @@ jobs: - name: Build GTFS GraphQL API documentation run: | - npm install -g @magidoc/cli@4.0.0 + npm install -g @magidoc/cli@4.1.4 magidoc generate - name: Deploy compiled HTML to Github pages diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 07d8c9a0c30..943c2a34227 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -8,11 +8,6 @@ This is only worth it when the execution is long running, i.e. more than ~50 mil """ directive @async on FIELD_DEFINITION -""" -Exactly one of the fields on an input object must be set and non-null while all others are omitted. -""" -directive @oneOf on INPUT_OBJECT - schema { query: QueryType } From 95ac495fea7e3a56c7ddb2934b1232330b0b7051 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 16 Feb 2024 10:34:09 +0200 Subject: [PATCH 094/165] Bring back @oneOf directive Magidoc version update didn't fix the need for it --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 943c2a34227..07d8c9a0c30 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -8,6 +8,11 @@ This is only worth it when the execution is long running, i.e. more than ~50 mil """ directive @async on FIELD_DEFINITION +""" +Exactly one of the fields on an input object must be set and non-null while all others are omitted. +""" +directive @oneOf on INPUT_OBJECT + schema { query: QueryType } From b178183843d90fd7c146dbddc7768f140f8dd7bf Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 16 Feb 2024 10:57:00 +0200 Subject: [PATCH 095/165] Add @specificiedBy to some scalars --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 07d8c9a0c30..7bf91552932 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1040,7 +1040,7 @@ type Coordinate { """ Either a latitude or a longitude as a WGS84 format floating point number. """ -scalar CoordinateValue +scalar CoordinateValue @specifiedBy(url: "https://earth-info.nga.mil/?dir=wgs84&action=wgs84") type Coordinates { """Latitude (WGS 84)""" @@ -1257,7 +1257,7 @@ type Geometry { points: Polyline } -scalar GeoJson +scalar GeoJson @specifiedBy(url: "https://www.rfcreader.com/#rfc7946") scalar Grams @@ -1418,7 +1418,7 @@ input InputPreferred { """ Locale in the format defined in [RFC5646](https://datatracker.ietf.org/doc/html/rfc5646). For example, `en` or `en-US`. """ -scalar Locale +scalar Locale @specifiedBy(url: "https://www.rfcreader.com/#rfc5646") """ Preferences for car parking facilities used during the routing. @@ -2169,12 +2169,12 @@ type Money { """ An ISO-8601-formatted datetime with offset, i.e. `2023-06-13T14:30+03:00` for 2:30pm on June 13th 2023 at Helsinki's offset from UTC at that time. """ -scalar OffsetDateTime +scalar OffsetDateTime @specifiedBy(url: "https://www.rfcreader.com/#rfc3339") """ An ISO-8601-formatted duration, i.e. `PT2H30M` for 2 hours and 30 minutes. """ -scalar Duration +scalar Duration @specifiedBy(url: "https://www.rfcreader.com/#rfc3339") type RideHailingProvider { "The ID of the ride hailing provider." @@ -3381,7 +3381,7 @@ List of coordinates in an encoded polyline format (see https://developers.google.com/maps/documentation/utilities/polylinealgorithm). The value appears in JSON as a string. """ -scalar Polyline +scalar Polyline @specifiedBy(url: "https://developers.google.com/maps/documentation/utilities/polylinealgorithm") """ Additional qualifier for a transport mode. From 1f112e8a8a5f3e4ef9126bc1a47d9faa81177653 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 19 Feb 2024 12:35:23 +0200 Subject: [PATCH 096/165] Update OffsetDateTime scalar and implement coordinate scalar --- .../apis/gtfs/GraphQLScalars.java | 79 ++++++++++++++++--- .../framework/time/OffsetDateTimeParser.java | 42 ++++++++++ 2 files changed, 109 insertions(+), 12 deletions(-) create mode 100644 src/main/java/org/opentripplanner/framework/time/OffsetDateTimeParser.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index 2361ef4141b..eca5136bd27 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -3,6 +3,8 @@ import com.bedatadriven.jackson.datatype.jts.JtsModule; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import graphql.language.FloatValue; +import graphql.language.IntValue; import graphql.language.StringValue; import graphql.relay.Relay; import graphql.schema.Coercing; @@ -18,6 +20,7 @@ import org.opentripplanner.framework.geometry.GeometryUtils; import org.opentripplanner.framework.graphql.scalar.DurationScalarFactory; import org.opentripplanner.framework.model.Grams; +import org.opentripplanner.framework.time.OffsetDateTimeParser; public class GraphQLScalars { @@ -54,7 +57,7 @@ public String parseLiteral(Object input) { ) .build(); - public static GraphQLScalarType offsetDateTimeScalar = GraphQLScalarType + public static final GraphQLScalarType offsetDateTimeScalar = GraphQLScalarType .newScalar() .name("OffsetDateTime") .coercing( @@ -66,41 +69,93 @@ public String serialize(@Nonnull Object dataFetcherResult) return zdt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); } else if (dataFetcherResult instanceof OffsetDateTime odt) { return odt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); - } else return null; + } else { + throw new CoercingSerializeException( + "Cannot serialize object of class %s".formatted( + dataFetcherResult.getClass().getSimpleName() + ) + ); + } } @Override public OffsetDateTime parseValue(Object input) throws CoercingParseValueException { - return null; + if (input instanceof CharSequence cs) { + return OffsetDateTimeParser.parseLeniently(cs).orElseThrow(() -> valueException(input)); + } + throw valueException(input); } @Override public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralException { - return null; + if (input instanceof StringValue sv) { + return OffsetDateTimeParser + .parseLeniently(sv.getValue()) + .orElseThrow(() -> literalException(input)); + } + throw literalException(input); + } + + private static CoercingParseValueException valueException(Object input) { + return new CoercingParseValueException("Cannot parse %s".formatted(input)); + } + + private static CoercingParseLiteralException literalException(Object input) { + return new CoercingParseLiteralException("Cannot parse %s".formatted(input)); } } ) .build(); - public static GraphQLScalarType coordinateValueScalar = GraphQLScalarType + public static final GraphQLScalarType coordinateValueScalar = GraphQLScalarType .newScalar() .name("CoordinateValue") .coercing( - new Coercing() { + new Coercing() { @Override - public String serialize(@Nonnull Object dataFetcherResult) + public Double serialize(@Nonnull Object dataFetcherResult) throws CoercingSerializeException { - return null; + if (dataFetcherResult instanceof Double doubleValue) { + return doubleValue; + } else if (dataFetcherResult instanceof Float floatValue) { + return floatValue.doubleValue(); + } else { + throw new CoercingSerializeException( + "Cannot serialize object of class %s as a coordinate number".formatted( + dataFetcherResult.getClass().getSimpleName() + ) + ); + } } @Override - public OffsetDateTime parseValue(Object input) throws CoercingParseValueException { - return null; + public Double parseValue(Object input) throws CoercingParseValueException { + if (input instanceof Double doubleValue) { + return validateCoordinate(doubleValue); + } + throw new CoercingParseValueException( + "Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input) + ); } @Override - public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralException { - return null; + public Double parseLiteral(Object input) throws CoercingParseLiteralException { + if (input instanceof FloatValue coordinate) { + return validateCoordinate(coordinate.getValue().doubleValue()); + } + if (input instanceof IntValue coordinate) { + return validateCoordinate(coordinate.getValue().doubleValue()); + } + throw new CoercingParseLiteralException( + "Expected a number, got: " + input.getClass().getSimpleName() + ); + } + + private static double validateCoordinate(double coordinate) { + if (coordinate >= -180.001 && coordinate <= 180.001) { + return coordinate; + } + throw new CoercingParseLiteralException("Not a valid WGS84 coordinate value"); } } ) diff --git a/src/main/java/org/opentripplanner/framework/time/OffsetDateTimeParser.java b/src/main/java/org/opentripplanner/framework/time/OffsetDateTimeParser.java new file mode 100644 index 00000000000..11652d797a7 --- /dev/null +++ b/src/main/java/org/opentripplanner/framework/time/OffsetDateTimeParser.java @@ -0,0 +1,42 @@ +package org.opentripplanner.framework.time; + +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.util.Optional; + +public class OffsetDateTimeParser { + + /** + * We need to have two offsets, in order to parse both "+0200" and "+02:00". The first is not + * really ISO-8601 compatible with the extended date and time. We need to make parsing strict, in + * order to keep the minute mandatory, otherwise we would be left with an unparsed minute + */ + public static final DateTimeFormatter LENIENT_PARSER = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .parseLenient() + .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + .optionalStart() + .parseStrict() + .appendOffset("+HH:MM:ss", "Z") + .parseLenient() + .optionalEnd() + .optionalStart() + .appendOffset("+HHmmss", "Z") + .optionalEnd() + .toFormatter(); + + /** + * Parses a ISO-8601 string into am OffsetDateTime instance allowing the offset to be both in + * '02:00' and '0200' format. + */ + public static Optional parseLeniently(CharSequence input) { + try { + var result = OffsetDateTime.parse(input, LENIENT_PARSER); + return Optional.of(result); + } catch (DateTimeParseException e) { + return Optional.empty(); + } + } +} From 8187ad5306f37e60181b2fe8bdbc1c4e088e3ee3 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 19 Feb 2024 13:03:22 +0200 Subject: [PATCH 097/165] Make origin and destination required --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 7bf91552932..f717fb555a2 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4266,12 +4266,12 @@ type QueryType { """ The origin where the search starts. Usually coordinates but can also be a stop location. """ - origin: PlanLabeledLocationInput + origin: PlanLabeledLocationInput! """ The destination where the search ends. Usually coordinates but can also be a stop location. """ - destination: PlanLabeledLocationInput + destination: PlanLabeledLocationInput! """ Street and transit modes used during the search. This also includes options to only return From 62a68ac070ac31a44944814253d029ae97ebabed Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 19 Feb 2024 13:42:08 +0200 Subject: [PATCH 098/165] Rename old route request mapper --- ...per.java => LegacyRouteRequestMapper.java} | 2 +- ...java => LegacyRouteRequestMapperTest.java} | 37 +++++++++++++++---- 2 files changed, 30 insertions(+), 9 deletions(-) rename src/main/java/org/opentripplanner/apis/gtfs/mapping/{RouteRequestMapper.java => LegacyRouteRequestMapper.java} (99%) rename src/test/java/org/opentripplanner/apis/gtfs/mapping/{RouteRequestMapperTest.java => LegacyRouteRequestMapperTest.java} (89%) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapper.java similarity index 99% rename from src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java rename to src/main/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapper.java index 5bc00cb7a85..7355658d89c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapper.java @@ -32,7 +32,7 @@ import org.opentripplanner.transit.model.basic.MainAndSubMode; import org.opentripplanner.transit.model.basic.TransitMode; -public class RouteRequestMapper { +public class LegacyRouteRequestMapper { @Nonnull public static RouteRequest toRouteRequest( diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapperTest.java similarity index 89% rename from src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java rename to src/test/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapperTest.java index 85736291ada..58081ea5b4f 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapperTest.java @@ -83,7 +83,7 @@ void parkingFilters() { var env = executionContext(arguments); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var routeRequest = LegacyRouteRequestMapper.toRouteRequest(env, context); assertNotNull(routeRequest); @@ -113,7 +113,10 @@ void parkingFilters() { void banning(Map banned, String expectedFilters) { Map arguments = Map.of("banned", banned); - var routeRequest = RouteRequestMapper.toRouteRequest(executionContext(arguments), context); + var routeRequest = LegacyRouteRequestMapper.toRouteRequest( + executionContext(arguments), + context + ); assertNotNull(routeRequest); assertEquals(expectedFilters, routeRequest.journey().transit().filters().toString()); @@ -137,7 +140,10 @@ void banning(Map banned, String expectedFilters) { void modes(List> modes, String expectedFilters) { Map arguments = Map.of("transportModes", modes); - var routeRequest = RouteRequestMapper.toRouteRequest(executionContext(arguments), context); + var routeRequest = LegacyRouteRequestMapper.toRouteRequest( + executionContext(arguments), + context + ); assertNotNull(routeRequest); assertEquals(expectedFilters, routeRequest.journey().transit().filters().toString()); @@ -150,7 +156,10 @@ private static Map mode(String mode) { @Test void defaultBikeOptimize() { Map arguments = Map.of(); - var routeRequest = RouteRequestMapper.toRouteRequest(executionContext(arguments), context); + var routeRequest = LegacyRouteRequestMapper.toRouteRequest( + executionContext(arguments), + context + ); assertEquals(SAFE_STREETS, routeRequest.preferences().bike().optimizeType()); } @@ -163,7 +172,10 @@ void bikeTriangle() { Map.of("safetyFactor", 0.2, "slopeFactor", 0.1, "timeFactor", 0.7) ); - var routeRequest = RouteRequestMapper.toRouteRequest(executionContext(arguments), context); + var routeRequest = LegacyRouteRequestMapper.toRouteRequest( + executionContext(arguments), + context + ); assertEquals(TRIANGLE, routeRequest.preferences().bike().optimizeType()); assertEquals( @@ -187,7 +199,10 @@ void noTriangle(GraphQLTypes.GraphQLOptimizeType bot) { Map.of("safetyFactor", 0.2, "slopeFactor", 0.1, "timeFactor", 0.7) ); - var routeRequest = RouteRequestMapper.toRouteRequest(executionContext(arguments), context); + var routeRequest = LegacyRouteRequestMapper.toRouteRequest( + executionContext(arguments), + context + ); assertEquals(OptimizationTypeMapper.map(bot), routeRequest.preferences().bike().optimizeType()); assertEquals( @@ -201,10 +216,16 @@ void walkReluctance() { var reluctance = 119d; Map arguments = Map.of("walkReluctance", reluctance); - var routeRequest = RouteRequestMapper.toRouteRequest(executionContext(arguments), context); + var routeRequest = LegacyRouteRequestMapper.toRouteRequest( + executionContext(arguments), + context + ); assertEquals(reluctance, routeRequest.preferences().walk().reluctance()); - var noParamsRequest = RouteRequestMapper.toRouteRequest(executionContext(Map.of()), context); + var noParamsRequest = LegacyRouteRequestMapper.toRouteRequest( + executionContext(Map.of()), + context + ); assertNotEquals(reluctance, noParamsRequest.preferences().walk().reluctance()); } From cd56144e43964d292935e938c5672492828009f1 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 19 Feb 2024 13:49:13 +0200 Subject: [PATCH 099/165] Add basic implementation for planConnection Still need to add support for a lot of input parameters --- .../apis/gtfs/GtfsGraphQLIndex.java | 2 + .../gtfs/datafetchers/PlanConnectionImpl.java | 108 ++++++++++++++++++ .../apis/gtfs/datafetchers/QueryTypeImpl.java | 27 +++-- .../gtfs/generated/GraphQLDataFetchers.java | 13 ++- .../apis/gtfs/generated/graphql-codegen.yml | 6 +- .../apis/gtfs/mapping/RouteRequestMapper.java | 66 +++++++++++ .../apis/gtfs/model/PlanLabeledLocation.java | 3 + .../apis/gtfs/model/PlanLocation.java | 20 +++- .../apis/gtfs/model/PlanPageInfo.java | 12 ++ .../framework/graphql/GraphQLUtils.java | 19 ++- 10 files changed, 254 insertions(+), 22 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlanConnectionImpl.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/model/PlanLabeledLocation.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index 5e065b8227b..07b0f615b25 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -51,6 +51,7 @@ import org.opentripplanner.apis.gtfs.datafetchers.PatternImpl; import org.opentripplanner.apis.gtfs.datafetchers.PlaceImpl; import org.opentripplanner.apis.gtfs.datafetchers.PlaceInterfaceTypeResolver; +import org.opentripplanner.apis.gtfs.datafetchers.PlanConnectionImpl; import org.opentripplanner.apis.gtfs.datafetchers.PlanImpl; import org.opentripplanner.apis.gtfs.datafetchers.QueryTypeImpl; import org.opentripplanner.apis.gtfs.datafetchers.RentalVehicleImpl; @@ -159,6 +160,7 @@ protected static GraphQLSchema buildSchema() { .type(typeWiring.build(PatternImpl.class)) .type(typeWiring.build(PlaceImpl.class)) .type(typeWiring.build(placeAtDistanceImpl.class)) + .type(typeWiring.build(PlanConnectionImpl.class)) .type(typeWiring.build(PlanImpl.class)) .type(typeWiring.build(QueryTypeImpl.class)) .type(typeWiring.build(RouteImpl.class)) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlanConnectionImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlanConnectionImpl.java new file mode 100644 index 00000000000..168f58b3224 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlanConnectionImpl.java @@ -0,0 +1,108 @@ +package org.opentripplanner.apis.gtfs.datafetchers; + +import graphql.relay.ConnectionCursor; +import graphql.relay.DefaultConnectionCursor; +import graphql.relay.DefaultEdge; +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import java.time.Duration; +import java.time.OffsetDateTime; +import org.opentripplanner.apis.gtfs.GraphQLRequestContext; +import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; +import org.opentripplanner.apis.gtfs.model.PlanLabeledLocation; +import org.opentripplanner.apis.gtfs.model.PlanPageInfo; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Place; +import org.opentripplanner.routing.api.response.RoutingError; +import org.opentripplanner.routing.api.response.RoutingResponse; +import org.opentripplanner.transit.service.TransitService; + +public class PlanConnectionImpl implements GraphQLDataFetchers.GraphQLPlanConnection { + + @Override + public DataFetcher searchDateTime() { + return environment -> { + var transitService = getTransitService(environment); + var instant = getSource(environment).getTripPlan().date; + return instant.atOffset(transitService.getTimeZone().getRules().getOffset(instant)); + }; + } + + @Override + public DataFetcher origin() { + return environment -> getLocation(getSource(environment).getTripPlan().from); + } + + @Override + public DataFetcher>> edges() { + return environment -> + getSource(environment) + .getTripPlan() + .itineraries.stream() + .map(itinerary -> new DefaultEdge<>(itinerary, new DefaultConnectionCursor("NoCursor"))) + .toList(); + } + + @Override + public DataFetcher> routingErrors() { + return environment -> getSource(environment).getRoutingErrors(); + } + + @Override + public DataFetcher destination() { + return environment -> getLocation(getSource(environment).getTripPlan().to); + } + + @Override + public DataFetcher pageInfo() { + return environment -> { + var startCursor = getSource(environment).getNextPageCursor() != null + ? getSource(environment).getPreviousPageCursor().encode() + : null; + ConnectionCursor startConnectionCursor = null; + if (startCursor != null) { + startConnectionCursor = new DefaultConnectionCursor(startCursor); + } + var endCursor = getSource(environment).getPreviousPageCursor() != null + ? getSource(environment).getNextPageCursor().encode() + : null; + ConnectionCursor endConnectionCursor = null; + if (endCursor != null) { + endConnectionCursor = new DefaultConnectionCursor(endCursor); + } + Duration searchWindowUsed = null; + var metadata = getSource(environment).getMetadata(); + if (metadata != null) { + searchWindowUsed = metadata.searchWindowUsed; + } + return new PlanPageInfo( + startConnectionCursor, + endConnectionCursor, + startCursor != null, + endCursor != null, + searchWindowUsed + ); + }; + } + + private PlanLabeledLocation getLocation(Place place) { + Object location = null; + var stop = place.stop; + var coordinate = place.coordinate; + if (stop != null) { + location = stop; + } else if (coordinate != null) { + location = coordinate; + } + // TODO make label field that is only the label + return new PlanLabeledLocation(location, place.name.toString()); + } + + private TransitService getTransitService(DataFetchingEnvironment environment) { + return environment.getContext().transitService(); + } + + private RoutingResponse getSource(DataFetchingEnvironment environment) { + return environment.getSource(); + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index b7a85b96c73..c63882561e9 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -30,6 +30,7 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLQueryTypeStopsByRadiusArgs; +import org.opentripplanner.apis.gtfs.mapping.LegacyRouteRequestMapper; import org.opentripplanner.apis.gtfs.mapping.RouteRequestMapper; import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.ext.fares.impl.GtfsFaresService; @@ -39,7 +40,6 @@ import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; import org.opentripplanner.gtfs.mapping.DirectionMapper; import org.opentripplanner.model.TripTimeOnDate; -import org.opentripplanner.model.plan.Itinerary; import org.opentripplanner.routing.alertpatch.EntitySelector; import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.routing.api.request.RouteRequest; @@ -481,21 +481,17 @@ public DataFetcher> patterns() { public DataFetcher> plan() { return environment -> { GraphQLRequestContext context = environment.getContext(); - RouteRequest request = RouteRequestMapper.toRouteRequest(environment, context); - RoutingResponse res = context.routingService().route(request); - return DataFetcherResult - .newResult() - .data(res) - .localContext(Map.of("locale", request.locale())) - .build(); + RouteRequest request = LegacyRouteRequestMapper.toRouteRequest(environment, context); + return getPlanResult(context, request); }; } @Override - public DataFetcher> planConnection() { + public DataFetcher> planConnection() { return environment -> { - List itineraries = List.of(); - return new SimpleListConnection<>(itineraries).get(environment); + GraphQLRequestContext context = environment.getContext(); + RouteRequest request = RouteRequestMapper.toRouteRequest(environment, context); + return getPlanResult(context, request); }; } @@ -942,6 +938,15 @@ private GraphFinder getGraphFinder(DataFetchingEnvironment environment) { return environment.getContext().graphFinder(); } + private DataFetcherResult getPlanResult(GraphQLRequestContext context, RouteRequest request) { + RoutingResponse res = context.routingService().route(request); + return DataFetcherResult + .newResult() + .data(res) + .localContext(Map.of("locale", request.locale())) + .build(); + } + protected static List filterAlerts( Collection alerts, GraphQLTypes.GraphQLQueryTypeAlertsArgs args diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 7ded260c70f..c942e771ba6 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -2,6 +2,7 @@ package org.opentripplanner.apis.gtfs.generated; import graphql.relay.Connection; +import graphql.relay.DefaultEdge; import graphql.relay.Edge; import graphql.schema.DataFetcher; import graphql.schema.TypeResolver; @@ -20,7 +21,9 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLRelativeDirection; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLRoutingErrorCode; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLTransitMode; +import org.opentripplanner.apis.gtfs.model.PlanLabeledLocation; import org.opentripplanner.apis.gtfs.model.PlanLocation; +import org.opentripplanner.apis.gtfs.model.PlanPageInfo; import org.opentripplanner.apis.gtfs.model.RideHailingProvider; import org.opentripplanner.apis.gtfs.model.StopPosition; import org.opentripplanner.apis.gtfs.model.TripOccupancy; @@ -664,13 +667,13 @@ public interface GraphQLPlan { * [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). */ public interface GraphQLPlanConnection { - public DataFetcher destination(); + public DataFetcher destination(); - public DataFetcher>> edges(); + public DataFetcher>> edges(); - public DataFetcher origin(); + public DataFetcher origin(); - public DataFetcher pageInfo(); + public DataFetcher pageInfo(); public DataFetcher> routingErrors(); @@ -766,7 +769,7 @@ public interface GraphQLQueryType { public DataFetcher> plan(); - public DataFetcher> planConnection(); + public DataFetcher> planConnection(); public DataFetcher rentalVehicle(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml index ccc33e2617b..244ff5fbb61 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml @@ -78,9 +78,11 @@ config: placeAtDistanceConnection: graphql.relay.Connection#Connection placeAtDistanceEdge: graphql.relay.Edge#Edge Plan: graphql.execution.DataFetcherResult - PlanConnection: graphql.relay.Connection#Connection - PlanEdge: graphql.relay.Edge#Edge + PlanConnection: graphql.execution.DataFetcherResult + PlanEdge: graphql.relay.DefaultEdge#DefaultEdge + PlanLabeledLocation: org.opentripplanner.apis.gtfs.model.PlanLabeledLocation#PlanLabeledLocation PlanLocation: org.opentripplanner.apis.gtfs.model.PlanLocation#PlanLocation + PlanPageInfo: org.opentripplanner.apis.gtfs.model.PlanPageInfo#PlanPageInfo RealtimeState: String RelativeDirection: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLRelativeDirection#GraphQLRelativeDirection Route: org.opentripplanner.transit.model.network.Route#Route diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java new file mode 100644 index 00000000000..82cd5816132 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -0,0 +1,66 @@ +package org.opentripplanner.apis.gtfs.mapping; + +import graphql.schema.DataFetchingEnvironment; +import java.time.Instant; +import javax.annotation.Nonnull; +import org.opentripplanner.apis.gtfs.GraphQLRequestContext; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.framework.graphql.GraphQLUtils; +import org.opentripplanner.model.GenericLocation; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.transit.model.framework.FeedScopedId; + +public class RouteRequestMapper { + + @Nonnull + public static RouteRequest toRouteRequest( + DataFetchingEnvironment environment, + GraphQLRequestContext context + ) { + RouteRequest request = context.defaultRouteRequest(); + + var args = new GraphQLTypes.GraphQLQueryTypePlanConnectionArgs(environment.getArguments()); + var dateTime = args.getGraphQLDateTime(); + if (dateTime.getGraphQLEarliestDeparture() != null) { + request.setDateTime(args.getGraphQLDateTime().getGraphQLEarliestDeparture().toInstant()); + } else if (dateTime.getGraphQLLatestArrival() != null) { + request.setDateTime(args.getGraphQLDateTime().getGraphQLLatestArrival().toInstant()); + } else { + request.setDateTime(Instant.now()); + } + request.setFrom(parseGenericLocation(args.getGraphQLOrigin())); + request.setTo(parseGenericLocation(args.getGraphQLDestination())); + request.setLocale(GraphQLUtils.getLocale(environment, args.getGraphQLLocale())); + + return request; + } + + private static GenericLocation parseGenericLocation( + GraphQLTypes.GraphQLPlanLabeledLocationInput locationInput + ) { + var stopLocation = locationInput.getGraphQLLocation().getGraphQLStopLocation(); + if (stopLocation.getGraphQLStopLocationId() != null) { + // TODO implement strict + var stopId = stopLocation.getGraphQLStopLocationId(); + if (FeedScopedId.isValidString(stopId)) { + // TODO make label field that is only the label + return new GenericLocation( + locationInput.getGraphQLLabel(), + FeedScopedId.parse(stopId), + null, + null + ); + } else { + throw new IllegalArgumentException("Stop id %s is not of valid format.".formatted(stopId)); + } + } + + var coordinate = locationInput.getGraphQLLocation().getGraphQLCoordinate(); + return new GenericLocation( + locationInput.getGraphQLLabel(), + null, + coordinate.getGraphQLLatitude(), + coordinate.getGraphQLLongitude() + ); + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLabeledLocation.java b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLabeledLocation.java new file mode 100644 index 00000000000..aa316e98058 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLabeledLocation.java @@ -0,0 +1,3 @@ +package org.opentripplanner.apis.gtfs.model; + +public record PlanLabeledLocation(Object location, String label) {} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLocation.java b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLocation.java index d7d80f12687..5bf1db4e2f9 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLocation.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLocation.java @@ -2,11 +2,25 @@ import graphql.TypeResolutionEnvironment; import graphql.schema.GraphQLObjectType; -import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; +import graphql.schema.GraphQLSchema; +import graphql.schema.TypeResolver; +import org.opentripplanner.framework.geometry.WgsCoordinate; +import org.opentripplanner.transit.model.site.StopLocation; + +public class PlanLocation implements TypeResolver { -public interface PlanLocation extends GraphQLDataFetchers.GraphQLPlanLocation { @Override - default GraphQLObjectType getType(TypeResolutionEnvironment env) { + public GraphQLObjectType getType(TypeResolutionEnvironment env) { + Object o = env.getObject(); + GraphQLSchema schema = env.getSchema(); + + if (o instanceof StopLocation) { + return schema.getObjectType("Stop"); + } + if (o instanceof WgsCoordinate) { + return schema.getObjectType("Coordinate"); + } + return null; } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java new file mode 100644 index 00000000000..e4953227a6c --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java @@ -0,0 +1,12 @@ +package org.opentripplanner.apis.gtfs.model; + +import graphql.relay.ConnectionCursor; +import java.time.Duration; + +public record PlanPageInfo( + ConnectionCursor startCursor, + ConnectionCursor endCursor, + boolean hasPreviousPage, + boolean hasNextPage, + Duration searchWindowUsed +) {} diff --git a/src/main/java/org/opentripplanner/framework/graphql/GraphQLUtils.java b/src/main/java/org/opentripplanner/framework/graphql/GraphQLUtils.java index 906ba37a892..334513fd419 100644 --- a/src/main/java/org/opentripplanner/framework/graphql/GraphQLUtils.java +++ b/src/main/java/org/opentripplanner/framework/graphql/GraphQLUtils.java @@ -17,7 +17,12 @@ public static String getTranslation(I18NString input, DataFetchingEnvironment en } public static Locale getLocale(DataFetchingEnvironment environment) { - return getLocale(environment, environment.getArgument("language")); + var localeString = environment.getArgument("language"); + if (localeString != null) { + return Locale.forLanguageTag((String) localeString); + } + + return getLocaleFromEnvironment(environment); } public static Locale getLocale(DataFetchingEnvironment environment, String localeString) { @@ -25,6 +30,18 @@ public static Locale getLocale(DataFetchingEnvironment environment, String local return Locale.forLanguageTag(localeString); } + return getLocaleFromEnvironment(environment); + } + + public static Locale getLocale(DataFetchingEnvironment environment, Locale locale) { + if (locale != null) { + return locale; + } + + return getLocaleFromEnvironment(environment); + } + + public static Locale getLocaleFromEnvironment(DataFetchingEnvironment environment) { // This can come from the accept-language header var userLocale = environment.getLocale(); var defaultLocale = getDefaultLocale(environment); From 40405ea821e713faf522c258e8628a5ebcb9d350 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 19 Feb 2024 14:56:06 +0200 Subject: [PATCH 100/165] Implement searchWindow, cursor and number of itineraries --- .../apis/gtfs/mapping/RouteRequestMapper.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index 82cd5816132..00bf506955f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -31,6 +31,23 @@ public static RouteRequest toRouteRequest( request.setFrom(parseGenericLocation(args.getGraphQLOrigin())); request.setTo(parseGenericLocation(args.getGraphQLDestination())); request.setLocale(GraphQLUtils.getLocale(environment, args.getGraphQLLocale())); + if (args.getGraphQLSearchWindow() != null) { + request.setSearchWindow(args.getGraphQLSearchWindow()); + } + + if (args.getGraphQLBefore() != null) { + request.setPageCursorFromEncoded(args.getGraphQLBefore()); + if (args.getGraphQLLast() != null) { + request.setNumItineraries(args.getGraphQLLast()); + } + } else if (args.getGraphQLAfter() != null) { + request.setPageCursorFromEncoded(args.getGraphQLAfter()); + if (args.getGraphQLFirst() != null) { + request.setNumItineraries(args.getGraphQLFirst()); + } + } else if (args.getGraphQLNumberOfItineraries() != null) { + request.setNumItineraries(args.getGraphQLNumberOfItineraries()); + } return request; } From b66adfc2fcca9fc403c323c82418d87615e070d7 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 19 Feb 2024 16:47:29 +0200 Subject: [PATCH 101/165] Implement itinerary filters and ratio scalar --- .../apis/gtfs/GraphQLScalars.java | 58 ++++++++++++++++--- .../ItineraryFilterDebugProfileMapper.java | 21 +++++++ .../apis/gtfs/mapping/RouteRequestMapper.java | 35 +++++++++++ 3 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/ItineraryFilterDebugProfileMapper.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index eca5136bd27..e91215a765a 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -15,6 +15,7 @@ import java.time.OffsetDateTime; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.util.Optional; import javax.annotation.Nonnull; import org.locationtech.jts.geom.Geometry; import org.opentripplanner.framework.geometry.GeometryUtils; @@ -264,25 +265,66 @@ public Grams parseLiteral(Object input) throws CoercingParseLiteralException { ) .build(); - public static GraphQLScalarType ratioScalar = GraphQLScalarType + public static final GraphQLScalarType ratioScalar = GraphQLScalarType .newScalar() .name("Ratio") .coercing( - new Coercing() { + new Coercing() { @Override - public String serialize(@Nonnull Object dataFetcherResult) + public Double serialize(@Nonnull Object dataFetcherResult) throws CoercingSerializeException { - return null; + var validationException = new CoercingSerializeException( + "Value is under 0 or greater than 1." + ); + if (dataFetcherResult instanceof Double doubleValue) { + return validateRatio(doubleValue).orElseThrow(() -> validationException); + } else if (dataFetcherResult instanceof Float floatValue) { + return validateRatio(floatValue.doubleValue()).orElseThrow(() -> validationException); + } else { + throw new CoercingSerializeException( + "Cannot serialize object of class %s as a ratio".formatted( + dataFetcherResult.getClass().getSimpleName() + ) + ); + } } @Override - public OffsetDateTime parseValue(Object input) throws CoercingParseValueException { - return null; + public Double parseValue(Object input) throws CoercingParseValueException { + if (input instanceof Double doubleValue) { + return validateRatio(doubleValue) + .orElseThrow(() -> + new CoercingParseValueException("Value is under 0 or greater than 1.") + ); + } + throw new CoercingParseValueException( + "Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input) + ); } @Override - public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralException { - return null; + public Double parseLiteral(Object input) throws CoercingParseLiteralException { + var validationException = new CoercingParseLiteralException( + "Value is under 0 or greater than 1." + ); + if (input instanceof FloatValue coordinate) { + return validateRatio(coordinate.getValue().doubleValue()) + .orElseThrow(() -> validationException); + } + if (input instanceof IntValue coordinate) { + return validateRatio(coordinate.getValue().doubleValue()) + .orElseThrow(() -> validationException); + } + throw new CoercingParseLiteralException( + "Expected a number, got: " + input.getClass().getSimpleName() + ); + } + + private static Optional validateRatio(double ratio) { + if (ratio >= -0.001 && ratio <= 1.001) { + return Optional.of(ratio); + } + return Optional.empty(); } } ) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/ItineraryFilterDebugProfileMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/ItineraryFilterDebugProfileMapper.java new file mode 100644 index 00000000000..40242783755 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/ItineraryFilterDebugProfileMapper.java @@ -0,0 +1,21 @@ +package org.opentripplanner.apis.gtfs.mapping; + +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile; + +/** + * Maps ItineraryFilterDebugProfile from API to internal model. + */ +public class ItineraryFilterDebugProfileMapper { + + public static ItineraryFilterDebugProfile map( + GraphQLTypes.GraphQLItineraryFilterDebugProfile profile + ) { + return switch (profile) { + case LIMIT_TO_NUMBER_OF_ITINERARIES -> ItineraryFilterDebugProfile.LIMIT_TO_NUM_OF_ITINERARIES; + case LIMIT_TO_SEARCH_WINDOW -> ItineraryFilterDebugProfile.LIMIT_TO_SEARCH_WINDOW; + case LIST_ALL -> ItineraryFilterDebugProfile.LIST_ALL; + case OFF -> ItineraryFilterDebugProfile.OFF; + }; + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index 00bf506955f..e25c887c87c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -8,6 +8,8 @@ import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences; +import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.transit.model.framework.FeedScopedId; public class RouteRequestMapper { @@ -49,9 +51,42 @@ public static RouteRequest toRouteRequest( request.setNumItineraries(args.getGraphQLNumberOfItineraries()); } + request.withPreferences(preferences -> setPreferences(preferences, args)); + return request; } + private static void setPreferences( + RoutingPreferences.Builder prefs, + GraphQLTypes.GraphQLQueryTypePlanConnectionArgs args + ) { + prefs.withItineraryFilter(filters -> + setItineraryFilters(filters, args.getGraphQLItineraryFilter()) + ); + } + + private static void setItineraryFilters( + ItineraryFilterPreferences.Builder filterPreferences, + GraphQLTypes.GraphQLPlanItineraryFilterInput filters + ) { + if (filters.getGraphQLItineraryFilterDebugProfile() != null) { + filterPreferences.withDebug( + ItineraryFilterDebugProfileMapper.map(filters.getGraphQLItineraryFilterDebugProfile()) + ); + } + if (filters.getGraphQLGroupSimilarityKeepOne() != null) { + filterPreferences.withGroupSimilarityKeepOne(filters.getGraphQLGroupSimilarityKeepOne()); + } + if (filters.getGraphQLGroupSimilarityKeepThree() != null) { + filterPreferences.withGroupSimilarityKeepThree(filters.getGraphQLGroupSimilarityKeepThree()); + } + if (filters.getGraphQLGroupedOtherThanSameLegsMaxCostMultiplier() != null) { + filterPreferences.withGroupedOtherThanSameLegsMaxCostMultiplier( + filters.getGraphQLGroupedOtherThanSameLegsMaxCostMultiplier() + ); + } + } + private static GenericLocation parseGenericLocation( GraphQLTypes.GraphQLPlanLabeledLocationInput locationInput ) { From 58bd1a49e1f5246a8bb78d9aee66b6b661a78d6b Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 19 Feb 2024 17:22:05 +0200 Subject: [PATCH 102/165] Refactor so that correct exception is thrown --- .../apis/gtfs/GraphQLScalars.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index e91215a765a..19b511ea7ce 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -132,7 +132,10 @@ public Double serialize(@Nonnull Object dataFetcherResult) @Override public Double parseValue(Object input) throws CoercingParseValueException { if (input instanceof Double doubleValue) { - return validateCoordinate(doubleValue); + return validateCoordinate(doubleValue) + .orElseThrow(() -> + new CoercingParseValueException("Not a valid WGS84 coordinate value") + ); } throw new CoercingParseValueException( "Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input) @@ -141,22 +144,27 @@ public Double parseValue(Object input) throws CoercingParseValueException { @Override public Double parseLiteral(Object input) throws CoercingParseLiteralException { + var validationException = new CoercingParseLiteralException( + "Not a valid WGS84 coordinate value" + ); if (input instanceof FloatValue coordinate) { - return validateCoordinate(coordinate.getValue().doubleValue()); + return validateCoordinate(coordinate.getValue().doubleValue()) + .orElseThrow(() -> validationException); } if (input instanceof IntValue coordinate) { - return validateCoordinate(coordinate.getValue().doubleValue()); + return validateCoordinate(coordinate.getValue().doubleValue()) + .orElseThrow(() -> validationException); } throw new CoercingParseLiteralException( "Expected a number, got: " + input.getClass().getSimpleName() ); } - private static double validateCoordinate(double coordinate) { + private static Optional validateCoordinate(double coordinate) { if (coordinate >= -180.001 && coordinate <= 180.001) { - return coordinate; + return Optional.of(coordinate); } - throw new CoercingParseLiteralException("Not a valid WGS84 coordinate value"); + return Optional.empty(); } } ) From aff064c491c2c5453872e32d76c2fbd9292d39f7 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 20 Feb 2024 23:15:06 +0200 Subject: [PATCH 103/165] Set default for number of itineraries --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index f717fb555a2..dcaf267c2c6 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4261,7 +4261,7 @@ type QueryType { is a possibility that more itineraries are used later on (limiting the number of returned itineraries does not affect the performance by a lot but can affect the network bandwidth usage). """ - numberOfItineraries: Int + numberOfItineraries: Int = 50 """ The origin where the search starts. Usually coordinates but can also be a stop location. From 367b4025c5e63071eb3be0f38db9ea9705cb11e8 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 20 Feb 2024 23:37:28 +0200 Subject: [PATCH 104/165] Remove static transit mode cost from schema --- .../apis/gtfs/generated/GraphQLTypes.java | 10 ---------- .../org/opentripplanner/apis/gtfs/schema.graphqls | 7 +------ 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 238490ea304..05957c32b17 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -4627,28 +4627,18 @@ public enum GraphQLTransitMode { public static class GraphQLTransitModePreferenceCostInput { - private org.opentripplanner.framework.model.Cost cost; private Double reluctance; public GraphQLTransitModePreferenceCostInput(Map args) { if (args != null) { - this.cost = (org.opentripplanner.framework.model.Cost) args.get("cost"); this.reluctance = (Double) args.get("reluctance"); } } - public org.opentripplanner.framework.model.Cost getGraphQLCost() { - return this.cost; - } - public Double getGraphQLReluctance() { return this.reluctance; } - public void setGraphQLCost(org.opentripplanner.framework.model.Cost cost) { - this.cost = cost; - } - public void setGraphQLReluctance(Double reluctance) { this.reluctance = reluctance; } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index dcaf267c2c6..691a1e4ac55 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -5231,15 +5231,10 @@ input PlanTransitModePreferenceInput { Costs related to using a transit mode. """ input TransitModePreferenceCostInput { - """ - A static cost that is added each time a transit mode is used. - """ - cost: Cost - """ A cost multiplier of transit leg travel time. """ - reluctance: Reluctance + reluctance: Reluctance! } """ From 41fac36758a7d1441feb6c0b690643c7c0e5edde Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 20 Feb 2024 23:55:40 +0200 Subject: [PATCH 105/165] Add basic implementation for modes arguments --- .../apis/gtfs/mapping/AccessModeMapper.java | 24 ++++ .../apis/gtfs/mapping/DirectModeMapper.java | 24 ++++ .../apis/gtfs/mapping/EgressModeMapper.java | 22 ++++ .../apis/gtfs/mapping/RouteRequestMapper.java | 116 +++++++++++++++++- .../apis/gtfs/mapping/TransferModeMapper.java | 17 +++ .../apis/gtfs/mapping/TransitModeMapper.java | 29 +++++ 6 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/AccessModeMapper.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectModeMapper.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/EgressModeMapper.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/TransferModeMapper.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/TransitModeMapper.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/AccessModeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/AccessModeMapper.java new file mode 100644 index 00000000000..5b26c8d5ee1 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/AccessModeMapper.java @@ -0,0 +1,24 @@ +package org.opentripplanner.apis.gtfs.mapping; + +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.routing.api.request.StreetMode; + +/** + * Maps access street mode from API to internal model. + */ +public class AccessModeMapper { + + public static StreetMode map(GraphQLTypes.GraphQLPlanAccessMode mode) { + return switch (mode) { + case BICYCLE -> StreetMode.BIKE; + case BICYCLE_RENTAL -> StreetMode.BIKE_RENTAL; + case BICYCLE_PARKING -> StreetMode.BIKE_TO_PARK; + case CAR_RENTAL -> StreetMode.CAR_RENTAL; + case CAR_PARKING -> StreetMode.CAR_TO_PARK; + case CAR_DROP_OFF -> StreetMode.CAR_PICKUP; + case FLEX -> StreetMode.FLEXIBLE; + case SCOOTER_RENTAL -> StreetMode.SCOOTER_RENTAL; + case WALK -> StreetMode.WALK; + }; + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectModeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectModeMapper.java new file mode 100644 index 00000000000..2e7fcee3d2f --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectModeMapper.java @@ -0,0 +1,24 @@ +package org.opentripplanner.apis.gtfs.mapping; + +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.routing.api.request.StreetMode; + +/** + * Maps direct street mode from API to internal model. + */ +public class DirectModeMapper { + + public static StreetMode map(GraphQLTypes.GraphQLPlanDirectMode mode) { + return switch (mode) { + case BICYCLE -> StreetMode.BIKE; + case BICYCLE_RENTAL -> StreetMode.BIKE_RENTAL; + case BICYCLE_PARKING -> StreetMode.BIKE_TO_PARK; + case CAR -> StreetMode.CAR; + case CAR_RENTAL -> StreetMode.CAR_RENTAL; + case CAR_PARKING -> StreetMode.CAR_TO_PARK; + case FLEX -> StreetMode.FLEXIBLE; + case SCOOTER_RENTAL -> StreetMode.SCOOTER_RENTAL; + case WALK -> StreetMode.WALK; + }; + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/EgressModeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/EgressModeMapper.java new file mode 100644 index 00000000000..4033b3e516d --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/EgressModeMapper.java @@ -0,0 +1,22 @@ +package org.opentripplanner.apis.gtfs.mapping; + +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.routing.api.request.StreetMode; + +/** + * Maps egress street mode from API to internal model. + */ +public class EgressModeMapper { + + public static StreetMode map(GraphQLTypes.GraphQLPlanEgressMode mode) { + return switch (mode) { + case BICYCLE -> StreetMode.BIKE; + case BICYCLE_RENTAL -> StreetMode.BIKE_RENTAL; + case CAR_RENTAL -> StreetMode.CAR_RENTAL; + case CAR_PICKUP -> StreetMode.CAR_PICKUP; + case FLEX -> StreetMode.FLEXIBLE; + case SCOOTER_RENTAL -> StreetMode.SCOOTER_RENTAL; + case WALK -> StreetMode.WALK; + }; + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index e25c887c87c..b08f4b245f9 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -2,14 +2,22 @@ import graphql.schema.DataFetchingEnvironment; import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import javax.annotation.Nonnull; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; +import org.opentripplanner.routing.api.request.preference.TransitPreferences; +import org.opentripplanner.routing.api.request.request.filter.SelectRequest; +import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest; +import org.opentripplanner.transit.model.basic.MainAndSubMode; import org.opentripplanner.transit.model.framework.FeedScopedId; public class RouteRequestMapper { @@ -51,18 +59,22 @@ public static RouteRequest toRouteRequest( request.setNumItineraries(args.getGraphQLNumberOfItineraries()); } - request.withPreferences(preferences -> setPreferences(preferences, args)); + request.withPreferences(preferences -> setPreferences(preferences, args, environment)); + + setModes(request, args.getGraphQLModes(), environment); return request; } private static void setPreferences( RoutingPreferences.Builder prefs, - GraphQLTypes.GraphQLQueryTypePlanConnectionArgs args + GraphQLTypes.GraphQLQueryTypePlanConnectionArgs args, + DataFetchingEnvironment environment ) { prefs.withItineraryFilter(filters -> setItineraryFilters(filters, args.getGraphQLItineraryFilter()) ); + prefs.withTransit(transit -> setTransitPreferences(transit, args, environment)); } private static void setItineraryFilters( @@ -87,6 +99,106 @@ private static void setItineraryFilters( } } + private static void setTransitPreferences( + TransitPreferences.Builder preferences, + GraphQLTypes.GraphQLQueryTypePlanConnectionArgs args, + DataFetchingEnvironment environment + ) { + var modes = args.getGraphQLModes(); + var transit = getTransitModes(environment); + if (!Boolean.TRUE.equals(modes.getGraphQLDirectOnly()) && transit.size() > 0) { + // TODO what to do with the static cost? + var reluctanceForMode = transit + .stream() + .filter(mode -> mode.containsKey("cost")) + .collect( + Collectors.toMap( + mode -> + TransitModeMapper.map( + GraphQLTypes.GraphQLTransitMode.valueOf((String) mode.get("mode")) + ), + mode -> (Double) ((Map) mode.get("cost")).get("reluctance") + ) + ); + preferences.setReluctanceForMode(reluctanceForMode); + } + } + + /** + * TODO this doesn't support multiple street modes yet + */ + private static void setModes( + RouteRequest request, + GraphQLTypes.GraphQLPlanModesInput modesInput, + DataFetchingEnvironment environment + ) { + var direct = modesInput.getGraphQLDirect(); + if (Boolean.TRUE.equals(modesInput.getGraphQLTransitOnly())) { + request.journey().direct().setMode(StreetMode.NOT_SET); + } else if (direct != null && direct.size() > 0) { + request.journey().direct().setMode(DirectModeMapper.map(direct.getFirst())); + } + + var transit = modesInput.getGraphQLTransit(); + if (Boolean.TRUE.equals(modesInput.getGraphQLDirectOnly())) { + request.journey().transit().disable(); + } else if (transit != null) { + var access = transit.getGraphQLAccess(); + if (access != null && access.size() > 0) { + request.journey().access().setMode(AccessModeMapper.map(access.getFirst())); + } + + var egress = transit.getGraphQLEgress(); + if (egress != null && egress.size() > 0) { + request.journey().egress().setMode(EgressModeMapper.map(egress.getFirst())); + } + + var transfer = transit.getGraphQLTransfer(); + if (transfer != null && transfer.size() > 0) { + request.journey().transfer().setMode(TransferModeMapper.map(transfer.getFirst())); + } + + var transitModes = getTransitModes(environment); + if (transitModes.size() > 0) { + var filterRequestBuilder = TransitFilterRequest.of(); + var mainAndSubModes = transitModes + .stream() + .map(mode -> + new MainAndSubMode( + TransitModeMapper.map( + GraphQLTypes.GraphQLTransitMode.valueOf((String) mode.get("mode")) + ) + ) + ) + .toList(); + filterRequestBuilder.addSelect( + SelectRequest.of().withTransportModes(mainAndSubModes).build() + ); + request.journey().transit().setFilters(List.of(filterRequestBuilder.build())); + } + } + } + + /** + * This methods returns list of modes and their costs from the argument structure: + * modes.transit.transit. This methods circumvents a bug in graphql-codegen as getting a list of + * input objects is not possible through using the generated types in {@link GraphQLTypes}. + *

+ * TODO this ugliness can be removed when the bug gets fixed + */ + private static List> getTransitModes(DataFetchingEnvironment environment) { + if (environment.containsArgument("modes")) { + Map modesArgs = environment.getArgument("modes"); + if (modesArgs.containsKey("transit")) { + Map transitArgs = (Map) modesArgs.get("transit"); + if (transitArgs.containsKey("transit")) { + return (List>) transitArgs.get("transit"); + } + } + } + return List.of(); + } + private static GenericLocation parseGenericLocation( GraphQLTypes.GraphQLPlanLabeledLocationInput locationInput ) { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/TransferModeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/TransferModeMapper.java new file mode 100644 index 00000000000..674a7c08423 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/TransferModeMapper.java @@ -0,0 +1,17 @@ +package org.opentripplanner.apis.gtfs.mapping; + +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.routing.api.request.StreetMode; + +/** + * Maps transfer street mode from API to internal model. + */ +public class TransferModeMapper { + + public static StreetMode map(GraphQLTypes.GraphQLPlanTransferMode mode) { + return switch (mode) { + case BICYCLE -> StreetMode.BIKE; + case WALK -> StreetMode.WALK; + }; + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/TransitModeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/TransitModeMapper.java new file mode 100644 index 00000000000..2128263d8de --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/TransitModeMapper.java @@ -0,0 +1,29 @@ +package org.opentripplanner.apis.gtfs.mapping; + +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.transit.model.basic.TransitMode; + +/** + * Maps transit mode from API to internal model. + */ +public class TransitModeMapper { + + public static TransitMode map(GraphQLTypes.GraphQLTransitMode mode) { + return switch (mode) { + case AIRPLANE -> TransitMode.AIRPLANE; + case BUS -> TransitMode.BUS; + case CABLE_CAR -> TransitMode.CABLE_CAR; + case COACH -> TransitMode.COACH; + case FERRY -> TransitMode.FERRY; + case FUNICULAR -> TransitMode.FUNICULAR; + case GONDOLA -> TransitMode.GONDOLA; + case RAIL -> TransitMode.RAIL; + case SUBWAY -> TransitMode.SUBWAY; + case TRAM -> TransitMode.TRAM; + case CARPOOL -> TransitMode.CARPOOL; + case TAXI -> TransitMode.TAXI; + case TROLLEYBUS -> TransitMode.TROLLEYBUS; + case MONORAIL -> TransitMode.MONORAIL; + }; + } +} From 0a3839b6eab0bb914ee874c5bd18d854d132c00a Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 22 Feb 2024 12:25:22 +0200 Subject: [PATCH 106/165] Remove origin and destination from output There were some issues with label and stations --- .../apis/gtfs/GtfsGraphQLIndex.java | 2 -- .../gtfs/datafetchers/PlanConnectionImpl.java | 25 --------------- .../gtfs/generated/GraphQLDataFetchers.java | 16 ---------- .../apis/gtfs/generated/graphql-codegen.yml | 2 -- .../apis/gtfs/model/PlanLabeledLocation.java | 3 -- .../apis/gtfs/model/PlanLocation.java | 26 ---------------- .../opentripplanner/apis/gtfs/schema.graphqls | 31 ------------------- 7 files changed, 105 deletions(-) delete mode 100644 src/main/java/org/opentripplanner/apis/gtfs/model/PlanLabeledLocation.java delete mode 100644 src/main/java/org/opentripplanner/apis/gtfs/model/PlanLocation.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index 07b0f615b25..6de8eca9e9c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -82,7 +82,6 @@ import org.opentripplanner.apis.gtfs.datafetchers.serviceTimeRangeImpl; import org.opentripplanner.apis.gtfs.datafetchers.stepImpl; import org.opentripplanner.apis.gtfs.datafetchers.stopAtDistanceImpl; -import org.opentripplanner.apis.gtfs.model.PlanLocation; import org.opentripplanner.apis.gtfs.model.StopPosition; import org.opentripplanner.ext.actuator.MicrometerGraphQLInstrumentation; import org.opentripplanner.framework.application.OTPFeature; @@ -141,7 +140,6 @@ protected static GraphQLSchema buildSchema() { .type("StopPosition", type -> type.typeResolver(new StopPosition() {})) .type("FareProduct", type -> type.typeResolver(new FareProductTypeResolver())) .type("AlertEntity", type -> type.typeResolver(new AlertEntityTypeResolver())) - .type("PlanLocation", type -> type.typeResolver(new PlanLocation() {})) .type(typeWiring.build(AgencyImpl.class)) .type(typeWiring.build(AlertImpl.class)) .type(typeWiring.build(BikeParkImpl.class)) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlanConnectionImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlanConnectionImpl.java index 168f58b3224..96a8557683b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlanConnectionImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/PlanConnectionImpl.java @@ -9,10 +9,8 @@ import java.time.OffsetDateTime; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; -import org.opentripplanner.apis.gtfs.model.PlanLabeledLocation; import org.opentripplanner.apis.gtfs.model.PlanPageInfo; import org.opentripplanner.model.plan.Itinerary; -import org.opentripplanner.model.plan.Place; import org.opentripplanner.routing.api.response.RoutingError; import org.opentripplanner.routing.api.response.RoutingResponse; import org.opentripplanner.transit.service.TransitService; @@ -28,11 +26,6 @@ public DataFetcher searchDateTime() { }; } - @Override - public DataFetcher origin() { - return environment -> getLocation(getSource(environment).getTripPlan().from); - } - @Override public DataFetcher>> edges() { return environment -> @@ -48,11 +41,6 @@ public DataFetcher> routingErrors() { return environment -> getSource(environment).getRoutingErrors(); } - @Override - public DataFetcher destination() { - return environment -> getLocation(getSource(environment).getTripPlan().to); - } - @Override public DataFetcher pageInfo() { return environment -> { @@ -85,19 +73,6 @@ public DataFetcher pageInfo() { }; } - private PlanLabeledLocation getLocation(Place place) { - Object location = null; - var stop = place.stop; - var coordinate = place.coordinate; - if (stop != null) { - location = stop; - } else if (coordinate != null) { - location = coordinate; - } - // TODO make label field that is only the label - return new PlanLabeledLocation(location, place.name.toString()); - } - private TransitService getTransitService(DataFetchingEnvironment environment) { return environment.getContext().transitService(); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index c942e771ba6..789bbae3b6b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -21,8 +21,6 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLRelativeDirection; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLRoutingErrorCode; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLTransitMode; -import org.opentripplanner.apis.gtfs.model.PlanLabeledLocation; -import org.opentripplanner.apis.gtfs.model.PlanLocation; import org.opentripplanner.apis.gtfs.model.PlanPageInfo; import org.opentripplanner.apis.gtfs.model.RideHailingProvider; import org.opentripplanner.apis.gtfs.model.StopPosition; @@ -667,12 +665,8 @@ public interface GraphQLPlan { * [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). */ public interface GraphQLPlanConnection { - public DataFetcher destination(); - public DataFetcher>> edges(); - public DataFetcher origin(); - public DataFetcher pageInfo(); public DataFetcher> routingErrors(); @@ -690,16 +684,6 @@ public interface GraphQLPlanEdge { public DataFetcher node(); } - /** Location that was used in the itinerary search as a origin or destination. */ - public interface GraphQLPlanLabeledLocation { - public DataFetcher label(); - - public DataFetcher location(); - } - - /** Union of locations types that could be used in a plan query. */ - public interface GraphQLPlanLocation extends TypeResolver {} - /** * Information about pagination in a connection. Part of the * [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm). diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml index 244ff5fbb61..b90c5810f83 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml @@ -80,8 +80,6 @@ config: Plan: graphql.execution.DataFetcherResult PlanConnection: graphql.execution.DataFetcherResult PlanEdge: graphql.relay.DefaultEdge#DefaultEdge - PlanLabeledLocation: org.opentripplanner.apis.gtfs.model.PlanLabeledLocation#PlanLabeledLocation - PlanLocation: org.opentripplanner.apis.gtfs.model.PlanLocation#PlanLocation PlanPageInfo: org.opentripplanner.apis.gtfs.model.PlanPageInfo#PlanPageInfo RealtimeState: String RelativeDirection: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLRelativeDirection#GraphQLRelativeDirection diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLabeledLocation.java b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLabeledLocation.java deleted file mode 100644 index aa316e98058..00000000000 --- a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLabeledLocation.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.opentripplanner.apis.gtfs.model; - -public record PlanLabeledLocation(Object location, String label) {} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLocation.java b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLocation.java deleted file mode 100644 index 5bf1db4e2f9..00000000000 --- a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanLocation.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.opentripplanner.apis.gtfs.model; - -import graphql.TypeResolutionEnvironment; -import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLSchema; -import graphql.schema.TypeResolver; -import org.opentripplanner.framework.geometry.WgsCoordinate; -import org.opentripplanner.transit.model.site.StopLocation; - -public class PlanLocation implements TypeResolver { - - @Override - public GraphQLObjectType getType(TypeResolutionEnvironment env) { - Object o = env.getObject(); - GraphQLSchema schema = env.getSchema(); - - if (o instanceof StopLocation) { - return schema.getObjectType("Stop"); - } - if (o instanceof WgsCoordinate) { - return schema.getObjectType("Coordinate"); - } - - return null; - } -} diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 691a1e4ac55..6d2d641d8f5 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1563,22 +1563,6 @@ input PlanItineraryFilterInput { groupedOtherThanSameLegsMaxCostMultiplier: Float = 2.0 } -""" -Location that was used in the itinerary search as a origin or destination. -""" -type PlanLabeledLocation { - """ - A location used in a plan query. This can be null if a stop location ID was specified as an input - location but a stop location with that ID does not exist. - """ - location: PlanLocation - - """ - A label that was attached to the location. - """ - label: String -} - """ Plan location settings. Location must be set. Label is optional and used for naming the location. @@ -1596,11 +1580,6 @@ input PlanLabeledLocationInput { label: String } -""" -Union of locations types that could be used in a plan query. -""" -union PlanLocation = Stop | Coordinate - """ Plan location. Either a coordinate or a stop location should be defined. """ @@ -3330,16 +3309,6 @@ type PlanConnection { """ searchDateTime: OffsetDateTime - """ - Origin of the itinerary search. - """ - origin: PlanLabeledLocation! - - """ - Destination of the itinerary search. - """ - destination: PlanLabeledLocation! - """ Errors faced during the routing search. """ From a02581161afb67fa16f3754786b657983d004805 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 22 Feb 2024 13:40:02 +0200 Subject: [PATCH 107/165] Remove strict stop location and some TODOs --- .../apis/gtfs/generated/GraphQLTypes.java | 10 -------- .../apis/gtfs/mapping/RouteRequestMapper.java | 3 --- .../opentripplanner/apis/gtfs/schema.graphqls | 25 ++++++------------- 3 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 05957c32b17..5ad7fc87785 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -1818,12 +1818,10 @@ public void setGraphQLTransit(GraphQLTransitPreferencesInput transit) { public static class GraphQLPlanStopLocationInput { private String stopLocationId; - private Boolean strict; public GraphQLPlanStopLocationInput(Map args) { if (args != null) { this.stopLocationId = (String) args.get("stopLocationId"); - this.strict = (Boolean) args.get("strict"); } } @@ -1831,17 +1829,9 @@ public String getGraphQLStopLocationId() { return this.stopLocationId; } - public Boolean getGraphQLStrict() { - return this.strict; - } - public void setGraphQLStopLocationId(String stopLocationId) { this.stopLocationId = stopLocationId; } - - public void setGraphQLStrict(Boolean strict) { - this.strict = strict; - } } public static class GraphQLPlanStreetPreferencesInput { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index b08f4b245f9..ed1304c96d8 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -107,7 +107,6 @@ private static void setTransitPreferences( var modes = args.getGraphQLModes(); var transit = getTransitModes(environment); if (!Boolean.TRUE.equals(modes.getGraphQLDirectOnly()) && transit.size() > 0) { - // TODO what to do with the static cost? var reluctanceForMode = transit .stream() .filter(mode -> mode.containsKey("cost")) @@ -204,10 +203,8 @@ private static GenericLocation parseGenericLocation( ) { var stopLocation = locationInput.getGraphQLLocation().getGraphQLStopLocation(); if (stopLocation.getGraphQLStopLocationId() != null) { - // TODO implement strict var stopId = stopLocation.getGraphQLStopLocationId(); if (FeedScopedId.isValidString(stopId)) { - // TODO make label field that is only the label return new GenericLocation( locationInput.getGraphQLLabel(), FeedScopedId.parse(stopId), diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 6d2d641d8f5..2c81b7f43a5 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1591,12 +1591,10 @@ input PlanLocationInput @oneOf { """ Stop, station, a group of stop places or multimodal stop place that should be used as - a location for the search. The stop place can be used in two ways: - 1. As a location where one should depart from or arrive to on transit depending on if - its used as an origin or a destination - 2. As coordinates where the search can start or end but it's not required to use this stop - place for a direct transit connnection. - Note, either coordinates or a stop location should be defined. + a location for the search. The trip doesn't have to use the given stop location for a + transit connection as it's possible to start walking to another stop from the given + location. If a station or a group of stop places is provided, a stop that makes the most + sense for the journey is picked as the location within the station or group of stop places. """ stopLocation: PlanStopLocationInput } @@ -1625,11 +1623,10 @@ input PlanPreferencesInput { """ Stop, station, a group of stop places or multimodal stop place that should be used as -a location for the search. The stop place can be used in two ways: -1. As a location where one should depart from or arrive to on transit depending on if - its used as an origin or a destination -2. As coordinates where the search can start or end but it's not required to use this stop - place for a direct transit connnection. +a location for the search. The trip doesn't have to use the given stop location for a +transit connection as it's possible to start walking to another stop from the given +location. If a station or a group of stop places is provided, a stop that makes the most +sense for the journey is picked as the location within the station or group of stop places. """ input PlanStopLocationInput { """ @@ -1637,12 +1634,6 @@ input PlanStopLocationInput { should be `FeedId:StopLocationId`. """ stopLocationId: String! - - """ - If set as true, one must either board or alight from this stop location. Otherwise, - it's optional and alternatively one can just walk to or from this location. - """ - strict: Boolean = false } """ From 0893557bfb423ca7fbf9e3aa22bef1b80430e5ac Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 22 Feb 2024 14:00:38 +0200 Subject: [PATCH 108/165] Clarify numberOfItineraries --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 2c81b7f43a5..f8aa1cd3bea 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4219,7 +4219,9 @@ type QueryType { itineraries are either closest to the defined earliest departure time or to the latest arrival time. It can make sense to search for more itineraries that what is immediately needed if there is a possibility that more itineraries are used later on (limiting the number of returned itineraries - does not affect the performance by a lot but can affect the network bandwidth usage). + does not affect the performance by a lot but can affect the network bandwidth usage). During the + search for next or previous pages with `after` or `before` cursors, this field is ignored and + `first` or `last` should be used instead. """ numberOfItineraries: Int = 50 From dd5880c997ec5db8cf6b1b7067a6d3674a686c8f Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 22 Feb 2024 14:01:12 +0200 Subject: [PATCH 109/165] Update src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls Co-authored-by: Leonard Ehrenfried --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index f8aa1cd3bea..813f20559ba 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1699,7 +1699,7 @@ input PlanTransitModesInput { transfer: [PlanTransferMode!] """ - Transit modes and reluctancies associated with them. Each defined mode can be used in + Transit modes and reluctances associated with them. Each defined mode can be used in an itinerary but doesn't have to be. If direct search is not disabled, there can be an itinerary without any transit legs. By default, all transit modes are usable. """ From d2cc973bdc8dd9bb612a5d8e78f0389f5c03ed4b Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 22 Feb 2024 14:24:09 +0200 Subject: [PATCH 110/165] Implement accessibility preferences --- .../apis/gtfs/mapping/RouteRequestMapper.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index ed1304c96d8..ccd94afcd4d 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -59,7 +59,7 @@ public static RouteRequest toRouteRequest( request.setNumItineraries(args.getGraphQLNumberOfItineraries()); } - request.withPreferences(preferences -> setPreferences(preferences, args, environment)); + request.withPreferences(preferences -> setPreferences(preferences, request, args, environment)); setModes(request, args.getGraphQLModes(), environment); @@ -68,13 +68,16 @@ public static RouteRequest toRouteRequest( private static void setPreferences( RoutingPreferences.Builder prefs, + RouteRequest request, GraphQLTypes.GraphQLQueryTypePlanConnectionArgs args, DataFetchingEnvironment environment ) { + var preferenceArgs = args.getGraphQLPreferences(); prefs.withItineraryFilter(filters -> setItineraryFilters(filters, args.getGraphQLItineraryFilter()) ); prefs.withTransit(transit -> setTransitPreferences(transit, args, environment)); + setAccessibilityPreferences(request, preferenceArgs.getGraphQLAccessibility()); } private static void setItineraryFilters( @@ -123,6 +126,15 @@ private static void setTransitPreferences( } } + private static void setAccessibilityPreferences( + RouteRequest request, + GraphQLTypes.GraphQLAccessibilityPreferencesInput preferenceArgs + ) { + if (preferenceArgs != null && preferenceArgs.getGraphQLWheelchair() != null) { + request.setWheelchair(preferenceArgs.getGraphQLWheelchair().getGraphQLEnabled()); + } + } + /** * TODO this doesn't support multiple street modes yet */ From 72cbacec2c6340f5e6cc9a0e219981944bb038d4 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 23 Feb 2024 15:29:59 +0200 Subject: [PATCH 111/165] Make some costs Costs --- .../apis/gtfs/generated/GraphQLTypes.java | 22 ++++++++++++------- .../opentripplanner/apis/gtfs/schema.graphqls | 4 ++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 5ad7fc87785..d2fc0a05e19 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -137,7 +137,7 @@ public static class GraphQLBicycleParkingPreferencesInput { private List filters; private List preferred; - private Integer unpreferredCost; + private org.opentripplanner.framework.model.Cost unpreferredCost; public GraphQLBicycleParkingPreferencesInput(Map args) { if (args != null) { @@ -147,7 +147,8 @@ public GraphQLBicycleParkingPreferencesInput(Map args) { if (args.get("preferred") != null) { this.preferred = (List) args.get("preferred"); } - this.unpreferredCost = (Integer) args.get("unpreferredCost"); + this.unpreferredCost = + (org.opentripplanner.framework.model.Cost) args.get("unpreferredCost"); } } @@ -159,7 +160,7 @@ public List getGraphQLPreferred() { return this.preferred; } - public Integer getGraphQLUnpreferredCost() { + public org.opentripplanner.framework.model.Cost getGraphQLUnpreferredCost() { return this.unpreferredCost; } @@ -171,7 +172,9 @@ public void setGraphQLPreferred(List preferred) { this.preferred = preferred; } - public void setGraphQLUnpreferredCost(Integer unpreferredCost) { + public void setGraphQLUnpreferredCost( + org.opentripplanner.framework.model.Cost unpreferredCost + ) { this.unpreferredCost = unpreferredCost; } } @@ -451,7 +454,7 @@ public static class GraphQLCarParkingPreferencesInput { private List filters; private List preferred; - private Integer unpreferredCost; + private org.opentripplanner.framework.model.Cost unpreferredCost; public GraphQLCarParkingPreferencesInput(Map args) { if (args != null) { @@ -461,7 +464,8 @@ public GraphQLCarParkingPreferencesInput(Map args) { if (args.get("preferred") != null) { this.preferred = (List) args.get("preferred"); } - this.unpreferredCost = (Integer) args.get("unpreferredCost"); + this.unpreferredCost = + (org.opentripplanner.framework.model.Cost) args.get("unpreferredCost"); } } @@ -473,7 +477,7 @@ public List getGraphQLPreferred() { return this.preferred; } - public Integer getGraphQLUnpreferredCost() { + public org.opentripplanner.framework.model.Cost getGraphQLUnpreferredCost() { return this.unpreferredCost; } @@ -485,7 +489,9 @@ public void setGraphQLPreferred(List preferred) { this.preferred = preferred; } - public void setGraphQLUnpreferredCost(Integer unpreferredCost) { + public void setGraphQLUnpreferredCost( + org.opentripplanner.framework.model.Cost unpreferredCost + ) { this.unpreferredCost = unpreferredCost; } } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 813f20559ba..1d73017d7ac 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1435,7 +1435,7 @@ input CarParkingPreferencesInput { at least one of the preferred conditions, will receive this extra cost and therefore avoided if preferred options are available. """ - unpreferredCost: Int + unpreferredCost: Cost """ If non-empty every parking facility that doesn't match this set of conditions will @@ -1459,7 +1459,7 @@ input BicycleParkingPreferencesInput { at least one of the preferred conditions, will receive this extra cost and therefore avoided if preferred options are available. """ - unpreferredCost: Int + unpreferredCost: Cost """ If non-empty every parking facility that doesn't match this set of conditions will From 9ffc1c00f38fa1ec357d849ceb8fe08b6c9a65f2 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 23 Feb 2024 20:03:00 +0200 Subject: [PATCH 112/165] Update @graphql-codegen/java version --- .../org/opentripplanner/apis/gtfs/generated/package.json | 2 +- .../org/opentripplanner/apis/gtfs/generated/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json b/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json index db865eaa003..6a840640ca9 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json @@ -12,7 +12,7 @@ "dependencies": { "@graphql-codegen/add": "5.0.2", "@graphql-codegen/cli": "5.0.2", - "@graphql-codegen/java": "4.0.0", + "@graphql-codegen/java": "4.0.1", "@graphql-codegen/java-resolvers": "3.0.0", "graphql": "16.8.1" } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock b/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock index fffe602db18..77829ecc911 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock @@ -810,10 +810,10 @@ "@graphql-codegen/visitor-plugin-common" "2.13.1" tslib "~2.6.0" -"@graphql-codegen/java@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@graphql-codegen/java/-/java-4.0.0.tgz#245e4403f19c390d5b6a03956558e34c3c4d7848" - integrity sha512-7pxwkgm0eFRDpq6PZx3n2tErBLr1wsG75USvCDnkkoz0145UCErk9GMAEfYgGx1mAm9+oUT+1wjZozjEYWjSow== +"@graphql-codegen/java@4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@graphql-codegen/java/-/java-4.0.1.tgz#2f62a7361e702691500c83903c6c6f829496fa5c" + integrity sha512-p51hsOgnuJInSNy+X2Vb+Su9U41iK1xU0YeciVx7JSnOyiT5nQRuDzsjDFvUIL9YZTM+KGbsomaRISOPM6Yq/Q== dependencies: "@graphql-codegen/java-common" "^3.0.0" "@graphql-codegen/plugin-helpers" "^3.0.0" From bd304461d770d37bcde06d51cc54991a7d045d2d Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 23 Feb 2024 20:04:25 +0200 Subject: [PATCH 113/165] Use @oneOf for optimization input again Setting defaults in the schema isn't really feasible for these --- .../apis/gtfs/generated/GraphQLTypes.java | 16 ++++---- .../opentripplanner/apis/gtfs/schema.graphqls | 38 ++++++------------- 2 files changed, 19 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index d2fc0a05e19..aedd564545a 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -592,7 +592,7 @@ public GraphQLCyclingOptimizationInput(Map args) { new GraphQLTriangleCyclingFactorsInput((Map) args.get("triangle")); if (args.get("type") instanceof GraphQLCyclingOptimizationType) { this.type = (GraphQLCyclingOptimizationType) args.get("type"); - } else { + } else if (args.get("type") != null) { this.type = GraphQLCyclingOptimizationType.valueOf((String) args.get("type")); } } @@ -624,7 +624,6 @@ public enum GraphQLCyclingOptimizationType { SAFEST_STREETS, SAFE_STREETS, SHORTEST_DURATION, - TRIANGLE, } public static class GraphQLDepartureRowStoptimesArgs { @@ -1614,7 +1613,7 @@ public GraphQLPlanItineraryFilterInput(Map args) { if (args.get("itineraryFilterDebugProfile") instanceof GraphQLItineraryFilterDebugProfile) { this.itineraryFilterDebugProfile = (GraphQLItineraryFilterDebugProfile) args.get("itineraryFilterDebugProfile"); - } else { + } else if (args.get("itineraryFilterDebugProfile") != null) { this.itineraryFilterDebugProfile = GraphQLItineraryFilterDebugProfile.valueOf( (String) args.get("itineraryFilterDebugProfile") @@ -1907,7 +1906,7 @@ public GraphQLPlanTransitModePreferenceInput(Map args) { new GraphQLTransitModePreferenceCostInput((Map) args.get("cost")); if (args.get("mode") instanceof GraphQLTransitMode) { this.mode = (GraphQLTransitMode) args.get("mode"); - } else { + } else if (args.get("mode") != null) { this.mode = GraphQLTransitMode.valueOf((String) args.get("mode")); } } @@ -2739,7 +2738,7 @@ public GraphQLQueryTypePlanArgs(Map args) { this.omitCanceled = (Boolean) args.get("omitCanceled"); if (args.get("optimize") instanceof GraphQLOptimizeType) { this.optimize = (GraphQLOptimizeType) args.get("optimize"); - } else { + } else if (args.get("optimize") != null) { this.optimize = GraphQLOptimizeType.valueOf((String) args.get("optimize")); } this.pageCursor = (String) args.get("pageCursor"); @@ -4020,7 +4019,7 @@ public GraphQLScooterOptimizationInput(Map args) { new GraphQLTriangleScooterFactorsInput((Map) args.get("triangle")); if (args.get("type") instanceof GraphQLScooterOptimizationType) { this.type = (GraphQLScooterOptimizationType) args.get("type"); - } else { + } else if (args.get("type") != null) { this.type = GraphQLScooterOptimizationType.valueOf((String) args.get("type")); } } @@ -4052,7 +4051,6 @@ public enum GraphQLScooterOptimizationType { SAFEST_STREETS, SAFE_STREETS, SHORTEST_DURATION, - TRIANGLE, } public static class GraphQLScooterPreferencesInput { @@ -4700,12 +4698,12 @@ public GraphQLTransportModeInput(Map args) { if (args != null) { if (args.get("mode") instanceof GraphQLMode) { this.mode = (GraphQLMode) args.get("mode"); - } else { + } else if (args.get("mode") != null) { this.mode = GraphQLMode.valueOf((String) args.get("mode")); } if (args.get("qualifier") instanceof GraphQLQualifier) { this.qualifier = (GraphQLQualifier) args.get("qualifier"); - } else { + } else if (args.get("qualifier") != null) { this.qualifier = GraphQLQualifier.valueOf((String) args.get("qualifier")); } } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 1d73017d7ac..8a5286d24f2 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -1760,13 +1760,13 @@ input TriangleCyclingFactorsInput { concerns such as convenience and general cyclist preferences by taking into account road surface etc. """ - safety: Ratio + safety: Ratio! """Relative importance of flat terrain""" - flatness: Ratio + flatness: Ratio! """Relative importance of duration""" - time: Ratio + time: Ratio! } """ @@ -1779,13 +1779,13 @@ input TriangleScooterFactorsInput { concerns such as convenience and general scooter preferences by taking into account road surface etc. """ - safety: Ratio + safety: Ratio! """Relative importance of flat terrain""" - flatness: Ratio + flatness: Ratio! """Relative importance of duration""" - time: Ratio + time: Ratio! } input InputUnpreferred { @@ -2986,15 +2986,14 @@ type BookingInfo { """ What criteria should be used when optimizing a cycling route. """ -input CyclingOptimizationInput { +input CyclingOptimizationInput @oneOf { """ Use one of the predefined optimization types. """ - type: CyclingOptimizationType! + type: CyclingOptimizationType """ - Define optimization by weighing three criteria. This should only be used when - optimization type is `TRIANGLE`. + Define optimization by weighing three criteria. """ triangle: TriangleCyclingFactorsInput } @@ -3031,26 +3030,19 @@ enum CyclingOptimizationType { by taking into account road surface etc. This option was previously called `GREENWAYS`. """ SAFEST_STREETS - - """ - Allows more fine-tuned configuration of how much different optimization criteria are considered in - routing. If this is defined, `triangle` values should be set as well. - """ - TRIANGLE } """ What criteria should be used when optimizing a scooter route. """ -input ScooterOptimizationInput { +input ScooterOptimizationInput @oneOf { """ Use one of the predefined optimization types. """ - type: ScooterOptimizationType! + type: ScooterOptimizationType """ - Define optimization by weighing three criteria. This should only be used when - optimization type is `TRIANGLE`. + Define optimization by weighing three criteria. """ triangle: TriangleScooterFactorsInput } @@ -3091,12 +3083,6 @@ enum ScooterOptimizationType { This option was previously called `GREENWAYS`. """ SAFEST_STREETS - - """ - Allows more fine-tuned configuration of how much different optimization criteria are considered in - routing. If this is defined, `triangle` values should be set as well. - """ - TRIANGLE } """ From 1b2a833f4465d45cb2fc06b0057c2da7b8bbdc53 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 23 Feb 2024 20:05:15 +0200 Subject: [PATCH 114/165] Implement cost scalar --- .../apis/gtfs/GraphQLScalars.java | 45 +++++++++++++++++++ .../apis/gtfs/GtfsGraphQLIndex.java | 7 +-- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index 19b511ea7ce..f25598499ef 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -20,6 +20,7 @@ import org.locationtech.jts.geom.Geometry; import org.opentripplanner.framework.geometry.GeometryUtils; import org.opentripplanner.framework.graphql.scalar.DurationScalarFactory; +import org.opentripplanner.framework.model.Cost; import org.opentripplanner.framework.model.Grams; import org.opentripplanner.framework.time.OffsetDateTimeParser; @@ -170,6 +171,50 @@ private static Optional validateCoordinate(double coordinate) { ) .build(); + public static final GraphQLScalarType costScalar = GraphQLScalarType + .newScalar() + .name("Cost") + .coercing( + new Coercing() { + @Override + public Integer serialize(@Nonnull Object dataFetcherResult) + throws CoercingSerializeException { + if (dataFetcherResult instanceof Integer intValue) { + return intValue; + } else if (dataFetcherResult instanceof Cost costValue) { + return costValue.toSeconds(); + } else { + throw new CoercingSerializeException( + "Cannot serialize object of class %s as a cost".formatted( + dataFetcherResult.getClass().getSimpleName() + ) + ); + } + } + + @Override + public Cost parseValue(Object input) throws CoercingParseValueException { + if (input instanceof Integer intValue) { + return Cost.costOfSeconds(intValue); + } + throw new CoercingParseValueException( + "Expected an integer, got %s %s".formatted(input.getClass().getSimpleName(), input) + ); + } + + @Override + public Cost parseLiteral(Object input) throws CoercingParseLiteralException { + if (input instanceof IntValue intValue) { + return Cost.costOfSeconds(intValue.getValue().intValue()); + } + throw new CoercingParseLiteralException( + "Expected an integer, got: " + input.getClass().getSimpleName() + ); + } + } + ) + .build(); + public static GraphQLScalarType geoJsonScalar = GraphQLScalarType .newScalar() .name("GeoJson") diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index 6de8eca9e9c..7cc059bf7f4 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -115,14 +115,9 @@ protected static GraphQLSchema buildSchema() { .scalar(GraphQLScalars.offsetDateTimeScalar) .scalar(GraphQLScalars.ratioScalar) .scalar(GraphQLScalars.coordinateValueScalar) + .scalar(GraphQLScalars.costScalar) .scalar(ExtendedScalars.GraphQLLong) .scalar(ExtendedScalars.Locale) - .scalar( - ExtendedScalars - .newAliasedScalar("Cost") - .aliasedScalar(ExtendedScalars.NonNegativeInt) - .build() - ) .scalar( ExtendedScalars .newAliasedScalar("Speed") From 66d61a45251aa7e9375f6a18e8909f9a497d4c0a Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 23 Feb 2024 20:06:40 +0200 Subject: [PATCH 115/165] Implement walk and bicycle preferences --- .../BicycleOptimizationTypeMapper.java | 20 ++ .../mapping/LegacyRouteRequestMapper.java | 32 +-- .../apis/gtfs/mapping/RouteRequestMapper.java | 268 +++++++++++++++++- 3 files changed, 290 insertions(+), 30 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/BicycleOptimizationTypeMapper.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/BicycleOptimizationTypeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/BicycleOptimizationTypeMapper.java new file mode 100644 index 00000000000..93718ae3277 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/BicycleOptimizationTypeMapper.java @@ -0,0 +1,20 @@ +package org.opentripplanner.apis.gtfs.mapping; + +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.core.BicycleOptimizeType; + +/** + * Maps bicycle optimization type from API to internal model. + */ +public class BicycleOptimizationTypeMapper { + + public static BicycleOptimizeType map(GraphQLTypes.GraphQLCyclingOptimizationType type) { + return switch (type) { + case SHORTEST_DURATION -> BicycleOptimizeType.SHORTEST_DURATION; + case FLAT_STREETS -> BicycleOptimizeType.FLAT_STREETS; + case SAFE_STREETS -> BicycleOptimizeType.SAFE_STREETS; + case SAFEST_STREETS -> BicycleOptimizeType.SAFEST_STREETS; + }; + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapper.java index 7355658d89c..cf737e59d92 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapper.java @@ -1,5 +1,8 @@ package org.opentripplanner.apis.gtfs.mapping; +import static org.opentripplanner.apis.gtfs.mapping.RouteRequestMapper.parseNotFilters; +import static org.opentripplanner.apis.gtfs.mapping.RouteRequestMapper.parseSelectFilters; + import graphql.schema.DataFetchingEnvironment; import java.time.Duration; import java.util.Arrays; @@ -7,10 +10,8 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.annotation.Nonnull; import org.opentripplanner.api.common.LocationStringParser; import org.opentripplanner.api.parameter.QualifiedMode; @@ -234,33 +235,6 @@ public static RouteRequest toRouteRequest( return request; } - private static Set parseNotFilters(Collection> filters) { - return parseFilters(filters, "not"); - } - - private static Set parseSelectFilters(Collection> filters) { - return parseFilters(filters, "select"); - } - - @Nonnull - private static Set parseFilters(Collection> filters, String key) { - return filters - .stream() - .flatMap(f -> - parseOperation((Collection>>) f.getOrDefault(key, List.of())) - ) - .collect(Collectors.toSet()); - } - - private static Stream parseOperation(Collection>> map) { - return map - .stream() - .flatMap(f -> { - var tags = f.getOrDefault("tags", List.of()); - return tags.stream(); - }); - } - private static boolean hasArgument(Map m, String name) { return m.containsKey(name) && m.get(name) != null; } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index ccd94afcd4d..b698fcac831 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -2,19 +2,28 @@ import graphql.schema.DataFetchingEnvironment; import java.time.Instant; +import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.preference.BikePreferences; import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.routing.api.request.preference.TransitPreferences; +import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; +import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences; +import org.opentripplanner.routing.api.request.preference.VehicleWalkingPreferences; +import org.opentripplanner.routing.api.request.preference.WalkPreferences; import org.opentripplanner.routing.api.request.request.filter.SelectRequest; import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest; import org.opentripplanner.transit.model.basic.MainAndSubMode; @@ -28,7 +37,6 @@ public static RouteRequest toRouteRequest( GraphQLRequestContext context ) { RouteRequest request = context.defaultRouteRequest(); - var args = new GraphQLTypes.GraphQLQueryTypePlanConnectionArgs(environment.getArguments()); var dateTime = args.getGraphQLDateTime(); if (dateTime.getGraphQLEarliestDeparture() != null) { @@ -66,6 +74,33 @@ public static RouteRequest toRouteRequest( return request; } + static Set parseNotFilters(Collection> filters) { + return parseFilters(filters, "not"); + } + + static Set parseSelectFilters(Collection> filters) { + return parseFilters(filters, "select"); + } + + @Nonnull + private static Set parseFilters(Collection> filters, String key) { + return filters + .stream() + .flatMap(f -> + parseOperation((Collection>>) f.getOrDefault(key, List.of())) + ) + .collect(Collectors.toSet()); + } + + private static Stream parseOperation(Collection>> map) { + return map + .stream() + .flatMap(f -> { + var tags = f.getOrDefault("tags", List.of()); + return tags.stream(); + }); + } + private static void setPreferences( RoutingPreferences.Builder prefs, RouteRequest request, @@ -77,6 +112,7 @@ private static void setPreferences( setItineraryFilters(filters, args.getGraphQLItineraryFilter()) ); prefs.withTransit(transit -> setTransitPreferences(transit, args, environment)); + setStreetPreferences(prefs, preferenceArgs.getGraphQLStreet(), environment); setAccessibilityPreferences(request, preferenceArgs.getGraphQLAccessibility()); } @@ -126,6 +162,236 @@ private static void setTransitPreferences( } } + private static void setStreetPreferences( + RoutingPreferences.Builder preferences, + GraphQLTypes.GraphQLPlanStreetPreferencesInput args, + DataFetchingEnvironment environment + ) { + if (args != null) { + preferences.withBike(bicycle -> + setBicyclePreferences(bicycle, args.getGraphQLBicycle(), environment) + ); + preferences.withWalk(walk -> setWalkPreferences(walk, args.getGraphQLWalk())); + } + } + + private static void setBicyclePreferences( + BikePreferences.Builder preferences, + GraphQLTypes.GraphQLBicyclePreferencesInput args, + DataFetchingEnvironment environment + ) { + if (args != null) { + var speed = args.getGraphQLSpeed(); + if (speed != null) { + preferences.withSpeed(speed); + } + var reluctance = args.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); + } + var boardCost = args.getGraphQLBoardCost(); + if (boardCost != null) { + preferences.withBoardCost(boardCost.toSeconds()); + } + preferences.withWalking(walk -> setBicycleWalkPreferences(walk, args.getGraphQLWalk())); + preferences.withParking(parking -> + setBicycleParkingPreferences(parking, args.getGraphQLParking(), environment) + ); + preferences.withRental(rental -> setBicycleRentalPreferences(rental, args.getGraphQLRental()) + ); + setBicycleOptimization(preferences, args.getGraphQLOptimization()); + } + } + + private static void setBicycleWalkPreferences( + VehicleWalkingPreferences.Builder preferences, + GraphQLTypes.GraphQLBicycleWalkPreferencesInput args + ) { + if (args != null) { + var speed = args.getGraphQLSpeed(); + if (speed != null) { + preferences.withSpeed(speed); + } + var mountTime = args.getGraphQLMountDismountTime(); + if (mountTime != null) { + preferences.withMountDismountTime(mountTime); + } + var cost = args.getGraphQLCost(); + if (cost != null) { + var reluctance = cost.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); + } + var mountCost = cost.getGraphQLMountDismountCost(); + if (mountCost != null) { + preferences.withMountDismountCost(mountCost.toSeconds()); + } + } + } + } + + private static void setBicycleParkingPreferences( + VehicleParkingPreferences.Builder preferences, + GraphQLTypes.GraphQLBicycleParkingPreferencesInput args, + DataFetchingEnvironment environment + ) { + if (args != null) { + var unpreferredCost = args.getGraphQLUnpreferredCost(); + if (unpreferredCost != null) { + preferences.withUnpreferredVehicleParkingTagCost(unpreferredCost.toSeconds()); + } + var filters = getParkingFilters(environment, "bicycle"); + preferences.withRequiredVehicleParkingTags(parseSelectFilters(filters)); + preferences.withBannedVehicleParkingTags(parseNotFilters(filters)); + var preferred = getParkingPreferred(environment, "bicycle"); + preferences.withPreferredVehicleParkingTags(parseSelectFilters(preferred)); + preferences.withNotPreferredVehicleParkingTags(parseNotFilters(preferred)); + } + } + + /** + * This methods returns required/banned parking tags of the given type from argument structure: + * preferences.street.type.parking.filters. This methods circumvents a bug in graphql-codegen as + * getting a list of input objects is not possible through using the generated types in + * {@link GraphQLTypes}. + *

+ * TODO this ugliness can be removed when the bug gets fixed + */ + @Nonnull + private static Collection> getParkingFilters( + DataFetchingEnvironment environment, + String type + ) { + var parking = getParking(environment, type); + var filters = parking != null && parking.containsKey("filters") + ? getParking(environment, type).get("filters") + : null; + return filters != null ? (Collection>) filters : List.of(); + } + + /** + * This methods returns preferred/unpreferred parking tags of the given type from argument + * structure: preferences.street.type.parking.preferred. This methods circumvents a bug in + * graphql-codegen as getting a list of input objects is not possible through using the generated + * types in {@link GraphQLTypes}. + *

+ * TODO this ugliness can be removed when the bug gets fixed + */ + @Nonnull + private static Collection> getParkingPreferred( + DataFetchingEnvironment environment, + String type + ) { + var parking = getParking(environment, type); + var preferred = parking != null && parking.containsKey("preferred") + ? getParking(environment, type).get("preferred") + : null; + return preferred != null ? (Collection>) preferred : List.of(); + } + + /** + * This methods returns parking preferences of the given type from argument structure: + * preferences.street.type.parking. This methods circumvents a bug in graphql-codegen as getting a + * list of input objects is not possible through using the generated types in + * {@link GraphQLTypes}. + *

+ * TODO this ugliness can be removed when the bug gets fixed + */ + @Nullable + private static Map getParking(DataFetchingEnvironment environment, String type) { + return ( + (Map) ( + (Map) ( + (Map) ((Map) environment.getArgument("preferences")).get( + "street" + ) + ).get(type) + ).get("parking") + ); + } + + private static void setBicycleRentalPreferences( + VehicleRentalPreferences.Builder preferences, + GraphQLTypes.GraphQLBicycleRentalPreferencesInput args + ) { + if (args != null) { + var allowedNetworks = args.getGraphQLAllowedNetworks(); + if (allowedNetworks != null && allowedNetworks.size() > 0) { + preferences.withBannedNetworks(Set.copyOf(allowedNetworks)); + } + var bannedNetworks = args.getGraphQLBannedNetworks(); + if (bannedNetworks != null && bannedNetworks.size() > 0) { + preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); + } + var destinationPolicy = args.getGraphQLDestinationBicyclePolicy(); + if (destinationPolicy != null) { + var allowed = destinationPolicy.getGraphQLAllowKeeping(); + preferences.withAllowArrivingInRentedVehicleAtDestination(Boolean.TRUE.equals(allowed)); + var cost = destinationPolicy.getGraphQLKeepingCost(); + if (cost != null) { + preferences.withArrivingInRentalVehicleAtDestinationCost(cost.toSeconds()); + } + } + } + } + + private static void setBicycleOptimization( + BikePreferences.Builder preferences, + GraphQLTypes.GraphQLCyclingOptimizationInput args + ) { + if (args != null) { + var type = args.getGraphQLType(); + var mappedType = type != null ? BicycleOptimizationTypeMapper.map(type) : null; + if (mappedType != null) { + preferences.withOptimizeType(mappedType); + } + var triangleArgs = args.getGraphQLTriangle(); + if (isBicycleTriangleSet(triangleArgs)) { + preferences.withForcedOptimizeTriangle(triangle -> { + triangle + .withSlope(triangleArgs.getGraphQLFlatness()) + .withSafety(triangleArgs.getGraphQLSafety()) + .withTime(triangleArgs.getGraphQLTime()); + }); + } + } + } + + private static boolean isBicycleTriangleSet( + GraphQLTypes.GraphQLTriangleCyclingFactorsInput args + ) { + return ( + args != null && + args.getGraphQLFlatness() != null && + args.getGraphQLSafety() != null && + args.getGraphQLTime() != null + ); + } + + private static void setWalkPreferences( + WalkPreferences.Builder preferences, + GraphQLTypes.GraphQLWalkPreferencesInput args + ) { + if (args != null) { + var speed = args.getGraphQLSpeed(); + if (speed != null) { + preferences.withSpeed(speed); + } + var reluctance = args.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); + } + var walkSafetyFactor = args.getGraphQLWalkSafetyFactor(); + if (walkSafetyFactor != null) { + preferences.withSafetyFactor(walkSafetyFactor); + } + var boardCost = args.getGraphQLBoardCost(); + if (boardCost != null) { + preferences.withBoardCost(boardCost.toSeconds()); + } + } + } + private static void setAccessibilityPreferences( RouteRequest request, GraphQLTypes.GraphQLAccessibilityPreferencesInput preferenceArgs From d623a9964bf3b76563138a7a0a648727fcf83856 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 26 Feb 2024 14:49:17 +0200 Subject: [PATCH 116/165] Implement scooter preferences --- .../apis/gtfs/mapping/RouteRequestMapper.java | 71 +++++++++++++++++++ .../VehicleOptimizationTypeMapper.java | 9 +++ 2 files changed, 80 insertions(+) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index b166dc981fb..3dc851e4cbd 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -19,6 +19,7 @@ import org.opentripplanner.routing.api.request.preference.BikePreferences; import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; +import org.opentripplanner.routing.api.request.preference.ScooterPreferences; import org.opentripplanner.routing.api.request.preference.TransitPreferences; import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences; @@ -171,6 +172,7 @@ private static void setStreetPreferences( preferences.withBike(bicycle -> setBicyclePreferences(bicycle, args.getGraphQLBicycle(), environment) ); + preferences.withScooter(scooter -> setScooterPreferences(scooter, args.getGraphQLScooter())); preferences.withWalk(walk -> setWalkPreferences(walk, args.getGraphQLWalk())); } } @@ -368,6 +370,75 @@ private static boolean isBicycleTriangleSet( ); } + private static void setScooterPreferences( + ScooterPreferences.Builder preferences, + GraphQLTypes.GraphQLScooterPreferencesInput args + ) { + if (args != null) { + var speed = args.getGraphQLSpeed(); + if (speed != null) { + preferences.withSpeed(speed); + } + var reluctance = args.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); + } + preferences.withRental(rental -> setScooterRentalPreferences(rental, args.getGraphQLRental()) + ); + setScooterOptimization(preferences, args.getGraphQLOptimization()); + } + } + + private static void setScooterRentalPreferences( + VehicleRentalPreferences.Builder preferences, + GraphQLTypes.GraphQLScooterRentalPreferencesInput args + ) { + if (args != null) { + var allowedNetworks = args.getGraphQLAllowedNetworks(); + if (allowedNetworks != null && allowedNetworks.size() > 0) { + preferences.withBannedNetworks(Set.copyOf(allowedNetworks)); + } + var bannedNetworks = args.getGraphQLBannedNetworks(); + if (bannedNetworks != null && bannedNetworks.size() > 0) { + preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); + } + // TODO validate if station based scooter systems work before adding destination policy + } + } + + private static void setScooterOptimization( + ScooterPreferences.Builder preferences, + GraphQLTypes.GraphQLScooterOptimizationInput args + ) { + if (args != null) { + var type = args.getGraphQLType(); + var mappedType = type != null ? VehicleOptimizationTypeMapper.map(type) : null; + if (mappedType != null) { + preferences.withOptimizeType(mappedType); + } + var triangleArgs = args.getGraphQLTriangle(); + if (isScooterTriangleSet(triangleArgs)) { + preferences.withForcedOptimizeTriangle(triangle -> { + triangle + .withSlope(triangleArgs.getGraphQLFlatness()) + .withSafety(triangleArgs.getGraphQLSafety()) + .withTime(triangleArgs.getGraphQLTime()); + }); + } + } + } + + private static boolean isScooterTriangleSet( + GraphQLTypes.GraphQLTriangleScooterFactorsInput args + ) { + return ( + args != null && + args.getGraphQLFlatness() != null && + args.getGraphQLSafety() != null && + args.getGraphQLTime() != null + ); + } + private static void setWalkPreferences( WalkPreferences.Builder preferences, GraphQLTypes.GraphQLWalkPreferencesInput args diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/VehicleOptimizationTypeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/VehicleOptimizationTypeMapper.java index e784588fd35..f908c400b11 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/VehicleOptimizationTypeMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/VehicleOptimizationTypeMapper.java @@ -16,4 +16,13 @@ public static VehicleRoutingOptimizeType map(GraphQLTypes.GraphQLCyclingOptimiza case SAFEST_STREETS -> VehicleRoutingOptimizeType.SAFEST_STREETS; }; } + + public static VehicleRoutingOptimizeType map(GraphQLTypes.GraphQLScooterOptimizationType type) { + return switch (type) { + case SHORTEST_DURATION -> VehicleRoutingOptimizeType.SHORTEST_DURATION; + case FLAT_STREETS -> VehicleRoutingOptimizeType.FLAT_STREETS; + case SAFE_STREETS -> VehicleRoutingOptimizeType.SAFE_STREETS; + case SAFEST_STREETS -> VehicleRoutingOptimizeType.SAFEST_STREETS; + }; + } } From fe052e0536150db48b5c66ede3c6ae6f90060f10 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 26 Feb 2024 16:32:41 +0200 Subject: [PATCH 117/165] Temporarily use a non-stable graphql-java release --- pom.xml | 6 +++++- .../transmodel/support/AbortOnTimeoutExecutionStrategy.java | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4bf56201d45..4dc4c3be73e 100644 --- a/pom.xml +++ b/pom.xml @@ -866,7 +866,11 @@ com.graphql-java graphql-java - 21.3 + + 0.0.0-2024-02-23T10-00-36-19a50f7 com.graphql-java diff --git a/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnTimeoutExecutionStrategy.java b/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnTimeoutExecutionStrategy.java index d143d65421c..a925aff0a1d 100644 --- a/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnTimeoutExecutionStrategy.java +++ b/src/main/java/org/opentripplanner/apis/transmodel/support/AbortOnTimeoutExecutionStrategy.java @@ -1,6 +1,7 @@ package org.opentripplanner.apis.transmodel.support; import graphql.execution.AsyncExecutionStrategy; +import graphql.execution.ExecutionStrategyParameters; import graphql.schema.DataFetchingEnvironment; import java.io.Closeable; import java.util.concurrent.CompletableFuture; @@ -27,13 +28,14 @@ public class AbortOnTimeoutExecutionStrategy extends AsyncExecutionStrategy impl @Override protected CompletableFuture handleFetchingException( DataFetchingEnvironment environment, + ExecutionStrategyParameters params, Throwable e ) { if (e instanceof OTPRequestTimeoutException te) { logTimeoutProgress(); throw te; } - return super.handleFetchingException(environment, e); + return super.handleFetchingException(environment, params, e); } @SuppressWarnings("Convert2MethodRef") From 752f08a12027eaf606ff9522dcff953ec85a8a81 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 26 Feb 2024 16:49:45 +0200 Subject: [PATCH 118/165] Implement car preferences --- .../apis/gtfs/mapping/RouteRequestMapper.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index 3dc851e4cbd..a45b88cc889 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -17,6 +17,7 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.preference.BikePreferences; +import org.opentripplanner.routing.api.request.preference.CarPreferences; import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.routing.api.request.preference.ScooterPreferences; @@ -172,6 +173,7 @@ private static void setStreetPreferences( preferences.withBike(bicycle -> setBicyclePreferences(bicycle, args.getGraphQLBicycle(), environment) ); + preferences.withCar(car -> setCarPreferences(car, args.getGraphQLCar(), environment)); preferences.withScooter(scooter -> setScooterPreferences(scooter, args.getGraphQLScooter())); preferences.withWalk(walk -> setWalkPreferences(walk, args.getGraphQLWalk())); } @@ -370,6 +372,59 @@ private static boolean isBicycleTriangleSet( ); } + private static void setCarPreferences( + CarPreferences.Builder preferences, + GraphQLTypes.GraphQLCarPreferencesInput args, + DataFetchingEnvironment environment + ) { + if (args != null) { + var reluctance = args.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); + } + preferences.withParking(parking -> + setCarParkingPreferences(parking, args.getGraphQLParking(), environment) + ); + preferences.withRental(rental -> setCarRentalPreferences(rental, args.getGraphQLRental())); + } + } + + private static void setCarParkingPreferences( + VehicleParkingPreferences.Builder preferences, + GraphQLTypes.GraphQLCarParkingPreferencesInput args, + DataFetchingEnvironment environment + ) { + if (args != null) { + var unpreferredCost = args.getGraphQLUnpreferredCost(); + if (unpreferredCost != null) { + preferences.withUnpreferredVehicleParkingTagCost(unpreferredCost.toSeconds()); + } + var filters = getParkingFilters(environment, "car"); + preferences.withRequiredVehicleParkingTags(parseSelectFilters(filters)); + preferences.withBannedVehicleParkingTags(parseNotFilters(filters)); + var preferred = getParkingPreferred(environment, "car"); + preferences.withPreferredVehicleParkingTags(parseSelectFilters(preferred)); + preferences.withNotPreferredVehicleParkingTags(parseNotFilters(preferred)); + } + } + + private static void setCarRentalPreferences( + VehicleRentalPreferences.Builder preferences, + GraphQLTypes.GraphQLCarRentalPreferencesInput args + ) { + if (args != null) { + var allowedNetworks = args.getGraphQLAllowedNetworks(); + if (allowedNetworks != null && allowedNetworks.size() > 0) { + preferences.withBannedNetworks(Set.copyOf(allowedNetworks)); + } + var bannedNetworks = args.getGraphQLBannedNetworks(); + if (bannedNetworks != null && bannedNetworks.size() > 0) { + preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); + } + // TODO validate if station based car systems work before adding destination policy + } + } + private static void setScooterPreferences( ScooterPreferences.Builder preferences, GraphQLTypes.GraphQLScooterPreferencesInput args From fee5f9ab8b4ec3d9c3be130f2e8c674e6482d48d Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 27 Feb 2024 13:46:29 +0200 Subject: [PATCH 119/165] Add recommendation for using first/last --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 8a5286d24f2..20d4b4cc477 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4260,8 +4260,10 @@ type QueryType { after: String """ - How many new itineraries should at maximum be returned in forward pagination. This parameter is - part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) + How many new itineraries should at maximum be returned in forward pagination. It's recommended to + use the same value as was used for the `numberOfItineraries` in the original search for optimal + performance. This parameter is part of the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) and should be used together with the `after` parameter. """ first: Int @@ -4276,8 +4278,10 @@ type QueryType { before: String """ - How many new itineraries should at maximum be returned in backwards pagination. This parameter is - part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) + How many new itineraries should at maximum be returned in backwards pagination. It's recommended to + use the same value as was used for the `numberOfItineraries` in the original search for optimal + performance. This parameter is part of the + [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) and should be used together with the `before` parameter. """ last: Int From 4cfa762b63c1b4807b05143012c7f56438ca903f Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 27 Feb 2024 15:22:57 +0200 Subject: [PATCH 120/165] Implement transit preferences --- .../apis/gtfs/mapping/RouteRequestMapper.java | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index a45b88cc889..9c42e6db099 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -21,6 +21,7 @@ import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; import org.opentripplanner.routing.api.request.preference.ScooterPreferences; +import org.opentripplanner.routing.api.request.preference.TransferPreferences; import org.opentripplanner.routing.api.request.preference.TransitPreferences; import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences; @@ -113,7 +114,9 @@ private static void setPreferences( prefs.withItineraryFilter(filters -> setItineraryFilters(filters, args.getGraphQLItineraryFilter()) ); - prefs.withTransit(transit -> setTransitPreferences(transit, args, environment)); + prefs.withTransit(transit -> { + prefs.withTransfer(transfer -> setTransitPreferences(transit, transfer, args, environment)); + }); setStreetPreferences(prefs, preferenceArgs.getGraphQLStreet(), environment); setAccessibilityPreferences(request, preferenceArgs.getGraphQLAccessibility()); } @@ -141,7 +144,8 @@ private static void setItineraryFilters( } private static void setTransitPreferences( - TransitPreferences.Builder preferences, + TransitPreferences.Builder transitPreferences, + TransferPreferences.Builder transferPreferences, GraphQLTypes.GraphQLQueryTypePlanConnectionArgs args, DataFetchingEnvironment environment ) { @@ -160,7 +164,60 @@ private static void setTransitPreferences( mode -> (Double) ((Map) mode.get("cost")).get("reluctance") ) ); - preferences.setReluctanceForMode(reluctanceForMode); + transitPreferences.setReluctanceForMode(reluctanceForMode); + } + var transitArgs = args.getGraphQLPreferences().getGraphQLTransit(); + if (transitArgs != null) { + var board = transitArgs.getGraphQLBoard(); + if (board != null) { + var slack = board.getGraphQLSlack(); + if (slack != null) { + transitPreferences.withDefaultBoardSlackSec((int) slack.toSeconds()); + } + var waitReluctance = board.getGraphQLWaitReluctance(); + if (waitReluctance != null) { + transferPreferences.withWaitReluctance(waitReluctance); + } + } + var alight = transitArgs.getGraphQLAlight(); + if (alight != null) { + var slack = alight.getGraphQLSlack(); + if (slack != null) { + transitPreferences.withDefaultAlightSlackSec((int) slack.toSeconds()); + } + } + var transfer = transitArgs.getGraphQLTransfer(); + if (transfer != null) { + var cost = transfer.getGraphQLCost(); + if (cost != null) { + transferPreferences.withCost(cost.toSeconds()); + } + var slack = transfer.getGraphQLSlack(); + if (slack != null) { + transferPreferences.withSlack((int) slack.toSeconds()); + } + var maxTransfers = transfer.getGraphQLMaximumTransfers(); + if (maxTransfers != null) { + transferPreferences.withMaxTransfers(maxTransfers); + } + var additionalTransfers = transfer.getGraphQLMaximumAdditionalTransfers(); + if (additionalTransfers != null) { + transferPreferences.withMaxAdditionalTransfers(additionalTransfers); + } + } + var timetable = transitArgs.getGraphQLTimetable(); + if (timetable != null) { + var excludeUpdates = timetable.getGraphQLExcludeRealTimeUpdates(); + transitPreferences.setIgnoreRealtimeUpdates(Boolean.TRUE.equals(excludeUpdates)); + var includePlannedCancellations = timetable.getGraphQLIncludePlannedCancellations(); + transitPreferences.setIncludePlannedCancellations( + Boolean.TRUE.equals(includePlannedCancellations) + ); + var includeRealtimeCancellations = timetable.getGraphQLIncludeRealTimeCancellations(); + transitPreferences.setIncludeRealtimeCancellations( + Boolean.TRUE.equals(includeRealtimeCancellations) + ); + } } } From 33de97af9eb6b9ef8c48ef6dfab1decf08b2c676 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 27 Feb 2024 17:12:18 +0200 Subject: [PATCH 121/165] Validate access/egress/transfer --- .../apis/gtfs/mapping/RouteRequestMapper.java | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index 9c42e6db099..2ab945f4826 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -3,6 +3,7 @@ import graphql.schema.DataFetchingEnvironment; import java.time.Instant; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -27,6 +28,7 @@ import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences; import org.opentripplanner.routing.api.request.preference.VehicleWalkingPreferences; import org.opentripplanner.routing.api.request.preference.WalkPreferences; +import org.opentripplanner.routing.api.request.request.JourneyRequest; import org.opentripplanner.routing.api.request.request.filter.SelectRequest; import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest; import org.opentripplanner.transit.model.basic.MainAndSubMode; @@ -72,7 +74,7 @@ public static RouteRequest toRouteRequest( request.withPreferences(preferences -> setPreferences(preferences, request, args, environment)); - setModes(request, args.getGraphQLModes(), environment); + setModes(request.journey(), args.getGraphQLModes(), environment); return request; } @@ -588,35 +590,36 @@ private static void setAccessibilityPreferences( * TODO this doesn't support multiple street modes yet */ private static void setModes( - RouteRequest request, + JourneyRequest journey, GraphQLTypes.GraphQLPlanModesInput modesInput, DataFetchingEnvironment environment ) { var direct = modesInput.getGraphQLDirect(); if (Boolean.TRUE.equals(modesInput.getGraphQLTransitOnly())) { - request.journey().direct().setMode(StreetMode.NOT_SET); + journey.direct().setMode(StreetMode.NOT_SET); } else if (direct != null && direct.size() > 0) { - request.journey().direct().setMode(DirectModeMapper.map(direct.getFirst())); + journey.direct().setMode(DirectModeMapper.map(direct.getFirst())); } var transit = modesInput.getGraphQLTransit(); if (Boolean.TRUE.equals(modesInput.getGraphQLDirectOnly())) { - request.journey().transit().disable(); + journey.transit().disable(); } else if (transit != null) { var access = transit.getGraphQLAccess(); if (access != null && access.size() > 0) { - request.journey().access().setMode(AccessModeMapper.map(access.getFirst())); + journey.access().setMode(AccessModeMapper.map(access.getFirst())); } var egress = transit.getGraphQLEgress(); if (egress != null && egress.size() > 0) { - request.journey().egress().setMode(EgressModeMapper.map(egress.getFirst())); + journey.egress().setMode(EgressModeMapper.map(egress.getFirst())); } var transfer = transit.getGraphQLTransfer(); if (transfer != null && transfer.size() > 0) { - request.journey().transfer().setMode(TransferModeMapper.map(transfer.getFirst())); + journey.transfer().setMode(TransferModeMapper.map(transfer.getFirst())); } + validateStreetModes(journey); var transitModes = getTransitModes(environment); if (transitModes.size() > 0) { @@ -634,11 +637,26 @@ private static void setModes( filterRequestBuilder.addSelect( SelectRequest.of().withTransportModes(mainAndSubModes).build() ); - request.journey().transit().setFilters(List.of(filterRequestBuilder.build())); + journey.transit().setFilters(List.of(filterRequestBuilder.build())); } } } + /** + * TODO this doesn't support multiple street modes yet + */ + private static void validateStreetModes(JourneyRequest journey) { + Set modes = new HashSet(); + modes.add(journey.access().mode()); + modes.add(journey.egress().mode()); + modes.add(journey.transfer().mode()); + if (modes.contains(StreetMode.BIKE) && modes.size() != 1) { + throw new IllegalArgumentException( + "If BICYCLE is used for access, egress or transfer, then it should be used for all." + ); + } + } + /** * This methods returns list of modes and their costs from the argument structure: * modes.transit.transit. This methods circumvents a bug in graphql-codegen as getting a list of From 20e9550dc0896815a58ce6fd590b0891aa6e19ca Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 27 Feb 2024 23:33:28 +0200 Subject: [PATCH 122/165] Remove destination policy from car and implement it for scooter --- .../apis/gtfs/generated/GraphQLTypes.java | 44 ------------------- .../apis/gtfs/mapping/RouteRequestMapper.java | 11 ++++- .../opentripplanner/apis/gtfs/schema.graphqls | 24 ---------- 3 files changed, 9 insertions(+), 70 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index aedd564545a..3cb0eb1e5a2 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -541,16 +541,11 @@ public static class GraphQLCarRentalPreferencesInput { private List allowedNetworks; private List bannedNetworks; - private GraphQLDestinationCarPolicyInput destinationCarPolicy; public GraphQLCarRentalPreferencesInput(Map args) { if (args != null) { this.allowedNetworks = (List) args.get("allowedNetworks"); this.bannedNetworks = (List) args.get("bannedNetworks"); - this.destinationCarPolicy = - new GraphQLDestinationCarPolicyInput( - (Map) args.get("destinationCarPolicy") - ); } } @@ -562,10 +557,6 @@ public List getGraphQLBannedNetworks() { return this.bannedNetworks; } - public GraphQLDestinationCarPolicyInput getGraphQLDestinationCarPolicy() { - return this.destinationCarPolicy; - } - public void setGraphQLAllowedNetworks(List allowedNetworks) { this.allowedNetworks = allowedNetworks; } @@ -573,12 +564,6 @@ public void setGraphQLAllowedNetworks(List allowedNetworks) { public void setGraphQLBannedNetworks(List bannedNetworks) { this.bannedNetworks = bannedNetworks; } - - public void setGraphQLDestinationCarPolicy( - GraphQLDestinationCarPolicyInput destinationCarPolicy - ) { - this.destinationCarPolicy = destinationCarPolicy; - } } public static class GraphQLCyclingOptimizationInput { @@ -714,35 +699,6 @@ public void setGraphQLKeepingCost(org.opentripplanner.framework.model.Cost keepi } } - public static class GraphQLDestinationCarPolicyInput { - - private Boolean allowKeeping; - private org.opentripplanner.framework.model.Cost keepingCost; - - public GraphQLDestinationCarPolicyInput(Map args) { - if (args != null) { - this.allowKeeping = (Boolean) args.get("allowKeeping"); - this.keepingCost = (org.opentripplanner.framework.model.Cost) args.get("keepingCost"); - } - } - - public Boolean getGraphQLAllowKeeping() { - return this.allowKeeping; - } - - public org.opentripplanner.framework.model.Cost getGraphQLKeepingCost() { - return this.keepingCost; - } - - public void setGraphQLAllowKeeping(Boolean allowKeeping) { - this.allowKeeping = allowKeeping; - } - - public void setGraphQLKeepingCost(org.opentripplanner.framework.model.Cost keepingCost) { - this.keepingCost = keepingCost; - } - } - public static class GraphQLDestinationScooterPolicyInput { private Boolean allowKeeping; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index 2ab945f4826..accd2f40fb3 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -480,7 +480,6 @@ private static void setCarRentalPreferences( if (bannedNetworks != null && bannedNetworks.size() > 0) { preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); } - // TODO validate if station based car systems work before adding destination policy } } @@ -516,7 +515,15 @@ private static void setScooterRentalPreferences( if (bannedNetworks != null && bannedNetworks.size() > 0) { preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); } - // TODO validate if station based scooter systems work before adding destination policy + var destinationPolicy = args.getGraphQLDestinationScooterPolicy(); + if (destinationPolicy != null) { + var allowed = destinationPolicy.getGraphQLAllowKeeping(); + preferences.withAllowArrivingInRentedVehicleAtDestination(Boolean.TRUE.equals(allowed)); + var cost = destinationPolicy.getGraphQLKeepingCost(); + if (cost != null) { + preferences.withArrivingInRentalVehicleAtDestinationCost(cost.toSeconds()); + } + } } } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 20d4b4cc477..453add335ed 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -377,24 +377,6 @@ input DestinationBicyclePolicyInput { keepingCost: Cost } -""" -Is it possible to arrive to the destination with a rented car and does it -come with an extra cost. -""" -input DestinationCarPolicyInput { - """ - For networks that require station drop-off, should the routing engine offer results that go directly to the destination without dropping off the rental car first. - """ - allowKeeping: Boolean - - """ - Cost associated with arriving to the destination with a rented car. - No cost is applied if arriving to the destination after dropping off the rented - car. - """ - keepingCost: Cost -} - """ Is it possible to arrive to the destination with a rented scooter and does it come with an extra cost. @@ -4347,12 +4329,6 @@ input ScooterRentalPreferencesInput { Preferences related to car rental (station based or floating car rental). """ input CarRentalPreferencesInput { - """ - Is it possible to arrive to the destination with a rented car and does it - come with an extra cost. - """ - destinationCarPolicy: DestinationCarPolicyInput - """ Rental networks which can be potentially used as part of an itinerary. """ From fd29a03eb47024c538066608fcef794284c459f4 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 27 Feb 2024 23:37:20 +0200 Subject: [PATCH 123/165] Set arriveBy --- .../opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index accd2f40fb3..9d166bf3872 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -48,6 +48,7 @@ public static RouteRequest toRouteRequest( request.setDateTime(args.getGraphQLDateTime().getGraphQLEarliestDeparture().toInstant()); } else if (dateTime.getGraphQLLatestArrival() != null) { request.setDateTime(args.getGraphQLDateTime().getGraphQLLatestArrival().toInstant()); + request.setArriveBy(true); } else { request.setDateTime(Instant.now()); } From 6d23c57ce1e0bb3ae9e82705978b616f6a298f70 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Wed, 28 Feb 2024 14:32:48 +0200 Subject: [PATCH 124/165] Update test class name --- .../apis/gtfs/mapping/LegacyRouteRequestMapperTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapperTest.java index ff0321bc5db..379338de21d 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapperTest.java @@ -38,7 +38,7 @@ import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; -class RouteRequestMapperTest implements PlanTestConstants { +class LegacyRouteRequestMapperTest implements PlanTestConstants { static final GraphQLRequestContext context; From a6bdc71e25602cec94d4703fbd180e76cff0a30d Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 1 Mar 2024 16:30:10 +0200 Subject: [PATCH 125/165] Mark old plan query as deprecated --- .../resources/org/opentripplanner/apis/gtfs/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 453add335ed..70c15ecbda2 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4152,7 +4152,7 @@ type QueryType { used correctly to get meaningful itineraries). """ startTransitTripId: String @deprecated(reason: "Not implemented in OTP2") - ): Plan @async + ): Plan @async @deprecated(reason: "Use planConnection instead") """ Plan (itinerary) search that follows From 9f3d64d794696201e9593f377b42eb5c1788d798 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 4 Mar 2024 14:24:51 +0200 Subject: [PATCH 126/165] Add validation for costs, reluctances and durations --- .../apis/gtfs/GraphQLScalars.java | 90 ++++++++++++++++++- .../apis/gtfs/GtfsGraphQLIndex.java | 7 +- .../apis/gtfs/mapping/RouteRequestMapper.java | 21 +++-- .../framework/time/DurationUtils.java | 64 ++++++++++++- 4 files changed, 168 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index f25598499ef..e807cfecc3b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -176,6 +176,8 @@ private static Optional validateCoordinate(double coordinate) { .name("Cost") .coercing( new Coercing() { + private static final int MAX_COST = 1000000; + @Override public Integer serialize(@Nonnull Object dataFetcherResult) throws CoercingSerializeException { @@ -195,6 +197,14 @@ public Integer serialize(@Nonnull Object dataFetcherResult) @Override public Cost parseValue(Object input) throws CoercingParseValueException { if (input instanceof Integer intValue) { + if (intValue < 0) { + throw new CoercingParseValueException("Cost cannot be negative"); + } + if (intValue > MAX_COST) { + throw new CoercingParseValueException( + "Cost cannot be greater than %d".formatted(MAX_COST) + ); + } return Cost.costOfSeconds(intValue); } throw new CoercingParseValueException( @@ -205,7 +215,16 @@ public Cost parseValue(Object input) throws CoercingParseValueException { @Override public Cost parseLiteral(Object input) throws CoercingParseLiteralException { if (input instanceof IntValue intValue) { - return Cost.costOfSeconds(intValue.getValue().intValue()); + var value = intValue.getValue().intValue(); + if (value < 0) { + throw new CoercingParseLiteralException("Cost cannot be negative"); + } + if (value > MAX_COST) { + throw new CoercingParseLiteralException( + "Cost cannot be greater than %d".formatted(MAX_COST) + ); + } + return Cost.costOfSeconds(value); } throw new CoercingParseLiteralException( "Expected an integer, got: " + input.getClass().getSimpleName() @@ -382,4 +401,73 @@ private static Optional validateRatio(double ratio) { } ) .build(); + + public static final GraphQLScalarType reluctanceScalar = GraphQLScalarType + .newScalar() + .name("Reluctance") + .coercing( + new Coercing() { + private static final double MAX_Reluctance = 100000; + + @Override + public Double serialize(@Nonnull Object dataFetcherResult) + throws CoercingSerializeException { + if (dataFetcherResult instanceof Double doubleValue) { + return doubleValue; + } else if (dataFetcherResult instanceof Float floatValue) { + return floatValue.doubleValue(); + } else { + throw new CoercingSerializeException( + "Cannot serialize object of class %s as a reluctance".formatted( + dataFetcherResult.getClass().getSimpleName() + ) + ); + } + } + + @Override + public Double parseValue(Object input) throws CoercingParseValueException { + if (input instanceof Double doubleValue) { + if (Double.doubleToRawLongBits(doubleValue) < 0) { + throw new CoercingParseValueException("Reluctance cannot be negative"); + } + if (doubleValue > MAX_Reluctance + 0.001) { + throw new CoercingParseValueException( + "Reluctance cannot be greater than %s".formatted(MAX_Reluctance) + ); + } + return doubleValue; + } + throw new CoercingParseValueException( + "Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input) + ); + } + + @Override + public Double parseLiteral(Object input) throws CoercingParseLiteralException { + if (input instanceof FloatValue reluctance) { + return validateLiteral(reluctance.getValue().doubleValue()); + } + if (input instanceof IntValue reluctance) { + return validateLiteral(reluctance.getValue().doubleValue()); + } + throw new CoercingParseLiteralException( + "Expected a number, got: " + input.getClass().getSimpleName() + ); + } + + private static double validateLiteral(double reluctance) { + if (Double.doubleToRawLongBits(reluctance) < 0) { + throw new CoercingParseLiteralException("Reluctance cannot be negative"); + } + if (reluctance > MAX_Reluctance + 0.001) { + throw new CoercingParseLiteralException( + "Reluctance cannot be greater than %s".formatted(MAX_Reluctance) + ); + } + return reluctance; + } + } + ) + .build(); } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java index 7cc059bf7f4..f97f4a7817f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java @@ -116,6 +116,7 @@ protected static GraphQLSchema buildSchema() { .scalar(GraphQLScalars.ratioScalar) .scalar(GraphQLScalars.coordinateValueScalar) .scalar(GraphQLScalars.costScalar) + .scalar(GraphQLScalars.reluctanceScalar) .scalar(ExtendedScalars.GraphQLLong) .scalar(ExtendedScalars.Locale) .scalar( @@ -124,12 +125,6 @@ protected static GraphQLSchema buildSchema() { .aliasedScalar(ExtendedScalars.NonNegativeFloat) .build() ) - .scalar( - ExtendedScalars - .newAliasedScalar("Reluctance") - .aliasedScalar(ExtendedScalars.NonNegativeFloat) - .build() - ) .type("Node", type -> type.typeResolver(new NodeTypeResolver())) .type("PlaceInterface", type -> type.typeResolver(new PlaceInterfaceTypeResolver())) .type("StopPosition", type -> type.typeResolver(new StopPosition() {})) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index 9d166bf3872..e2833767d1f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -14,6 +14,7 @@ import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.framework.graphql.GraphQLUtils; +import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; @@ -56,7 +57,9 @@ public static RouteRequest toRouteRequest( request.setTo(parseGenericLocation(args.getGraphQLDestination())); request.setLocale(GraphQLUtils.getLocale(environment, args.getGraphQLLocale())); if (args.getGraphQLSearchWindow() != null) { - request.setSearchWindow(args.getGraphQLSearchWindow()); + request.setSearchWindow( + DurationUtils.requireNonNegativeLarge(args.getGraphQLSearchWindow(), "searchWindow") + ); } if (args.getGraphQLBefore() != null) { @@ -175,7 +178,9 @@ private static void setTransitPreferences( if (board != null) { var slack = board.getGraphQLSlack(); if (slack != null) { - transitPreferences.withDefaultBoardSlackSec((int) slack.toSeconds()); + transitPreferences.withDefaultBoardSlackSec( + (int) DurationUtils.requireNonNegativeMedium(slack, "board slack").toSeconds() + ); } var waitReluctance = board.getGraphQLWaitReluctance(); if (waitReluctance != null) { @@ -186,7 +191,9 @@ private static void setTransitPreferences( if (alight != null) { var slack = alight.getGraphQLSlack(); if (slack != null) { - transitPreferences.withDefaultAlightSlackSec((int) slack.toSeconds()); + transitPreferences.withDefaultAlightSlackSec( + (int) DurationUtils.requireNonNegativeMedium(slack, "alight slack").toSeconds() + ); } } var transfer = transitArgs.getGraphQLTransfer(); @@ -197,7 +204,9 @@ private static void setTransitPreferences( } var slack = transfer.getGraphQLSlack(); if (slack != null) { - transferPreferences.withSlack((int) slack.toSeconds()); + transferPreferences.withSlack( + (int) DurationUtils.requireNonNegativeMedium(slack, "transfer slack").toSeconds() + ); } var maxTransfers = transfer.getGraphQLMaximumTransfers(); if (maxTransfers != null) { @@ -278,7 +287,9 @@ private static void setBicycleWalkPreferences( } var mountTime = args.getGraphQLMountDismountTime(); if (mountTime != null) { - preferences.withMountDismountTime(mountTime); + preferences.withMountDismountTime( + DurationUtils.requireNonNegativeShort(mountTime, "bicycle mount dismount time") + ); } var cost = args.getGraphQLCost(); if (cost != null) { diff --git a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java index bf59964fbb2..6f5f3d48df3 100644 --- a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java +++ b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java @@ -14,7 +14,7 @@ /** * This class extend the Java {@link Duration} with utility functionality to parse and convert - * integer and text to a {@link Duration}. + * integer and text to a {@link Duration}. This class also contains methods to validate durations. *

* OTP make have use of the Duration in a lenient ISO-8601 duration format. For example: *

@@ -181,7 +181,67 @@ public static String msToSecondsStr(long timeMs) {
   public static Duration requireNonNegative(Duration value) {
     Objects.requireNonNull(value);
     if (value.isNegative()) {
-      throw new IllegalArgumentException("Duration can no be negative: " + value);
+      throw new IllegalArgumentException("Duration can't be negative: " + value);
+    }
+    return value;
+  }
+
+  /**
+   * Checks that duration is not negative and not over 2 days.
+   *
+   * @param subject used to identify name of the problematic value when throwing an exception.
+   */
+  public static Duration requireNonNegativeLarge(Duration value, String subject) {
+    Objects.requireNonNull(value);
+    if (value.isNegative()) {
+      throw new IllegalArgumentException(
+        "Duration %s can't be negative: %s.".formatted(subject, value)
+      );
+    }
+    if (value.compareTo(Duration.ofDays(2)) > 0) {
+      throw new IllegalArgumentException(
+        "Duration %s can't be longer than two days: %s.".formatted(subject, value)
+      );
+    }
+    return value;
+  }
+
+  /**
+   * Checks that duration is not negative and not over 2 hours.
+   *
+   * @param subject used to identify name of the problematic value when throwing an exception.
+   */
+  public static Duration requireNonNegativeMedium(Duration value, String subject) {
+    Objects.requireNonNull(value);
+    if (value.isNegative()) {
+      throw new IllegalArgumentException(
+        "Duration %s can't be negative: %s.".formatted(subject, value)
+      );
+    }
+    if (value.compareTo(Duration.ofHours(2)) > 0) {
+      throw new IllegalArgumentException(
+        "Duration %s can't be longer than two hours: %s.".formatted(subject, value)
+      );
+    }
+    return value;
+  }
+
+  /**
+   * Checks that duration is not negative and not over 30 minutes.
+   *
+   * @param subject used to identify name of the problematic value when throwing an exception.
+   */
+  public static Duration requireNonNegativeShort(Duration value, String subject) {
+    Objects.requireNonNull(value);
+    if (value.isNegative()) {
+      throw new IllegalArgumentException(
+        "Duration %s can't be negative: %s.".formatted(subject, value)
+      );
+    }
+    if (value.compareTo(Duration.ofMinutes(30)) > 0) {
+      throw new IllegalArgumentException(
+        "Duration %s can't be longer than 30 minutes: %s.".formatted(subject, value)
+      );
     }
     return value;
   }

From 61ab6e066e865c5a8a5f8eac1561e08b4a179d83 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Mon, 4 Mar 2024 23:19:55 +0200
Subject: [PATCH 127/165] Use uppercase for constants

---
 .../apis/gtfs/GraphQLScalars.java             | 24 +++++++++----------
 .../apis/gtfs/GtfsGraphQLIndex.java           | 20 ++++++++--------
 .../apis/gtfs/GraphQLScalarsTest.java         |  6 ++---
 3 files changed, 25 insertions(+), 25 deletions(-)

diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java
index e807cfecc3b..ee49b30b8a8 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java
@@ -26,11 +26,11 @@
 
 public class GraphQLScalars {
 
-  private static final ObjectMapper geoJsonMapper = new ObjectMapper()
+  private static final ObjectMapper GEOJSON_MAPPER = new ObjectMapper()
     .registerModule(new JtsModule(GeometryUtils.getGeometryFactory()));
-  public static GraphQLScalarType durationScalar = DurationScalarFactory.createDurationScalar();
+  public static final GraphQLScalarType DURATION_SCALAR = DurationScalarFactory.createDurationScalar();
 
-  public static GraphQLScalarType polylineScalar = GraphQLScalarType
+  public static final GraphQLScalarType POLYLINE_SCALAR = GraphQLScalarType
     .newScalar()
     .name("Polyline")
     .description(
@@ -59,7 +59,7 @@ public String parseLiteral(Object input) {
     )
     .build();
 
-  public static final GraphQLScalarType offsetDateTimeScalar = GraphQLScalarType
+  public static final GraphQLScalarType OFFSET_DATETIME_SCALAR = GraphQLScalarType
     .newScalar()
     .name("OffsetDateTime")
     .coercing(
@@ -109,7 +109,7 @@ private static CoercingParseLiteralException literalException(Object input) {
     )
     .build();
 
-  public static final GraphQLScalarType coordinateValueScalar = GraphQLScalarType
+  public static final GraphQLScalarType COORDINATE_VALUE_SCALAR = GraphQLScalarType
     .newScalar()
     .name("CoordinateValue")
     .coercing(
@@ -171,7 +171,7 @@ private static Optional validateCoordinate(double coordinate) {
     )
     .build();
 
-  public static final GraphQLScalarType costScalar = GraphQLScalarType
+  public static final GraphQLScalarType COST_SCALAR = GraphQLScalarType
     .newScalar()
     .name("Cost")
     .coercing(
@@ -234,7 +234,7 @@ public Cost parseLiteral(Object input) throws CoercingParseLiteralException {
     )
     .build();
 
-  public static GraphQLScalarType geoJsonScalar = GraphQLScalarType
+  public static GraphQLScalarType GEOJSON_SCALAR = GraphQLScalarType
     .newScalar()
     .name("GeoJson")
     .description("Geographic data structures in JSON format. See: https://geojson.org/")
@@ -244,7 +244,7 @@ public Cost parseLiteral(Object input) throws CoercingParseLiteralException {
         public JsonNode serialize(Object dataFetcherResult) throws CoercingSerializeException {
           if (dataFetcherResult instanceof Geometry) {
             var geom = (Geometry) dataFetcherResult;
-            return geoJsonMapper.valueToTree(geom);
+            return GEOJSON_MAPPER.valueToTree(geom);
           }
           return null;
         }
@@ -262,7 +262,7 @@ public Geometry parseLiteral(Object input) throws CoercingParseLiteralException
     )
     .build();
 
-  public static GraphQLScalarType graphQLIDScalar = GraphQLScalarType
+  public static GraphQLScalarType GRAPHQL_ID_SCALAR = GraphQLScalarType
     .newScalar()
     .name("ID")
     .coercing(
@@ -302,7 +302,7 @@ public Relay.ResolvedGlobalId parseLiteral(Object input)
     )
     .build();
 
-  public static GraphQLScalarType gramsScalar = GraphQLScalarType
+  public static GraphQLScalarType GRAMS_SCALAR = GraphQLScalarType
     .newScalar()
     .name("Grams")
     .coercing(
@@ -337,7 +337,7 @@ public Grams parseLiteral(Object input) throws CoercingParseLiteralException {
     )
     .build();
 
-  public static final GraphQLScalarType ratioScalar = GraphQLScalarType
+  public static final GraphQLScalarType RATIO_SCALAR = GraphQLScalarType
     .newScalar()
     .name("Ratio")
     .coercing(
@@ -402,7 +402,7 @@ private static Optional validateRatio(double ratio) {
     )
     .build();
 
-  public static final GraphQLScalarType reluctanceScalar = GraphQLScalarType
+  public static final GraphQLScalarType RELUCTANCE_SCALAR = GraphQLScalarType
     .newScalar()
     .name("Reluctance")
     .coercing(
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java
index f97f4a7817f..73f60fdda98 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java
@@ -107,16 +107,16 @@ protected static GraphQLSchema buildSchema() {
       IntrospectionTypeWiring typeWiring = new IntrospectionTypeWiring(typeRegistry);
       RuntimeWiring runtimeWiring = RuntimeWiring
         .newRuntimeWiring()
-        .scalar(GraphQLScalars.durationScalar)
-        .scalar(GraphQLScalars.polylineScalar)
-        .scalar(GraphQLScalars.geoJsonScalar)
-        .scalar(GraphQLScalars.graphQLIDScalar)
-        .scalar(GraphQLScalars.gramsScalar)
-        .scalar(GraphQLScalars.offsetDateTimeScalar)
-        .scalar(GraphQLScalars.ratioScalar)
-        .scalar(GraphQLScalars.coordinateValueScalar)
-        .scalar(GraphQLScalars.costScalar)
-        .scalar(GraphQLScalars.reluctanceScalar)
+        .scalar(GraphQLScalars.DURATION_SCALAR)
+        .scalar(GraphQLScalars.POLYLINE_SCALAR)
+        .scalar(GraphQLScalars.GEOJSON_SCALAR)
+        .scalar(GraphQLScalars.GRAPHQL_ID_SCALAR)
+        .scalar(GraphQLScalars.GRAMS_SCALAR)
+        .scalar(GraphQLScalars.OFFSET_DATETIME_SCALAR)
+        .scalar(GraphQLScalars.RATIO_SCALAR)
+        .scalar(GraphQLScalars.COORDINATE_VALUE_SCALAR)
+        .scalar(GraphQLScalars.COST_SCALAR)
+        .scalar(GraphQLScalars.RELUCTANCE_SCALAR)
         .scalar(ExtendedScalars.GraphQLLong)
         .scalar(ExtendedScalars.Locale)
         .scalar(
diff --git a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLScalarsTest.java b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLScalarsTest.java
index 90b35a31b9f..6c6fc104880 100644
--- a/src/test/java/org/opentripplanner/apis/gtfs/GraphQLScalarsTest.java
+++ b/src/test/java/org/opentripplanner/apis/gtfs/GraphQLScalarsTest.java
@@ -15,7 +15,7 @@ class GraphQLScalarsTest {
 
   @Test
   void duration() {
-    var string = GraphQLScalars.durationScalar.getCoercing().serialize(Duration.ofMinutes(30));
+    var string = GraphQLScalars.DURATION_SCALAR.getCoercing().serialize(Duration.ofMinutes(30));
     assertEquals("PT30M", string);
   }
 
@@ -23,7 +23,7 @@ void duration() {
   void nonDuration() {
     Assertions.assertThrows(
       CoercingSerializeException.class,
-      () -> GraphQLScalars.durationScalar.getCoercing().serialize(new Object())
+      () -> GraphQLScalars.DURATION_SCALAR.getCoercing().serialize(new Object())
     );
   }
 
@@ -38,7 +38,7 @@ void geoJson() throws JsonProcessingException {
         new Coordinate(0, 0),
       }
     );
-    var geoJson = GraphQLScalars.geoJsonScalar.getCoercing().serialize(polygon);
+    var geoJson = GraphQLScalars.GEOJSON_SCALAR.getCoercing().serialize(polygon);
 
     var jsonNode = ObjectMappers
       .ignoringExtraFields()

From d1fb0707cf0d92d089706466febd447fbbc22895 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Mon, 11 Mar 2024 17:54:21 +0200
Subject: [PATCH 128/165] Apply suggestions from code review

Co-authored-by: Leonard Ehrenfried 
---
 .../java/org/opentripplanner/apis/gtfs/GraphQLScalars.java    | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java
index ee49b30b8a8..d71582e651c 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java
@@ -346,7 +346,7 @@ public Grams parseLiteral(Object input) throws CoercingParseLiteralException {
         public Double serialize(@Nonnull Object dataFetcherResult)
           throws CoercingSerializeException {
           var validationException = new CoercingSerializeException(
-            "Value is under 0 or greater than 1."
+            "Value is less than 0 or greater than 1."
           );
           if (dataFetcherResult instanceof Double doubleValue) {
             return validateRatio(doubleValue).orElseThrow(() -> validationException);
@@ -366,7 +366,7 @@ public Double parseValue(Object input) throws CoercingParseValueException {
           if (input instanceof Double doubleValue) {
             return validateRatio(doubleValue)
               .orElseThrow(() ->
-                new CoercingParseValueException("Value is under 0 or greater than 1.")
+                new CoercingParseValueException("Value is less than 0 or greater than 1.")
               );
           }
           throw new CoercingParseValueException(

From e5f1d23dd13f43eb48f7e4b9e9b57589fb403a45 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Mon, 11 Mar 2024 17:55:43 +0200
Subject: [PATCH 129/165] Make new plan deprecated temporarily instead of the
 old one

---
 .../resources/org/opentripplanner/apis/gtfs/schema.graphqls   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
index 70c15ecbda2..c1c5c0d2b92 100644
--- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
+++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls
@@ -4152,7 +4152,7 @@ type QueryType {
         used correctly to get meaningful itineraries).
         """
         startTransitTripId: String @deprecated(reason: "Not implemented in OTP2")
-    ): Plan @async @deprecated(reason: "Use planConnection instead")
+    ): Plan @async
 
     """
     Plan (itinerary) search that follows
@@ -4267,7 +4267,7 @@ type QueryType {
         and should be used together with the `before` parameter.
         """
         last: Int
-    ): PlanConnection @async
+    ): PlanConnection @async @deprecated(reason: "Experimental and can include breaking changes, use plan instead")
 }
 
 enum RealtimeState {

From 539b3dedb11b8570ad03d1f7b93472ef37748961 Mon Sep 17 00:00:00 2001
From: Joel Lappalainen 
Date: Mon, 11 Mar 2024 18:14:45 +0200
Subject: [PATCH 130/165] Use CollectionUtils isEmpty

---
 .../apis/gtfs/mapping/RouteRequestMapper.java | 25 ++++++++++---------
 .../framework/collection/CollectionUtils.java | 13 ++++++++++
 2 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java
index e2833767d1f..830fb5390f9 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java
@@ -13,6 +13,7 @@
 import javax.annotation.Nullable;
 import org.opentripplanner.apis.gtfs.GraphQLRequestContext;
 import org.opentripplanner.apis.gtfs.generated.GraphQLTypes;
+import org.opentripplanner.framework.collection.CollectionUtils;
 import org.opentripplanner.framework.graphql.GraphQLUtils;
 import org.opentripplanner.framework.time.DurationUtils;
 import org.opentripplanner.model.GenericLocation;
@@ -157,7 +158,7 @@ private static void setTransitPreferences(
   ) {
     var modes = args.getGraphQLModes();
     var transit = getTransitModes(environment);
-    if (!Boolean.TRUE.equals(modes.getGraphQLDirectOnly()) && transit.size() > 0) {
+    if (!Boolean.TRUE.equals(modes.getGraphQLDirectOnly()) && !CollectionUtils.isEmpty(transit)) {
       var reluctanceForMode = transit
         .stream()
         .filter(mode -> mode.containsKey("cost"))
@@ -391,11 +392,11 @@ private static void setBicycleRentalPreferences(
   ) {
     if (args != null) {
       var allowedNetworks = args.getGraphQLAllowedNetworks();
-      if (allowedNetworks != null && allowedNetworks.size() > 0) {
+      if (!CollectionUtils.isEmpty(allowedNetworks)) {
         preferences.withBannedNetworks(Set.copyOf(allowedNetworks));
       }
       var bannedNetworks = args.getGraphQLBannedNetworks();
-      if (bannedNetworks != null && bannedNetworks.size() > 0) {
+      if (!CollectionUtils.isEmpty(bannedNetworks)) {
         preferences.withBannedNetworks(Set.copyOf(bannedNetworks));
       }
       var destinationPolicy = args.getGraphQLDestinationBicyclePolicy();
@@ -485,11 +486,11 @@ private static void setCarRentalPreferences(
   ) {
     if (args != null) {
       var allowedNetworks = args.getGraphQLAllowedNetworks();
-      if (allowedNetworks != null && allowedNetworks.size() > 0) {
+      if (!CollectionUtils.isEmpty(allowedNetworks)) {
         preferences.withBannedNetworks(Set.copyOf(allowedNetworks));
       }
       var bannedNetworks = args.getGraphQLBannedNetworks();
-      if (bannedNetworks != null && bannedNetworks.size() > 0) {
+      if (!CollectionUtils.isEmpty(bannedNetworks)) {
         preferences.withBannedNetworks(Set.copyOf(bannedNetworks));
       }
     }
@@ -520,11 +521,11 @@ private static void setScooterRentalPreferences(
   ) {
     if (args != null) {
       var allowedNetworks = args.getGraphQLAllowedNetworks();
-      if (allowedNetworks != null && allowedNetworks.size() > 0) {
+      if (!CollectionUtils.isEmpty(allowedNetworks)) {
         preferences.withBannedNetworks(Set.copyOf(allowedNetworks));
       }
       var bannedNetworks = args.getGraphQLBannedNetworks();
-      if (bannedNetworks != null && bannedNetworks.size() > 0) {
+      if (!CollectionUtils.isEmpty(bannedNetworks)) {
         preferences.withBannedNetworks(Set.copyOf(bannedNetworks));
       }
       var destinationPolicy = args.getGraphQLDestinationScooterPolicy();
@@ -616,7 +617,7 @@ private static void setModes(
     var direct = modesInput.getGraphQLDirect();
     if (Boolean.TRUE.equals(modesInput.getGraphQLTransitOnly())) {
       journey.direct().setMode(StreetMode.NOT_SET);
-    } else if (direct != null && direct.size() > 0) {
+    } else if (!CollectionUtils.isEmpty(direct)) {
       journey.direct().setMode(DirectModeMapper.map(direct.getFirst()));
     }
 
@@ -625,23 +626,23 @@ private static void setModes(
       journey.transit().disable();
     } else if (transit != null) {
       var access = transit.getGraphQLAccess();
-      if (access != null && access.size() > 0) {
+      if (!CollectionUtils.isEmpty(access)) {
         journey.access().setMode(AccessModeMapper.map(access.getFirst()));
       }
 
       var egress = transit.getGraphQLEgress();
-      if (egress != null && egress.size() > 0) {
+      if (!CollectionUtils.isEmpty(egress)) {
         journey.egress().setMode(EgressModeMapper.map(egress.getFirst()));
       }
 
       var transfer = transit.getGraphQLTransfer();
-      if (transfer != null && transfer.size() > 0) {
+      if (!CollectionUtils.isEmpty(transfer)) {
         journey.transfer().setMode(TransferModeMapper.map(transfer.getFirst()));
       }
       validateStreetModes(journey);
 
       var transitModes = getTransitModes(environment);
-      if (transitModes.size() > 0) {
+      if (!CollectionUtils.isEmpty(transitModes)) {
         var filterRequestBuilder = TransitFilterRequest.of();
         var mainAndSubModes = transitModes
           .stream()
diff --git a/src/main/java/org/opentripplanner/framework/collection/CollectionUtils.java b/src/main/java/org/opentripplanner/framework/collection/CollectionUtils.java
index 1edbfc527d1..b34db13e270 100644
--- a/src/main/java/org/opentripplanner/framework/collection/CollectionUtils.java
+++ b/src/main/java/org/opentripplanner/framework/collection/CollectionUtils.java
@@ -32,4 +32,17 @@ public static  String toString(@Nullable Collection c, String nullText) {
     }
     return stream.collect(Collectors.joining(", ", "[", "]"));
   }
+
+  /**
+   * A null-safe version of isEmpty() for a collection.
+   * 

+ * If the collection is {@code null} then {@code true} is returned. + *

+ * If the collection is empty then {@code true} is returned. + *

+ * Otherwise {@code false} is returned. + */ + public static boolean isEmpty(@Nullable Collection c) { + return c == null || c.isEmpty(); + } } From ba773384e38be52d846f34b061f703cb1e3c9a13 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 11 Mar 2024 18:23:53 +0200 Subject: [PATCH 131/165] Don't override existing preferences if undefined in request --- .../apis/gtfs/mapping/RouteRequestMapper.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index 830fb5390f9..de167ff281c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -221,15 +221,17 @@ private static void setTransitPreferences( var timetable = transitArgs.getGraphQLTimetable(); if (timetable != null) { var excludeUpdates = timetable.getGraphQLExcludeRealTimeUpdates(); - transitPreferences.setIgnoreRealtimeUpdates(Boolean.TRUE.equals(excludeUpdates)); + if (excludeUpdates != null) { + transitPreferences.setIgnoreRealtimeUpdates(excludeUpdates); + } var includePlannedCancellations = timetable.getGraphQLIncludePlannedCancellations(); - transitPreferences.setIncludePlannedCancellations( - Boolean.TRUE.equals(includePlannedCancellations) - ); + if (includePlannedCancellations != null) { + transitPreferences.setIncludePlannedCancellations(includePlannedCancellations); + } var includeRealtimeCancellations = timetable.getGraphQLIncludeRealTimeCancellations(); - transitPreferences.setIncludeRealtimeCancellations( - Boolean.TRUE.equals(includeRealtimeCancellations) - ); + if (includeRealtimeCancellations != null) { + transitPreferences.setIncludeRealtimeCancellations(includeRealtimeCancellations); + } } } } @@ -402,7 +404,9 @@ private static void setBicycleRentalPreferences( var destinationPolicy = args.getGraphQLDestinationBicyclePolicy(); if (destinationPolicy != null) { var allowed = destinationPolicy.getGraphQLAllowKeeping(); - preferences.withAllowArrivingInRentedVehicleAtDestination(Boolean.TRUE.equals(allowed)); + if (allowed != null) { + preferences.withAllowArrivingInRentedVehicleAtDestination(allowed); + } var cost = destinationPolicy.getGraphQLKeepingCost(); if (cost != null) { preferences.withArrivingInRentalVehicleAtDestinationCost(cost.toSeconds()); @@ -531,7 +535,9 @@ private static void setScooterRentalPreferences( var destinationPolicy = args.getGraphQLDestinationScooterPolicy(); if (destinationPolicy != null) { var allowed = destinationPolicy.getGraphQLAllowKeeping(); - preferences.withAllowArrivingInRentedVehicleAtDestination(Boolean.TRUE.equals(allowed)); + if (allowed != null) { + preferences.withAllowArrivingInRentedVehicleAtDestination(allowed); + } var cost = destinationPolicy.getGraphQLKeepingCost(); if (cost != null) { preferences.withArrivingInRentalVehicleAtDestinationCost(cost.toSeconds()); From 089a2caff2d07c1ff34b1358ade63fd2d3375551 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 11 Mar 2024 23:00:14 +0200 Subject: [PATCH 132/165] Fix copypaste mistake --- .../org/opentripplanner/apis/gtfs/GraphQLScalars.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index d71582e651c..a889f63ea90 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -379,12 +379,12 @@ public Double parseLiteral(Object input) throws CoercingParseLiteralException { var validationException = new CoercingParseLiteralException( "Value is under 0 or greater than 1." ); - if (input instanceof FloatValue coordinate) { - return validateRatio(coordinate.getValue().doubleValue()) + if (input instanceof FloatValue ratio) { + return validateRatio(ratio.getValue().doubleValue()) .orElseThrow(() -> validationException); } - if (input instanceof IntValue coordinate) { - return validateRatio(coordinate.getValue().doubleValue()) + if (input instanceof IntValue ratio) { + return validateRatio(ratio.getValue().doubleValue()) .orElseThrow(() -> validationException); } throw new CoercingParseLiteralException( From 3c9e438c1f9850f1b8b98b3d803018d0851326d5 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 11 Mar 2024 23:11:58 +0200 Subject: [PATCH 133/165] Add tests for scalars --- .../apis/gtfs/CoordinateValueTest.java | 98 +++++++++++++++++++ .../apis/gtfs/CostScalarTest.java | 78 +++++++++++++++ .../apis/gtfs/RatioScalarTest.java | 89 +++++++++++++++++ .../apis/gtfs/ReluctanceScalarTest.java | 84 ++++++++++++++++ 4 files changed, 349 insertions(+) create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/CoordinateValueTest.java create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/CostScalarTest.java create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/RatioScalarTest.java create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/ReluctanceScalarTest.java diff --git a/src/test/java/org/opentripplanner/apis/gtfs/CoordinateValueTest.java b/src/test/java/org/opentripplanner/apis/gtfs/CoordinateValueTest.java new file mode 100644 index 00000000000..60028174b5c --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/CoordinateValueTest.java @@ -0,0 +1,98 @@ +package org.opentripplanner.apis.gtfs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import graphql.language.FloatValue; +import graphql.language.IntValue; +import graphql.language.StringValue; +import graphql.schema.CoercingParseLiteralException; +import graphql.schema.CoercingParseValueException; +import graphql.schema.CoercingSerializeException; +import java.math.BigDecimal; +import java.math.BigInteger; +import org.junit.jupiter.api.Test; + +class CoordinateValueScalarTest { + + private static final Double COORDINATE = 10.0; + private static final double COORDINATE_MAX = 180.0; + private static final double COORDINATE_MIN = 180.0; + private static final double TOO_HIGH = 190; + private static final double TOO_LOW = -190; + private static final String TEXT = "foo"; + private static final double DELTA = 0.0001; + + @Test + void testSerialize() { + var coordinate = (Double) GraphQLScalars.COORDINATE_VALUE_SCALAR + .getCoercing() + .serialize(COORDINATE); + assertEquals(COORDINATE, coordinate, DELTA); + coordinate = + (Double) GraphQLScalars.COORDINATE_VALUE_SCALAR + .getCoercing() + .serialize(COORDINATE.floatValue()); + assertEquals(COORDINATE, coordinate, DELTA); + assertThrows( + CoercingSerializeException.class, + () -> GraphQLScalars.COORDINATE_VALUE_SCALAR.getCoercing().serialize(TEXT) + ); + } + + @Test + void testParseValue() { + var coordinate = (Double) GraphQLScalars.COORDINATE_VALUE_SCALAR + .getCoercing() + .parseValue(COORDINATE); + assertEquals(COORDINATE, coordinate, DELTA); + coordinate = + (Double) GraphQLScalars.COORDINATE_VALUE_SCALAR.getCoercing().parseValue(COORDINATE_MIN); + assertEquals(COORDINATE_MIN, coordinate, DELTA); + coordinate = + (Double) GraphQLScalars.COORDINATE_VALUE_SCALAR.getCoercing().parseValue(COORDINATE_MAX); + assertEquals(COORDINATE_MAX, coordinate, DELTA); + assertThrows( + CoercingParseValueException.class, + () -> GraphQLScalars.COORDINATE_VALUE_SCALAR.getCoercing().parseValue(TEXT) + ); + assertThrows( + CoercingParseValueException.class, + () -> GraphQLScalars.COORDINATE_VALUE_SCALAR.getCoercing().parseValue(TOO_LOW) + ); + assertThrows( + CoercingParseValueException.class, + () -> GraphQLScalars.COORDINATE_VALUE_SCALAR.getCoercing().parseValue(TOO_HIGH) + ); + } + + @Test + void testParseLiteral() { + var coordinateDouble = (Double) GraphQLScalars.COORDINATE_VALUE_SCALAR + .getCoercing() + .parseLiteral(new FloatValue(BigDecimal.valueOf(COORDINATE))); + assertEquals(COORDINATE, coordinateDouble, DELTA); + var coordinateInt = (Double) GraphQLScalars.COORDINATE_VALUE_SCALAR + .getCoercing() + .parseLiteral(new IntValue(BigInteger.valueOf(COORDINATE.intValue()))); + assertEquals(COORDINATE, coordinateInt, DELTA); + assertThrows( + CoercingParseLiteralException.class, + () -> GraphQLScalars.COORDINATE_VALUE_SCALAR.getCoercing().parseLiteral(new StringValue(TEXT)) + ); + assertThrows( + CoercingParseLiteralException.class, + () -> + GraphQLScalars.COORDINATE_VALUE_SCALAR + .getCoercing() + .parseLiteral(new FloatValue(BigDecimal.valueOf(TOO_HIGH))) + ); + assertThrows( + CoercingParseLiteralException.class, + () -> + GraphQLScalars.COORDINATE_VALUE_SCALAR + .getCoercing() + .parseLiteral(new FloatValue(BigDecimal.valueOf(TOO_LOW))) + ); + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/CostScalarTest.java b/src/test/java/org/opentripplanner/apis/gtfs/CostScalarTest.java new file mode 100644 index 00000000000..bb8b5bfe445 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/CostScalarTest.java @@ -0,0 +1,78 @@ +package org.opentripplanner.apis.gtfs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import graphql.language.IntValue; +import graphql.language.StringValue; +import graphql.schema.CoercingParseLiteralException; +import graphql.schema.CoercingParseValueException; +import graphql.schema.CoercingSerializeException; +import java.math.BigInteger; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; + +class CostScalarTest { + + private static final Cost COST_THIRTY = Cost.costOfSeconds(30); + private static final int THIRTY = 30; + private static final int NEGATIVE_THIRTY = -30; + private static final int TOO_HIGH = 300000000; + private static final String TEXT = "foo"; + + @Test + void testSerialize() { + var cost = GraphQLScalars.COST_SCALAR.getCoercing().serialize(COST_THIRTY); + assertEquals(THIRTY, cost); + var costNumber = GraphQLScalars.COST_SCALAR.getCoercing().serialize(THIRTY); + assertEquals(THIRTY, costNumber); + assertThrows( + CoercingSerializeException.class, + () -> GraphQLScalars.COST_SCALAR.getCoercing().serialize(TEXT) + ); + } + + @Test + void testParseValue() { + var cost = GraphQLScalars.COST_SCALAR.getCoercing().parseValue(THIRTY); + assertEquals(COST_THIRTY, cost); + assertThrows( + CoercingParseValueException.class, + () -> GraphQLScalars.COST_SCALAR.getCoercing().parseValue(TEXT) + ); + assertThrows( + CoercingParseValueException.class, + () -> GraphQLScalars.COST_SCALAR.getCoercing().parseValue(NEGATIVE_THIRTY) + ); + assertThrows( + CoercingParseValueException.class, + () -> GraphQLScalars.COST_SCALAR.getCoercing().parseValue(TOO_HIGH) + ); + } + + @Test + void testParseLiteral() { + var cost = GraphQLScalars.COST_SCALAR + .getCoercing() + .parseLiteral(new IntValue(BigInteger.valueOf(THIRTY))); + assertEquals(COST_THIRTY, cost); + assertThrows( + CoercingParseLiteralException.class, + () -> GraphQLScalars.COST_SCALAR.getCoercing().parseLiteral(new StringValue(TEXT)) + ); + assertThrows( + CoercingParseLiteralException.class, + () -> + GraphQLScalars.COST_SCALAR + .getCoercing() + .parseLiteral(new IntValue(BigInteger.valueOf(NEGATIVE_THIRTY))) + ); + assertThrows( + CoercingParseLiteralException.class, + () -> + GraphQLScalars.COST_SCALAR + .getCoercing() + .parseLiteral(new IntValue(BigInteger.valueOf(TOO_HIGH))) + ); + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/RatioScalarTest.java b/src/test/java/org/opentripplanner/apis/gtfs/RatioScalarTest.java new file mode 100644 index 00000000000..5af93dcef1d --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/RatioScalarTest.java @@ -0,0 +1,89 @@ +package org.opentripplanner.apis.gtfs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import graphql.language.FloatValue; +import graphql.language.IntValue; +import graphql.language.StringValue; +import graphql.schema.CoercingParseLiteralException; +import graphql.schema.CoercingParseValueException; +import graphql.schema.CoercingSerializeException; +import java.math.BigDecimal; +import java.math.BigInteger; +import org.junit.jupiter.api.Test; + +class RatioScalarTest { + + private static final Double HALF = 0.5; + private static final double ZERO = 0; + private static final double ONE = 1; + private static final double TOO_HIGH = 1.1; + private static final double TOO_LOW = -1.1; + private static final String TEXT = "foo"; + private static final double DELTA = 0.0001; + + @Test + void testSerialize() { + var ratio = (Double) GraphQLScalars.RATIO_SCALAR.getCoercing().serialize(HALF); + assertEquals(HALF, ratio, DELTA); + ratio = (Double) GraphQLScalars.RATIO_SCALAR.getCoercing().serialize(HALF.floatValue()); + assertEquals(HALF, ratio, DELTA); + assertThrows( + CoercingSerializeException.class, + () -> GraphQLScalars.RATIO_SCALAR.getCoercing().serialize(TEXT) + ); + } + + @Test + void testParseValue() { + var ratio = (Double) GraphQLScalars.RATIO_SCALAR.getCoercing().parseValue(HALF); + assertEquals(HALF, ratio, DELTA); + ratio = (Double) GraphQLScalars.RATIO_SCALAR.getCoercing().parseValue(ZERO); + assertEquals(ZERO, ratio, DELTA); + ratio = (Double) GraphQLScalars.RATIO_SCALAR.getCoercing().parseValue(ONE); + assertEquals(ONE, ratio, DELTA); + assertThrows( + CoercingParseValueException.class, + () -> GraphQLScalars.RATIO_SCALAR.getCoercing().parseValue(TEXT) + ); + assertThrows( + CoercingParseValueException.class, + () -> GraphQLScalars.RATIO_SCALAR.getCoercing().parseValue(TOO_LOW) + ); + assertThrows( + CoercingParseValueException.class, + () -> GraphQLScalars.RATIO_SCALAR.getCoercing().parseValue(TOO_HIGH) + ); + } + + @Test + void testParseLiteral() { + var ratioDouble = (Double) GraphQLScalars.RATIO_SCALAR + .getCoercing() + .parseLiteral(new FloatValue(BigDecimal.valueOf(HALF))); + assertEquals(HALF, ratioDouble, DELTA); + var ratioInt = (Double) GraphQLScalars.RATIO_SCALAR + .getCoercing() + .parseLiteral(new IntValue(BigInteger.valueOf(HALF.intValue()))); + assertEquals(HALF.intValue(), ratioInt, DELTA); + assertThrows( + CoercingParseLiteralException.class, + () -> GraphQLScalars.RATIO_SCALAR.getCoercing().parseLiteral(new StringValue(TEXT)) + ); + assertThrows( + CoercingParseLiteralException.class, + () -> + GraphQLScalars.RATIO_SCALAR + .getCoercing() + .parseLiteral(new FloatValue(BigDecimal.valueOf(TOO_HIGH))) + ); + assertThrows( + CoercingParseLiteralException.class, + () -> + GraphQLScalars.RATIO_SCALAR + .getCoercing() + .parseLiteral(new FloatValue(BigDecimal.valueOf(TOO_LOW))) + ); + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/ReluctanceScalarTest.java b/src/test/java/org/opentripplanner/apis/gtfs/ReluctanceScalarTest.java new file mode 100644 index 00000000000..c5965cdeec6 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/ReluctanceScalarTest.java @@ -0,0 +1,84 @@ +package org.opentripplanner.apis.gtfs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import graphql.language.FloatValue; +import graphql.language.IntValue; +import graphql.language.StringValue; +import graphql.schema.CoercingParseLiteralException; +import graphql.schema.CoercingParseValueException; +import graphql.schema.CoercingSerializeException; +import java.math.BigDecimal; +import java.math.BigInteger; +import org.junit.jupiter.api.Test; + +class ReluctanceScalarTest { + + private static final Double HALF = 0.5; + private static final double TOO_HIGH = 100001; + private static final double TOO_LOW = -0.1; + private static final String TEXT = "foo"; + private static final double DELTA = 0.0001; + + @Test + void testSerialize() { + var reluctance = (Double) GraphQLScalars.RELUCTANCE_SCALAR.getCoercing().serialize(HALF); + assertEquals(HALF, reluctance, DELTA); + reluctance = + (Double) GraphQLScalars.RELUCTANCE_SCALAR.getCoercing().serialize(HALF.floatValue()); + assertEquals(HALF, reluctance, DELTA); + assertThrows( + CoercingSerializeException.class, + () -> GraphQLScalars.RELUCTANCE_SCALAR.getCoercing().serialize(TEXT) + ); + } + + @Test + void testParseValue() { + var reluctance = (Double) GraphQLScalars.RELUCTANCE_SCALAR.getCoercing().parseValue(HALF); + assertEquals(HALF, reluctance, DELTA); + assertThrows( + CoercingParseValueException.class, + () -> GraphQLScalars.RELUCTANCE_SCALAR.getCoercing().parseValue(TEXT) + ); + assertThrows( + CoercingParseValueException.class, + () -> GraphQLScalars.RELUCTANCE_SCALAR.getCoercing().parseValue(TOO_LOW) + ); + assertThrows( + CoercingParseValueException.class, + () -> GraphQLScalars.RELUCTANCE_SCALAR.getCoercing().parseValue(TOO_HIGH) + ); + } + + @Test + void testParseLiteral() { + var reluctanceDouble = (Double) GraphQLScalars.RELUCTANCE_SCALAR + .getCoercing() + .parseLiteral(new FloatValue(BigDecimal.valueOf(HALF))); + assertEquals(HALF, reluctanceDouble, DELTA); + var reluctanceInt = (Double) GraphQLScalars.RELUCTANCE_SCALAR + .getCoercing() + .parseLiteral(new IntValue(BigInteger.valueOf(HALF.intValue()))); + assertEquals(HALF.intValue(), reluctanceInt, DELTA); + assertThrows( + CoercingParseLiteralException.class, + () -> GraphQLScalars.RELUCTANCE_SCALAR.getCoercing().parseLiteral(new StringValue(TEXT)) + ); + assertThrows( + CoercingParseLiteralException.class, + () -> + GraphQLScalars.RELUCTANCE_SCALAR + .getCoercing() + .parseLiteral(new FloatValue(BigDecimal.valueOf(TOO_HIGH))) + ); + assertThrows( + CoercingParseLiteralException.class, + () -> + GraphQLScalars.RELUCTANCE_SCALAR + .getCoercing() + .parseLiteral(new FloatValue(BigDecimal.valueOf(TOO_LOW))) + ); + } +} From 5165c6b83da107bcbffdf570c78ce22aa0378055 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 14 Mar 2024 15:22:18 +0200 Subject: [PATCH 134/165] Remove duplicate OffsetDateTime codegen --- .../apis/gtfs/generated/GraphQLDataFetchers.java | 3 ++- .../opentripplanner/apis/gtfs/generated/graphql-codegen.yml | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java index 39e1919dfeb..6ac26fe51b1 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java @@ -817,7 +817,8 @@ public interface GraphQLQueryType { public DataFetcher viewer(); } - public interface GraphQLRealtimeEstimate { + /** Real-time estimates for a vehicle at a certain place. */ + public interface GraphQLRealTimeEstimate { public DataFetcher delay(); public DataFetcher time(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml index 555331c114b..6fff500d137 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml @@ -26,7 +26,6 @@ config: Polyline: String GeoJson: org.locationtech.jts.geom.Geometry Grams: org.opentripplanner.framework.model.Grams - OffsetDateTime: java.time.OffsetDateTime Duration: java.time.Duration Cost: org.opentripplanner.framework.model.Cost CoordinateValue: Double From bd8e498094943df0f7936508f9b0a39248be2565 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 14 Mar 2024 21:58:14 +0200 Subject: [PATCH 135/165] Remove some defaults that should be injected later on from code/conf --- .../org/opentripplanner/apis/gtfs/schema.graphqls | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 9ef893574ad..1011c98c91b 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4247,7 +4247,7 @@ type QueryType { search for next or previous pages with `after` or `before` cursors, this field is ignored and `first` or `last` should be used instead. """ - numberOfItineraries: Int = 50 + numberOfItineraries: Int """ The origin where the search starts. Usually coordinates but can also be a stop location. @@ -5145,7 +5145,7 @@ input TimetablePreferencesInput { realistic as the transfers could be invalid due to delays, trips can be cancelled or stops can be skipped. """ - excludeRealTimeUpdates: Boolean = false + excludeRealTimeUpdates: Boolean """ When true, departures that have been cancelled ahead of time will be @@ -5154,7 +5154,7 @@ input TimetablePreferencesInput { could be filtered out as the alternative containing a cancellation would normally be better. """ - includePlannedCancellations: Boolean = false + includePlannedCancellations: Boolean """ When true, departures that have been cancelled through a real-time feed will be @@ -5163,7 +5163,7 @@ input TimetablePreferencesInput { could be filtered out as the alternative containing a cancellation would normally be better. This option can't be set to true while `includeRealTimeUpdates` is false. """ - includeRealTimeCancellations: Boolean = false + includeRealTimeCancellations: Boolean } """ From f969bd9071331a481906480e22167405865d505d Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 14 Mar 2024 23:36:06 +0200 Subject: [PATCH 136/165] Refactor scalars and make min reluctance 0.1 --- .../apis/gtfs/GraphQLScalars.java | 95 ++++++++----------- .../apis/gtfs/ReluctanceScalarTest.java | 7 +- 2 files changed, 42 insertions(+), 60 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index a45acc372d3..29d3e2c5575 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -121,6 +121,8 @@ public OffsetDateTime parseLiteral(Object input) throws CoercingParseLiteralExce .name("CoordinateValue") .coercing( new Coercing() { + private static final String VALIDATION_ERROR_MESSAGE = "Not a valid WGS84 coordinate value"; + @Override public Double serialize(@Nonnull Object dataFetcherResult) throws CoercingSerializeException { @@ -141,9 +143,7 @@ public Double serialize(@Nonnull Object dataFetcherResult) public Double parseValue(Object input) throws CoercingParseValueException { if (input instanceof Double doubleValue) { return validateCoordinate(doubleValue) - .orElseThrow(() -> - new CoercingParseValueException("Not a valid WGS84 coordinate value") - ); + .orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE)); } throw new CoercingParseValueException( "Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input) @@ -152,16 +152,13 @@ public Double parseValue(Object input) throws CoercingParseValueException { @Override public Double parseLiteral(Object input) throws CoercingParseLiteralException { - var validationException = new CoercingParseLiteralException( - "Not a valid WGS84 coordinate value" - ); if (input instanceof FloatValue coordinate) { return validateCoordinate(coordinate.getValue().doubleValue()) - .orElseThrow(() -> validationException); + .orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE)); } if (input instanceof IntValue coordinate) { return validateCoordinate(coordinate.getValue().doubleValue()) - .orElseThrow(() -> validationException); + .orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE)); } throw new CoercingParseLiteralException( "Expected a number, got: " + input.getClass().getSimpleName() @@ -184,6 +181,8 @@ private static Optional validateCoordinate(double coordinate) { .coercing( new Coercing() { private static final int MAX_COST = 1000000; + private static final String VALIDATION_ERROR_MESSAGE = + "Cost cannot be negative or greater than %d".formatted(MAX_COST); @Override public Integer serialize(@Nonnull Object dataFetcherResult) @@ -204,15 +203,8 @@ public Integer serialize(@Nonnull Object dataFetcherResult) @Override public Cost parseValue(Object input) throws CoercingParseValueException { if (input instanceof Integer intValue) { - if (intValue < 0) { - throw new CoercingParseValueException("Cost cannot be negative"); - } - if (intValue > MAX_COST) { - throw new CoercingParseValueException( - "Cost cannot be greater than %d".formatted(MAX_COST) - ); - } - return Cost.costOfSeconds(intValue); + return validateCost(intValue) + .orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE)); } throw new CoercingParseValueException( "Expected an integer, got %s %s".formatted(input.getClass().getSimpleName(), input) @@ -223,20 +215,20 @@ public Cost parseValue(Object input) throws CoercingParseValueException { public Cost parseLiteral(Object input) throws CoercingParseLiteralException { if (input instanceof IntValue intValue) { var value = intValue.getValue().intValue(); - if (value < 0) { - throw new CoercingParseLiteralException("Cost cannot be negative"); - } - if (value > MAX_COST) { - throw new CoercingParseLiteralException( - "Cost cannot be greater than %d".formatted(MAX_COST) - ); - } - return Cost.costOfSeconds(value); + return validateCost(value) + .orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE)); } throw new CoercingParseLiteralException( "Expected an integer, got: " + input.getClass().getSimpleName() ); } + + private static Optional validateCost(int cost) { + if (cost >= 0 && cost <= MAX_COST) { + return Optional.of(Cost.costOfSeconds(cost)); + } + return Optional.empty(); + } } ) .build(); @@ -349,12 +341,13 @@ public Grams parseLiteral(Object input) throws CoercingParseLiteralException { .name("Ratio") .coercing( new Coercing() { + private static final String VALIDATION_ERROR_MESSAGE = + "Value is under 0 or greater than 1."; + @Override public Double serialize(@Nonnull Object dataFetcherResult) throws CoercingSerializeException { - var validationException = new CoercingSerializeException( - "Value is less than 0 or greater than 1." - ); + var validationException = new CoercingSerializeException(VALIDATION_ERROR_MESSAGE); if (dataFetcherResult instanceof Double doubleValue) { return validateRatio(doubleValue).orElseThrow(() -> validationException); } else if (dataFetcherResult instanceof Float floatValue) { @@ -372,9 +365,7 @@ public Double serialize(@Nonnull Object dataFetcherResult) public Double parseValue(Object input) throws CoercingParseValueException { if (input instanceof Double doubleValue) { return validateRatio(doubleValue) - .orElseThrow(() -> - new CoercingParseValueException("Value is less than 0 or greater than 1.") - ); + .orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE)); } throw new CoercingParseValueException( "Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input) @@ -383,16 +374,13 @@ public Double parseValue(Object input) throws CoercingParseValueException { @Override public Double parseLiteral(Object input) throws CoercingParseLiteralException { - var validationException = new CoercingParseLiteralException( - "Value is under 0 or greater than 1." - ); if (input instanceof FloatValue ratio) { return validateRatio(ratio.getValue().doubleValue()) - .orElseThrow(() -> validationException); + .orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE)); } if (input instanceof IntValue ratio) { return validateRatio(ratio.getValue().doubleValue()) - .orElseThrow(() -> validationException); + .orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE)); } throw new CoercingParseLiteralException( "Expected a number, got: " + input.getClass().getSimpleName() @@ -414,7 +402,10 @@ private static Optional validateRatio(double ratio) { .name("Reluctance") .coercing( new Coercing() { + private static final double MIN_Reluctance = 0.1; private static final double MAX_Reluctance = 100000; + private static final String VALIDATION_ERROR_MESSAGE = + "Reluctance needs to be between %s and %s".formatted(MIN_Reluctance, MAX_Reluctance); @Override public Double serialize(@Nonnull Object dataFetcherResult) @@ -435,15 +426,8 @@ public Double serialize(@Nonnull Object dataFetcherResult) @Override public Double parseValue(Object input) throws CoercingParseValueException { if (input instanceof Double doubleValue) { - if (Double.doubleToRawLongBits(doubleValue) < 0) { - throw new CoercingParseValueException("Reluctance cannot be negative"); - } - if (doubleValue > MAX_Reluctance + 0.001) { - throw new CoercingParseValueException( - "Reluctance cannot be greater than %s".formatted(MAX_Reluctance) - ); - } - return doubleValue; + return validateReluctance(doubleValue) + .orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE)); } throw new CoercingParseValueException( "Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input) @@ -453,26 +437,23 @@ public Double parseValue(Object input) throws CoercingParseValueException { @Override public Double parseLiteral(Object input) throws CoercingParseLiteralException { if (input instanceof FloatValue reluctance) { - return validateLiteral(reluctance.getValue().doubleValue()); + return validateReluctance(reluctance.getValue().doubleValue()) + .orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE)); } if (input instanceof IntValue reluctance) { - return validateLiteral(reluctance.getValue().doubleValue()); + return validateReluctance(reluctance.getValue().doubleValue()) + .orElseThrow(() -> new CoercingParseLiteralException(VALIDATION_ERROR_MESSAGE)); } throw new CoercingParseLiteralException( "Expected a number, got: " + input.getClass().getSimpleName() ); } - private static double validateLiteral(double reluctance) { - if (Double.doubleToRawLongBits(reluctance) < 0) { - throw new CoercingParseLiteralException("Reluctance cannot be negative"); + private static Optional validateReluctance(double reluctance) { + if (reluctance >= MIN_Reluctance - 0.001 && reluctance <= MAX_Reluctance + 0.001) { + return Optional.of(reluctance); } - if (reluctance > MAX_Reluctance + 0.001) { - throw new CoercingParseLiteralException( - "Reluctance cannot be greater than %s".formatted(MAX_Reluctance) - ); - } - return reluctance; + return Optional.empty(); } } ) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/ReluctanceScalarTest.java b/src/test/java/org/opentripplanner/apis/gtfs/ReluctanceScalarTest.java index c5965cdeec6..6a1bb7124a9 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/ReluctanceScalarTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/ReluctanceScalarTest.java @@ -16,8 +16,9 @@ class ReluctanceScalarTest { private static final Double HALF = 0.5; + private static final int ONE = 1; private static final double TOO_HIGH = 100001; - private static final double TOO_LOW = -0.1; + private static final double TOO_LOW = 0; private static final String TEXT = "foo"; private static final double DELTA = 0.0001; @@ -60,8 +61,8 @@ void testParseLiteral() { assertEquals(HALF, reluctanceDouble, DELTA); var reluctanceInt = (Double) GraphQLScalars.RELUCTANCE_SCALAR .getCoercing() - .parseLiteral(new IntValue(BigInteger.valueOf(HALF.intValue()))); - assertEquals(HALF.intValue(), reluctanceInt, DELTA); + .parseLiteral(new IntValue(BigInteger.valueOf(ONE))); + assertEquals(ONE, reluctanceInt, DELTA); assertThrows( CoercingParseLiteralException.class, () -> GraphQLScalars.RELUCTANCE_SCALAR.getCoercing().parseLiteral(new StringValue(TEXT)) From bde1bd126ea1209bb37a41cc86bd9e16466570f1 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 15 Mar 2024 12:16:28 +0200 Subject: [PATCH 137/165] Add integration test --- .../gtfs/expectations/planConnection.json | 38 ++++ .../apis/gtfs/queries/planConnection.graphql | 163 ++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 src/test/resources/org/opentripplanner/apis/gtfs/expectations/planConnection.json create mode 100644 src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/expectations/planConnection.json b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/planConnection.json new file mode 100644 index 00000000000..53ab016cc93 --- /dev/null +++ b/src/test/resources/org/opentripplanner/apis/gtfs/expectations/planConnection.json @@ -0,0 +1,38 @@ +{ + "data" : { + "planConnection" : { + "searchDateTime" : "2023-01-27T21:08:35+01:00", + "routingErrors" : [ ], + "pageInfo" : { + "hasNextPage" : false, + "hasPreviousPage" : false, + "startCursor" : null, + "endCursor" : null, + "searchWindowUsed" : null + }, + "edges" : [ + { + "cursor" : "NoCursor", + "node" : { + "start" : "2020-02-02T11:00:00Z", + "end" : "2020-02-02T12:00:00Z", + "legs" : [ + { + "mode" : "WALK" + }, + { + "mode" : "BUS" + }, + { + "mode" : "RAIL" + }, + { + "mode" : "CAR" + } + ] + } + } + ] + } + } +} diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql new file mode 100644 index 00000000000..39691b349f8 --- /dev/null +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql @@ -0,0 +1,163 @@ +{ + planConnection( + dateTime: { + earliestDeparture: "2023-06-13T14:30+03:00" + } + searchWindow: "PT2H30M" + numberOfItineraries: 5 + origin: { + location: { + coordinate: { + latitude: 45.5552 + longitude: -122.6534 + } + } + label: "Home" + } + destination: { + location: { + coordinate: { + latitude: 45.4908 + longitude: -122.5519 + } + } + label: "Work" + } + modes: { + directOnly: false + transitOnly: false + direct: [WALK] + transit: { + access: [BICYCLE_RENTAL] + transfer: [WALK] + egress: [BICYCLE_RENTAL] + transit: [ + { + mode: TRAM + cost: { + reluctance: 1.3 + } + }, + { + mode: BUS + } + ] + } + } + preferences: { + accessibility: { + wheelchair: { + enabled: true + } + } + street: { + car: { + reluctance: 6.5 + rental: { + allowedNetworks: ["foo", "bar"] + bannedNetworks: ["foobar"] + } + parking: { + unpreferredCost: 200 + preferred: [{ + select: [{ + tags: ["best-park"] + }] + }] + filters: [{ + not: [{ + tags: ["worst-park"] + }] + }] + } + } + bicycle: { + reluctance: 3.0 + speed: 7.4 + optimization: { + type: SAFEST_STREETS + } + boardCost: 200 + walk: { + speed: 1.3 + cost: { + mountDismountCost: 100 + reluctance: 3.5 + } + mountDismountTime: "PT5S" + } + rental: { + destinationBicyclePolicy: { + allowKeeping: true + keepingCost: 300 + } + allowedNetworks: ["foo", "bar"] + bannedNetworks: ["foobar"] + } + parking: { + unpreferredCost: 200 + preferred: [{ + select: [{ + tags: ["best-park"] + }] + }] + filters: [{ + not: [{ + tags: ["worst-park"] + }] + }] + } + } + walk: { + speed: 2.4 + reluctance: 1.5 + walkSafetyFactor: 0.5 + boardCost: 200 + } + } + transit: { + board: { + waitReluctance: 3.2 + slack: "PT1M30S" + } + alight: { + slack: "PT0S" + } + transfer: { + cost: 200 + slack: "PT2M" + maximumAdditionalTransfers: 2 + maximumTransfers: 5 + } + timetable: { + excludeRealTimeUpdates: false + includePlannedCancellations: false + includeRealTimeCancellations: true + } + } + } + locale: "en" + ) { + searchDateTime + routingErrors { + code + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + searchWindowUsed + } + edges { + cursor + node { + start + end + legs { + mode + } + } + } + } +} \ No newline at end of file From 02323394c323547a49133036f01dec233bfe11a1 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 15 Mar 2024 17:09:49 +0200 Subject: [PATCH 138/165] Add some tests for RouteRequestMapper --- .../gtfs/mapping/RouteRequestMapperTest.java | 360 ++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java new file mode 100644 index 00000000000..fb40c9e7d7f --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java @@ -0,0 +1,360 @@ +package org.opentripplanner.apis.gtfs.mapping; + +import static graphql.Assert.assertTrue; +import static graphql.execution.ExecutionContextBuilder.newExecutionContextBuilder; +import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; + +import graphql.ExecutionInput; +import graphql.execution.ExecutionId; +import graphql.schema.DataFetchingEnvironment; +import graphql.schema.DataFetchingEnvironmentImpl; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.locationtech.jts.geom.Coordinate; +import org.opentripplanner._support.time.ZoneIds; +import org.opentripplanner.apis.gtfs.GraphQLRequestContext; +import org.opentripplanner.apis.gtfs.TestRoutingService; +import org.opentripplanner.ext.fares.impl.DefaultFareService; +import org.opentripplanner.model.plan.paging.cursor.PageCursor; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.graphfinder.GraphFinder; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; +import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; +import org.opentripplanner.transit.model.basic.TransitMode; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.TransitModel; + +class RouteRequestMapperTest { + + private static final Coordinate ORIGIN = new Coordinate(1.0, 2.0); + private static final Coordinate DESTINATION = new Coordinate(2.0, 1.0); + private static final Locale LOCALE = Locale.GERMAN; + private static final GraphQLRequestContext context; + private static final Map args = new HashMap<>(); + + static { + args.put( + "origin", + Map.ofEntries( + entry("location", Map.of("coordinate", Map.of("latitude", ORIGIN.x, "longitude", ORIGIN.y))) + ) + ); + args.put( + "destination", + Map.ofEntries( + entry( + "location", + Map.of("coordinate", Map.of("latitude", DESTINATION.x, "longitude", DESTINATION.y)) + ) + ) + ); + + Graph graph = new Graph(); + var transitModel = new TransitModel(); + transitModel.initTimeZone(ZoneIds.BERLIN); + final DefaultTransitService transitService = new DefaultTransitService(transitModel); + context = + new GraphQLRequestContext( + new TestRoutingService(List.of()), + transitService, + new DefaultFareService(), + graph.getVehicleParkingService(), + new DefaultVehicleRentalService(), + new DefaultRealtimeVehicleService(transitService), + GraphFinder.getInstance(graph, transitService::findRegularStops), + new RouteRequest() + ); + } + + @Test + void testMinimalArgs() { + var env = executionContext(args); + var defaultRequest = new RouteRequest(); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + assertEquals(ORIGIN.x, routeRequest.from().lat); + assertEquals(ORIGIN.y, routeRequest.from().lng); + assertEquals(DESTINATION.x, routeRequest.to().lat); + assertEquals(DESTINATION.y, routeRequest.to().lng); + assertEquals(LOCALE, routeRequest.locale()); + assertEquals(defaultRequest.preferences(), routeRequest.preferences()); + assertEquals(defaultRequest.wheelchair(), routeRequest.wheelchair()); + assertEquals(defaultRequest.arriveBy(), routeRequest.arriveBy()); + assertEquals(defaultRequest.isTripPlannedForNow(), routeRequest.isTripPlannedForNow()); + assertEquals(defaultRequest.numItineraries(), routeRequest.numItineraries()); + assertEquals(defaultRequest.searchWindow(), routeRequest.searchWindow()); + assertEquals(defaultRequest.journey().modes(), routeRequest.journey().modes()); + assertTrue(defaultRequest.journey().transit().filters().size() == 1); + assertTrue(routeRequest.journey().transit().filters().size() == 1); + assertTrue(routeRequest.journey().transit().enabled()); + assertEquals( + defaultRequest.journey().transit().filters().getFirst(), + routeRequest.journey().transit().filters().getFirst() + ); + assertTrue( + Duration + .between(defaultRequest.dateTime(), routeRequest.dateTime()) + .compareTo(Duration.ofSeconds(10)) < + 0 + ); + } + + @Test + void testEarliestDeparture() { + var dateTimeArgs = createArgsCopy(); + var dateTime = OffsetDateTime.of(LocalDate.of(2020, 3, 15), LocalTime.MIDNIGHT, ZoneOffset.UTC); + dateTimeArgs.put("dateTime", Map.ofEntries(entry("earliestDeparture", dateTime))); + var env = executionContext(dateTimeArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + assertEquals(dateTime.toInstant(), routeRequest.dateTime()); + assertFalse(routeRequest.arriveBy()); + assertFalse(routeRequest.isTripPlannedForNow()); + } + + @Test + void testLatestArrival() { + var dateTimeArgs = createArgsCopy(); + var dateTime = OffsetDateTime.of(LocalDate.of(2020, 3, 15), LocalTime.MIDNIGHT, ZoneOffset.UTC); + dateTimeArgs.put("dateTime", Map.ofEntries(entry("latestArrival", dateTime))); + var env = executionContext(dateTimeArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + assertEquals(dateTime.toInstant(), routeRequest.dateTime()); + assertTrue(routeRequest.arriveBy()); + assertFalse(routeRequest.isTripPlannedForNow()); + } + + @Test + void testStopLocationAndLabel() { + Map stopLocationArgs = new HashMap<>(); + var stopA = "foo:1"; + var stopB = "foo:2"; + var originLabel = "start"; + var destinationLabel = "end"; + stopLocationArgs.put( + "origin", + Map.ofEntries( + entry("location", Map.of("stopLocation", Map.of("stopLocationId", stopA))), + entry("label", originLabel) + ) + ); + stopLocationArgs.put( + "destination", + Map.ofEntries( + entry("location", Map.of("stopLocation", Map.of("stopLocationId", stopB))), + entry("label", destinationLabel) + ) + ); + var env = executionContext(stopLocationArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + assertEquals(FeedScopedId.parse(stopA), routeRequest.from().stopId); + assertEquals(originLabel, routeRequest.from().label); + assertEquals(FeedScopedId.parse(stopB), routeRequest.to().stopId); + assertEquals(destinationLabel, routeRequest.to().label); + } + + @Test + void testLocale() { + var englishLocale = Locale.ENGLISH; + var localeArgs = createArgsCopy(); + localeArgs.put("locale", englishLocale); + var env = executionContext(localeArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + assertEquals(englishLocale, routeRequest.locale()); + } + + @Test + void testSearchWindow() { + var searchWindow = Duration.ofHours(5); + var searchWindowArgs = createArgsCopy(); + searchWindowArgs.put("searchWindow", searchWindow); + var env = executionContext(searchWindowArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + assertEquals(searchWindow, routeRequest.searchWindow()); + } + + @Test + void testBefore() { + var before = PageCursor.decode( + "MXxQUkVWSU9VU19QQUdFfDIwMjQtMDMtMTVUMTM6MzU6MzlafHw0MG18U1RSRUVUX0FORF9BUlJJVkFMX1RJTUV8ZmFsc2V8MjAyNC0wMy0xNVQxNDoyODoxNFp8MjAyNC0wMy0xNVQxNToxNDoyMlp8MXw0MjUzfA==" + ); + var last = 8; + var beforeArgs = createArgsCopy(); + beforeArgs.put("before", before.encode()); + beforeArgs.put("first", 3); + beforeArgs.put("last", last); + beforeArgs.put("numberOfItineraries", 3); + var env = executionContext(beforeArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + assertEquals(before, routeRequest.pageCursor()); + assertEquals(last, routeRequest.numItineraries()); + } + + @Test + void testAfter() { + var after = PageCursor.decode( + "MXxORVhUX1BBR0V8MjAyNC0wMy0xNVQxNDo0MzoxNFp8fDQwbXxTVFJFRVRfQU5EX0FSUklWQUxfVElNRXxmYWxzZXwyMDI0LTAzLTE1VDE0OjI4OjE0WnwyMDI0LTAzLTE1VDE1OjE0OjIyWnwxfDQyNTN8" + ); + var first = 8; + var afterArgs = createArgsCopy(); + afterArgs.put("after", after.encode()); + afterArgs.put("first", first); + afterArgs.put("last", 3); + afterArgs.put("numberOfItineraries", 3); + var env = executionContext(afterArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + assertEquals(after, routeRequest.pageCursor()); + assertEquals(first, routeRequest.numItineraries()); + } + + @Test + void testNumberOfItineraries() { + var itineraries = 8; + var itinArgs = createArgsCopy(); + itinArgs.put("numberOfItineraries", itineraries); + var env = executionContext(itinArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + assertEquals(itineraries, routeRequest.numItineraries()); + } + + @Test + void testDirectOnly() { + var modesArgs = createArgsCopy(); + modesArgs.put("modes", Map.ofEntries(entry("directOnly", true))); + var env = executionContext(modesArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + assertFalse(routeRequest.journey().transit().enabled()); + } + + @Test + void testTransitOnly() { + var modesArgs = createArgsCopy(); + modesArgs.put("modes", Map.ofEntries(entry("transitOnly", true))); + var env = executionContext(modesArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + assertEquals(StreetMode.NOT_SET, routeRequest.journey().direct().mode()); + } + + @Test + void testStreetModes() { + var modesArgs = createArgsCopy(); + var bicycle = List.of("BICYCLE"); + modesArgs.put( + "modes", + Map.ofEntries( + entry("direct", List.of("CAR")), + entry( + "transit", + Map.ofEntries( + entry("access", bicycle), + entry("egress", bicycle), + entry("transfer", bicycle) + ) + ) + ) + ); + var env = executionContext(modesArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + assertEquals(StreetMode.CAR, routeRequest.journey().direct().mode()); + assertEquals(StreetMode.BIKE, routeRequest.journey().access().mode()); + assertEquals(StreetMode.BIKE, routeRequest.journey().egress().mode()); + assertEquals(StreetMode.BIKE, routeRequest.journey().transfer().mode()); + } + + @Test + void testTransitModes() { + var modesArgs = createArgsCopy(); + var tramCost = 1.5; + modesArgs.put( + "modes", + Map.ofEntries( + entry( + "transit", + Map.ofEntries( + entry( + "transit", + List.of( + Map.ofEntries( + entry("mode", "TRAM"), + entry("cost", Map.ofEntries(entry("reluctance", tramCost))) + ), + Map.ofEntries(entry("mode", "FERRY")) + ) + ) + ) + ) + ) + ); + var env = executionContext(modesArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var reluctanceForMode = routeRequest.preferences().transit().reluctanceForMode(); + assertEquals(tramCost, reluctanceForMode.get(TransitMode.TRAM)); + assertNull(reluctanceForMode.get(TransitMode.FERRY)); + assertEquals(1, routeRequest.journey().transit().filters().size()); + // TODO somehow test which modes are enabled + } + + @Test + void testItineraryFilters() { + var filterArgs = createArgsCopy(); + var profile = ItineraryFilterDebugProfile.LIMIT_TO_NUM_OF_ITINERARIES; + var keepOne = 0.4; + var keepThree = 0.6; + var multiplier = 3.5; + filterArgs.put( + "itineraryFilter", + Map.ofEntries( + entry("itineraryFilterDebugProfile", "LIMIT_TO_NUMBER_OF_ITINERARIES"), + entry("groupSimilarityKeepOne", keepOne), + entry("groupSimilarityKeepThree", keepThree), + entry("groupedOtherThanSameLegsMaxCostMultiplier", multiplier) + ) + ); + var env = executionContext(filterArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var itinFilter = routeRequest.preferences().itineraryFilter(); + assertEquals(profile, itinFilter.debug()); + assertEquals(keepOne, itinFilter.groupSimilarityKeepOne()); + assertEquals(keepThree, itinFilter.groupSimilarityKeepThree()); + assertEquals(multiplier, itinFilter.groupedOtherThanSameLegsMaxCostMultiplier()); + } + + private Map createArgsCopy() { + Map newArgs = new HashMap<>(); + newArgs.putAll(args); + return newArgs; + } + + private DataFetchingEnvironment executionContext(Map arguments) { + ExecutionInput executionInput = ExecutionInput + .newExecutionInput() + .query("") + .operationName("planConnection") + .context(context) + .locale(LOCALE) + .build(); + + var executionContext = newExecutionContextBuilder() + .executionInput(executionInput) + .executionId(ExecutionId.from(this.getClass().getName())) + .build(); + return DataFetchingEnvironmentImpl + .newDataFetchingEnvironment(executionContext) + .arguments(arguments) + .localContext(Map.of("locale", LOCALE)) + .build(); + } +} From 6f8d31b3ebee3234dfdceb97357866c76e179a8b Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 19 Mar 2024 23:54:46 +0200 Subject: [PATCH 139/165] Add test for transit modes --- .../apis/gtfs/mapping/RouteRequestMapperTest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java index fb40c9e7d7f..ae2e3c57105 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java @@ -102,8 +102,8 @@ void testMinimalArgs() { assertTrue(routeRequest.journey().transit().filters().size() == 1); assertTrue(routeRequest.journey().transit().enabled()); assertEquals( - defaultRequest.journey().transit().filters().getFirst(), - routeRequest.journey().transit().filters().getFirst() + defaultRequest.journey().transit().filters().toString(), + routeRequest.journey().transit().filters().toString() ); assertTrue( Duration @@ -303,8 +303,10 @@ void testTransitModes() { var reluctanceForMode = routeRequest.preferences().transit().reluctanceForMode(); assertEquals(tramCost, reluctanceForMode.get(TransitMode.TRAM)); assertNull(reluctanceForMode.get(TransitMode.FERRY)); - assertEquals(1, routeRequest.journey().transit().filters().size()); - // TODO somehow test which modes are enabled + assertEquals( + "[TransitFilterRequest{select: [SelectRequest{transportModes: [FERRY, TRAM]}]}]", + routeRequest.journey().transit().filters().toString() + ); } @Test From 678e2389e5bcc27ece62d1100d49d00a97eea969 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 21 Mar 2024 16:12:53 +0200 Subject: [PATCH 140/165] Fix allowedNetworks --- .../opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java index de167ff281c..3361e8a4410 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java @@ -395,7 +395,7 @@ private static void setBicycleRentalPreferences( if (args != null) { var allowedNetworks = args.getGraphQLAllowedNetworks(); if (!CollectionUtils.isEmpty(allowedNetworks)) { - preferences.withBannedNetworks(Set.copyOf(allowedNetworks)); + preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); } var bannedNetworks = args.getGraphQLBannedNetworks(); if (!CollectionUtils.isEmpty(bannedNetworks)) { From 36809bee6a8a3590c80ec662e6558c1a291fe51a Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 21 Mar 2024 16:13:40 +0200 Subject: [PATCH 141/165] Add tests for bike preferences --- .../gtfs/mapping/RouteRequestMapperTest.java | 253 ++++++++++++++++++ 1 file changed, 253 insertions(+) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java index ae2e3c57105..9704d6ca122 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java @@ -20,16 +20,19 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Coordinate; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.TestRoutingService; import org.opentripplanner.ext.fares.impl.DefaultFareService; +import org.opentripplanner.framework.model.Cost; import org.opentripplanner.model.plan.paging.cursor.PageCursor; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile; +import org.opentripplanner.routing.core.VehicleRoutingOptimizeType; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graphfinder.GraphFinder; import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; @@ -334,6 +337,256 @@ void testItineraryFilters() { assertEquals(multiplier, itinFilter.groupedOtherThanSameLegsMaxCostMultiplier()); } + @Test + void testBasicBikePreferences() { + var bicycleArgs = createArgsCopy(); + var reluctance = 7.5; + var speed = 15d; + var boardCost = Cost.costOfSeconds(50); + bicycleArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries( + entry("reluctance", reluctance), + entry("speed", speed), + entry("boardCost", boardCost) + ) + ) + ) + ) + ) + ); + var env = executionContext(bicycleArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var bikePreferences = routeRequest.preferences().bike(); + assertEquals(reluctance, bikePreferences.reluctance()); + assertEquals(speed, bikePreferences.speed()); + assertEquals(boardCost.toSeconds(), bikePreferences.boardCost()); + } + + @Test + void testBikeWalkPreferences() { + var bicycleArgs = createArgsCopy(); + var walkSpeed = 7d; + var mountDismountTime = Duration.ofSeconds(23); + var mountDismountCost = Cost.costOfSeconds(35); + var walkReluctance = 6.3; + bicycleArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries( + entry( + "walk", + Map.ofEntries( + entry("speed", walkSpeed), + entry("mountDismountTime", mountDismountTime), + entry( + "cost", + Map.ofEntries( + entry("mountDismountCost", mountDismountCost), + entry("reluctance", walkReluctance) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ); + var env = executionContext(bicycleArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var bikeWalkingPreferences = routeRequest.preferences().bike().walking(); + assertEquals(walkSpeed, bikeWalkingPreferences.speed()); + assertEquals(mountDismountTime, bikeWalkingPreferences.mountDismountTime()); + assertEquals(mountDismountCost, bikeWalkingPreferences.mountDismountCost()); + assertEquals(walkReluctance, bikeWalkingPreferences.reluctance()); + } + + @Test + void testBikeTrianglePreferences() { + var bicycleArgs = createArgsCopy(); + var bikeSafety = 0.3; + var bikeFlatness = 0.5; + var bikeTime = 0.2; + bicycleArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries( + entry( + "optimization", + Map.ofEntries( + entry( + "triangle", + Map.ofEntries( + entry("safety", bikeSafety), + entry("flatness", bikeFlatness), + entry("time", bikeTime) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ); + var env = executionContext(bicycleArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var bikePreferences = routeRequest.preferences().bike(); + assertEquals(VehicleRoutingOptimizeType.TRIANGLE, bikePreferences.optimizeType()); + var bikeTrianglePreferences = bikePreferences.optimizeTriangle(); + assertEquals(bikeSafety, bikeTrianglePreferences.safety()); + assertEquals(bikeFlatness, bikeTrianglePreferences.slope()); + assertEquals(bikeTime, bikeTrianglePreferences.time()); + } + + @Test + void testBikeOptimizationPreferences() { + var bicycleArgs = createArgsCopy(); + bicycleArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries(entry("optimization", Map.ofEntries(entry("type", "SAFEST_STREETS")))) + ) + ) + ) + ) + ); + var env = executionContext(bicycleArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var bikePreferences = routeRequest.preferences().bike(); + assertEquals(VehicleRoutingOptimizeType.SAFEST_STREETS, bikePreferences.optimizeType()); + } + + @Test + void testBikeRentalPreferences() { + var bicycleArgs = createArgsCopy(); + var allowed = Set.of("foo", "bar"); + var banned = Set.of("not"); + var allowKeeping = true; + var keepingCost = Cost.costOfSeconds(150); + bicycleArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries( + entry( + "rental", + Map.ofEntries( + entry("allowedNetworks", allowed.stream().toList()), + entry("bannedNetworks", banned.stream().toList()), + entry( + "destinationBicyclePolicy", + Map.ofEntries( + entry("allowKeeping", allowKeeping), + entry("keepingCost", keepingCost) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ); + var env = executionContext(bicycleArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var bikeRentalPreferences = routeRequest.preferences().bike().rental(); + assertEquals(allowed, bikeRentalPreferences.allowedNetworks()); + assertEquals(banned, bikeRentalPreferences.bannedNetworks()); + assertEquals(allowKeeping, bikeRentalPreferences.allowArrivingInRentedVehicleAtDestination()); + assertEquals(keepingCost, bikeRentalPreferences.arrivingInRentalVehicleAtDestinationCost()); + } + + @Test + void testBikeParkingPreferences() { + var bicycleArgs = createArgsCopy(); + var unpreferredCost = Cost.costOfSeconds(150); + var notFilter = List.of("wheelbender"); + var selectFilter = List.of("locker", "roof"); + var unpreferred = List.of("bad"); + var preferred = List.of("a", "b"); + bicycleArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries( + entry( + "parking", + Map.ofEntries( + entry("unpreferredCost", unpreferredCost), + entry( + "filters", + List.of( + Map.ofEntries( + entry("not", List.of(Map.of("tags", notFilter))), + entry("select", List.of(Map.of("tags", selectFilter))) + ) + ) + ), + entry( + "preferred", + List.of( + Map.ofEntries( + entry("not", List.of(Map.of("tags", unpreferred))), + entry("select", List.of(Map.of("tags", preferred))) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ); + var env = executionContext(bicycleArgs); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var bikeParkingPreferences = routeRequest.preferences().bike().parking(); + assertEquals(unpreferredCost, bikeParkingPreferences.unpreferredVehicleParkingTagCost()); + assertEquals( + "VehicleParkingFilter{not: [tags=%s], select: [tags=%s]}".formatted(notFilter, selectFilter), + bikeParkingPreferences.filter().toString() + ); + assertEquals( + "VehicleParkingFilter{not: [tags=%s], select: [tags=%s]}".formatted(unpreferred, preferred), + bikeParkingPreferences.preferred().toString() + ); + } + private Map createArgsCopy() { Map newArgs = new HashMap<>(); newArgs.putAll(args); From d52c5c42a7c6acf258d7241c3a15e56766a942c9 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Mar 2024 14:32:45 +0200 Subject: [PATCH 142/165] Create own package for route request mapping --- .../apis/gtfs/datafetchers/QueryTypeImpl.java | 4 ++-- .../gtfs/mapping/{ => routerequest}/AccessModeMapper.java | 2 +- .../gtfs/mapping/{ => routerequest}/DirectModeMapper.java | 2 +- .../gtfs/mapping/{ => routerequest}/EgressModeMapper.java | 2 +- .../ItineraryFilterDebugProfileMapper.java | 2 +- .../{ => routerequest}/LegacyRouteRequestMapper.java | 6 +++--- .../mapping/{ => routerequest}/OptimizationTypeMapper.java | 2 +- .../gtfs/mapping/{ => routerequest}/RouteRequestMapper.java | 3 ++- .../gtfs/mapping/{ => routerequest}/TransferModeMapper.java | 2 +- .../{ => routerequest}/VehicleOptimizationTypeMapper.java | 2 +- .../{ => routerequest}/LegacyRouteRequestMapperTest.java | 2 +- .../mapping/{ => routerequest}/RouteRequestMapperTest.java | 2 +- 12 files changed, 16 insertions(+), 15 deletions(-) rename src/main/java/org/opentripplanner/apis/gtfs/mapping/{ => routerequest}/AccessModeMapper.java (92%) rename src/main/java/org/opentripplanner/apis/gtfs/mapping/{ => routerequest}/DirectModeMapper.java (92%) rename src/main/java/org/opentripplanner/apis/gtfs/mapping/{ => routerequest}/EgressModeMapper.java (91%) rename src/main/java/org/opentripplanner/apis/gtfs/mapping/{ => routerequest}/ItineraryFilterDebugProfileMapper.java (92%) rename src/main/java/org/opentripplanner/apis/gtfs/mapping/{ => routerequest}/LegacyRouteRequestMapper.java (98%) rename src/main/java/org/opentripplanner/apis/gtfs/mapping/{ => routerequest}/OptimizationTypeMapper.java (91%) rename src/main/java/org/opentripplanner/apis/gtfs/mapping/{ => routerequest}/RouteRequestMapper.java (99%) rename src/main/java/org/opentripplanner/apis/gtfs/mapping/{ => routerequest}/TransferModeMapper.java (87%) rename src/main/java/org/opentripplanner/apis/gtfs/mapping/{ => routerequest}/VehicleOptimizationTypeMapper.java (94%) rename src/test/java/org/opentripplanner/apis/gtfs/mapping/{ => routerequest}/LegacyRouteRequestMapperTest.java (99%) rename src/test/java/org/opentripplanner/apis/gtfs/mapping/{ => routerequest}/RouteRequestMapperTest.java (99%) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index c63882561e9..eae4bc2ad33 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -30,8 +30,8 @@ import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLQueryTypeStopsByRadiusArgs; -import org.opentripplanner.apis.gtfs.mapping.LegacyRouteRequestMapper; -import org.opentripplanner.apis.gtfs.mapping.RouteRequestMapper; +import org.opentripplanner.apis.gtfs.mapping.routerequest.LegacyRouteRequestMapper; +import org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapper; import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.ext.fares.impl.GtfsFaresService; import org.opentripplanner.ext.fares.model.FareRuleSet; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/AccessModeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/AccessModeMapper.java similarity index 92% rename from src/main/java/org/opentripplanner/apis/gtfs/mapping/AccessModeMapper.java rename to src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/AccessModeMapper.java index 5b26c8d5ee1..ac4c90a1a56 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/AccessModeMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/AccessModeMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.gtfs.mapping; +package org.opentripplanner.apis.gtfs.mapping.routerequest; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.routing.api.request.StreetMode; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectModeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/DirectModeMapper.java similarity index 92% rename from src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectModeMapper.java rename to src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/DirectModeMapper.java index 2e7fcee3d2f..e34e7ad2aed 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/DirectModeMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/DirectModeMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.gtfs.mapping; +package org.opentripplanner.apis.gtfs.mapping.routerequest; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.routing.api.request.StreetMode; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/EgressModeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/EgressModeMapper.java similarity index 91% rename from src/main/java/org/opentripplanner/apis/gtfs/mapping/EgressModeMapper.java rename to src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/EgressModeMapper.java index 4033b3e516d..f03b160ac97 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/EgressModeMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/EgressModeMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.gtfs.mapping; +package org.opentripplanner.apis.gtfs.mapping.routerequest; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.routing.api.request.StreetMode; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/ItineraryFilterDebugProfileMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ItineraryFilterDebugProfileMapper.java similarity index 92% rename from src/main/java/org/opentripplanner/apis/gtfs/mapping/ItineraryFilterDebugProfileMapper.java rename to src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ItineraryFilterDebugProfileMapper.java index 40242783755..7a83388ff1e 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/ItineraryFilterDebugProfileMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ItineraryFilterDebugProfileMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.gtfs.mapping; +package org.opentripplanner.apis.gtfs.mapping.routerequest; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java similarity index 98% rename from src/main/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapper.java rename to src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java index c790fa72656..22c3ef628e2 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java @@ -1,7 +1,7 @@ -package org.opentripplanner.apis.gtfs.mapping; +package org.opentripplanner.apis.gtfs.mapping.routerequest; -import static org.opentripplanner.apis.gtfs.mapping.RouteRequestMapper.parseNotFilters; -import static org.opentripplanner.apis.gtfs.mapping.RouteRequestMapper.parseSelectFilters; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapper.parseNotFilters; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapper.parseSelectFilters; import graphql.schema.DataFetchingEnvironment; import java.time.Duration; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/OptimizationTypeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/OptimizationTypeMapper.java similarity index 91% rename from src/main/java/org/opentripplanner/apis/gtfs/mapping/OptimizationTypeMapper.java rename to src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/OptimizationTypeMapper.java index 7f4f5200256..dc17891f9d2 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/OptimizationTypeMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/OptimizationTypeMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.gtfs.mapping; +package org.opentripplanner.apis.gtfs.mapping.routerequest; import javax.annotation.Nonnull; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java similarity index 99% rename from src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java rename to src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java index 3361e8a4410..e729cd32821 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.gtfs.mapping; +package org.opentripplanner.apis.gtfs.mapping.routerequest; import graphql.schema.DataFetchingEnvironment; import java.time.Instant; @@ -13,6 +13,7 @@ import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.apis.gtfs.mapping.TransitModeMapper; import org.opentripplanner.framework.collection.CollectionUtils; import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.framework.time.DurationUtils; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/TransferModeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransferModeMapper.java similarity index 87% rename from src/main/java/org/opentripplanner/apis/gtfs/mapping/TransferModeMapper.java rename to src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransferModeMapper.java index 674a7c08423..ffa7363e3a7 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/TransferModeMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransferModeMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.gtfs.mapping; +package org.opentripplanner.apis.gtfs.mapping.routerequest; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.routing.api.request.StreetMode; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/VehicleOptimizationTypeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/VehicleOptimizationTypeMapper.java similarity index 94% rename from src/main/java/org/opentripplanner/apis/gtfs/mapping/VehicleOptimizationTypeMapper.java rename to src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/VehicleOptimizationTypeMapper.java index f908c400b11..c47cbd3cf7a 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/VehicleOptimizationTypeMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/VehicleOptimizationTypeMapper.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.gtfs.mapping; +package org.opentripplanner.apis.gtfs.mapping.routerequest; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.routing.core.VehicleRoutingOptimizeType; diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java similarity index 99% rename from src/test/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapperTest.java rename to src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index bc976b26248..382543fc501 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/LegacyRouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.gtfs.mapping; +package org.opentripplanner.apis.gtfs.mapping.routerequest; import static graphql.execution.ExecutionContextBuilder.newExecutionContextBuilder; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java similarity index 99% rename from src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java rename to src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index 9704d6ca122..740ec2bc373 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -1,4 +1,4 @@ -package org.opentripplanner.apis.gtfs.mapping; +package org.opentripplanner.apis.gtfs.mapping.routerequest; import static graphql.Assert.assertTrue; import static graphql.execution.ExecutionContextBuilder.newExecutionContextBuilder; From fef0e7958afcecb0017e9e1446f083bc10fb1ca4 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Mar 2024 16:07:12 +0200 Subject: [PATCH 143/165] Split RouteRequestMapper --- .../mapping/routerequest/ArgumentUtils.java | 123 ++++ .../BicyclePreferencesMapper.java | 155 +++++ .../routerequest/CarPreferencesMapper.java | 69 ++ .../LegacyRouteRequestMapper.java | 4 +- .../routerequest/ModePreferencesMapper.java | 90 +++ .../routerequest/RouteRequestMapper.java | 587 +----------------- .../ScooterPreferencesMapper.java | 89 +++ .../TransitPreferencesMapper.java | 102 +++ .../routerequest/WalkPreferencesMapper.java | 31 + 9 files changed, 668 insertions(+), 582 deletions(-) create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ArgumentUtils.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/BicyclePreferencesMapper.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ModePreferencesMapper.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java create mode 100644 src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/WalkPreferencesMapper.java diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ArgumentUtils.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ArgumentUtils.java new file mode 100644 index 00000000000..c9235afe185 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ArgumentUtils.java @@ -0,0 +1,123 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import graphql.schema.DataFetchingEnvironment; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; + +public class ArgumentUtils { + + /** + * This methods returns list of modes and their costs from the argument structure: + * modes.transit.transit. This methods circumvents a bug in graphql-codegen as getting a list of + * input objects is not possible through using the generated types in {@link GraphQLTypes}. + *

+ * TODO this ugliness can be removed when the bug gets fixed + */ + static List> getTransitModes(DataFetchingEnvironment environment) { + if (environment.containsArgument("modes")) { + Map modesArgs = environment.getArgument("modes"); + if (modesArgs.containsKey("transit")) { + Map transitArgs = (Map) modesArgs.get("transit"); + if (transitArgs.containsKey("transit")) { + return (List>) transitArgs.get("transit"); + } + } + } + return List.of(); + } + + /** + * This methods returns parking preferences of the given type from argument structure: + * preferences.street.type.parking. This methods circumvents a bug in graphql-codegen as getting a + * list of input objects is not possible through using the generated types in + * {@link GraphQLTypes}. + *

+ * TODO this ugliness can be removed when the bug gets fixed + */ + @Nullable + static Map getParking(DataFetchingEnvironment environment, String type) { + return ( + (Map) ( + (Map) ( + (Map) ((Map) environment.getArgument("preferences")).get( + "street" + ) + ).get(type) + ).get("parking") + ); + } + + /** + * This methods returns required/banned parking tags of the given type from argument structure: + * preferences.street.type.parking.filters. This methods circumvents a bug in graphql-codegen as + * getting a list of input objects is not possible through using the generated types in + * {@link GraphQLTypes}. + *

+ * TODO this ugliness can be removed when the bug gets fixed + */ + @Nonnull + static Collection> getParkingFilters( + DataFetchingEnvironment environment, + String type + ) { + var parking = getParking(environment, type); + var filters = parking != null && parking.containsKey("filters") + ? getParking(environment, type).get("filters") + : null; + return filters != null ? (Collection>) filters : List.of(); + } + + /** + * This methods returns preferred/unpreferred parking tags of the given type from argument + * structure: preferences.street.type.parking.preferred. This methods circumvents a bug in + * graphql-codegen as getting a list of input objects is not possible through using the generated + * types in {@link GraphQLTypes}. + *

+ * TODO this ugliness can be removed when the bug gets fixed + */ + @Nonnull + static Collection> getParkingPreferred( + DataFetchingEnvironment environment, + String type + ) { + var parking = getParking(environment, type); + var preferred = parking != null && parking.containsKey("preferred") + ? getParking(environment, type).get("preferred") + : null; + return preferred != null ? (Collection>) preferred : List.of(); + } + + static Set parseNotFilters(Collection> filters) { + return parseFilters(filters, "not"); + } + + static Set parseSelectFilters(Collection> filters) { + return parseFilters(filters, "select"); + } + + @Nonnull + private static Set parseFilters(Collection> filters, String key) { + return filters + .stream() + .flatMap(f -> + parseOperation((Collection>>) f.getOrDefault(key, List.of())) + ) + .collect(Collectors.toSet()); + } + + private static Stream parseOperation(Collection>> map) { + return map + .stream() + .flatMap(f -> { + var tags = f.getOrDefault("tags", List.of()); + return tags.stream(); + }); + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/BicyclePreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/BicyclePreferencesMapper.java new file mode 100644 index 00000000000..cd0f1ef16da --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/BicyclePreferencesMapper.java @@ -0,0 +1,155 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ArgumentUtils.getParkingFilters; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ArgumentUtils.getParkingPreferred; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ArgumentUtils.parseNotFilters; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ArgumentUtils.parseSelectFilters; + +import graphql.schema.DataFetchingEnvironment; +import java.util.Set; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.framework.collection.CollectionUtils; +import org.opentripplanner.framework.time.DurationUtils; +import org.opentripplanner.routing.api.request.preference.BikePreferences; +import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; +import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences; +import org.opentripplanner.routing.api.request.preference.VehicleWalkingPreferences; + +public class BicyclePreferencesMapper { + + static void setBicyclePreferences( + BikePreferences.Builder preferences, + GraphQLTypes.GraphQLBicyclePreferencesInput args, + DataFetchingEnvironment environment + ) { + if (args != null) { + var speed = args.getGraphQLSpeed(); + if (speed != null) { + preferences.withSpeed(speed); + } + var reluctance = args.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); + } + var boardCost = args.getGraphQLBoardCost(); + if (boardCost != null) { + preferences.withBoardCost(boardCost.toSeconds()); + } + preferences.withWalking(walk -> setBicycleWalkPreferences(walk, args.getGraphQLWalk())); + preferences.withParking(parking -> + setBicycleParkingPreferences(parking, args.getGraphQLParking(), environment) + ); + preferences.withRental(rental -> setBicycleRentalPreferences(rental, args.getGraphQLRental()) + ); + setBicycleOptimization(preferences, args.getGraphQLOptimization()); + } + } + + private static void setBicycleWalkPreferences( + VehicleWalkingPreferences.Builder preferences, + GraphQLTypes.GraphQLBicycleWalkPreferencesInput args + ) { + if (args != null) { + var speed = args.getGraphQLSpeed(); + if (speed != null) { + preferences.withSpeed(speed); + } + var mountTime = args.getGraphQLMountDismountTime(); + if (mountTime != null) { + preferences.withMountDismountTime( + DurationUtils.requireNonNegativeShort(mountTime, "bicycle mount dismount time") + ); + } + var cost = args.getGraphQLCost(); + if (cost != null) { + var reluctance = cost.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); + } + var mountCost = cost.getGraphQLMountDismountCost(); + if (mountCost != null) { + preferences.withMountDismountCost(mountCost.toSeconds()); + } + } + } + } + + private static void setBicycleParkingPreferences( + VehicleParkingPreferences.Builder preferences, + GraphQLTypes.GraphQLBicycleParkingPreferencesInput args, + DataFetchingEnvironment environment + ) { + if (args != null) { + var unpreferredCost = args.getGraphQLUnpreferredCost(); + if (unpreferredCost != null) { + preferences.withUnpreferredVehicleParkingTagCost(unpreferredCost.toSeconds()); + } + var filters = getParkingFilters(environment, "bicycle"); + preferences.withRequiredVehicleParkingTags(parseSelectFilters(filters)); + preferences.withBannedVehicleParkingTags(parseNotFilters(filters)); + var preferred = getParkingPreferred(environment, "bicycle"); + preferences.withPreferredVehicleParkingTags(parseSelectFilters(preferred)); + preferences.withNotPreferredVehicleParkingTags(parseNotFilters(preferred)); + } + } + + private static void setBicycleRentalPreferences( + VehicleRentalPreferences.Builder preferences, + GraphQLTypes.GraphQLBicycleRentalPreferencesInput args + ) { + if (args != null) { + var allowedNetworks = args.getGraphQLAllowedNetworks(); + if (!CollectionUtils.isEmpty(allowedNetworks)) { + preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); + } + var bannedNetworks = args.getGraphQLBannedNetworks(); + if (!CollectionUtils.isEmpty(bannedNetworks)) { + preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); + } + var destinationPolicy = args.getGraphQLDestinationBicyclePolicy(); + if (destinationPolicy != null) { + var allowed = destinationPolicy.getGraphQLAllowKeeping(); + if (allowed != null) { + preferences.withAllowArrivingInRentedVehicleAtDestination(allowed); + } + var cost = destinationPolicy.getGraphQLKeepingCost(); + if (cost != null) { + preferences.withArrivingInRentalVehicleAtDestinationCost(cost.toSeconds()); + } + } + } + } + + private static void setBicycleOptimization( + BikePreferences.Builder preferences, + GraphQLTypes.GraphQLCyclingOptimizationInput args + ) { + if (args != null) { + var type = args.getGraphQLType(); + var mappedType = type != null ? VehicleOptimizationTypeMapper.map(type) : null; + if (mappedType != null) { + preferences.withOptimizeType(mappedType); + } + var triangleArgs = args.getGraphQLTriangle(); + if (isBicycleTriangleSet(triangleArgs)) { + preferences.withForcedOptimizeTriangle(triangle -> { + triangle + .withSlope(triangleArgs.getGraphQLFlatness()) + .withSafety(triangleArgs.getGraphQLSafety()) + .withTime(triangleArgs.getGraphQLTime()); + }); + } + } + } + + private static boolean isBicycleTriangleSet( + GraphQLTypes.GraphQLTriangleCyclingFactorsInput args + ) { + return ( + args != null && + args.getGraphQLFlatness() != null && + args.getGraphQLSafety() != null && + args.getGraphQLTime() != null + ); + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java new file mode 100644 index 00000000000..b815144c1d0 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java @@ -0,0 +1,69 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ArgumentUtils.getParkingFilters; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ArgumentUtils.getParkingPreferred; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ArgumentUtils.parseNotFilters; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ArgumentUtils.parseSelectFilters; + +import graphql.schema.DataFetchingEnvironment; +import java.util.Set; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.framework.collection.CollectionUtils; +import org.opentripplanner.routing.api.request.preference.CarPreferences; +import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; +import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences; + +public class CarPreferencesMapper { + + static void setCarPreferences( + CarPreferences.Builder preferences, + GraphQLTypes.GraphQLCarPreferencesInput args, + DataFetchingEnvironment environment + ) { + if (args != null) { + var reluctance = args.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); + } + preferences.withParking(parking -> + setCarParkingPreferences(parking, args.getGraphQLParking(), environment) + ); + preferences.withRental(rental -> setCarRentalPreferences(rental, args.getGraphQLRental())); + } + } + + private static void setCarParkingPreferences( + VehicleParkingPreferences.Builder preferences, + GraphQLTypes.GraphQLCarParkingPreferencesInput args, + DataFetchingEnvironment environment + ) { + if (args != null) { + var unpreferredCost = args.getGraphQLUnpreferredCost(); + if (unpreferredCost != null) { + preferences.withUnpreferredVehicleParkingTagCost(unpreferredCost.toSeconds()); + } + var filters = getParkingFilters(environment, "car"); + preferences.withRequiredVehicleParkingTags(parseSelectFilters(filters)); + preferences.withBannedVehicleParkingTags(parseNotFilters(filters)); + var preferred = getParkingPreferred(environment, "car"); + preferences.withPreferredVehicleParkingTags(parseSelectFilters(preferred)); + preferences.withNotPreferredVehicleParkingTags(parseNotFilters(preferred)); + } + } + + private static void setCarRentalPreferences( + VehicleRentalPreferences.Builder preferences, + GraphQLTypes.GraphQLCarRentalPreferencesInput args + ) { + if (args != null) { + var allowedNetworks = args.getGraphQLAllowedNetworks(); + if (!CollectionUtils.isEmpty(allowedNetworks)) { + preferences.withBannedNetworks(Set.copyOf(allowedNetworks)); + } + var bannedNetworks = args.getGraphQLBannedNetworks(); + if (!CollectionUtils.isEmpty(bannedNetworks)) { + preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); + } + } + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java index 22c3ef628e2..f602871162f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java @@ -1,7 +1,7 @@ package org.opentripplanner.apis.gtfs.mapping.routerequest; -import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapper.parseNotFilters; -import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapper.parseSelectFilters; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ArgumentUtils.parseNotFilters; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ArgumentUtils.parseSelectFilters; import graphql.schema.DataFetchingEnvironment; import java.time.Duration; diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ModePreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ModePreferencesMapper.java new file mode 100644 index 00000000000..28e884acbde --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ModePreferencesMapper.java @@ -0,0 +1,90 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ArgumentUtils.getTransitModes; + +import graphql.schema.DataFetchingEnvironment; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.apis.gtfs.mapping.TransitModeMapper; +import org.opentripplanner.framework.collection.CollectionUtils; +import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.routing.api.request.request.JourneyRequest; +import org.opentripplanner.routing.api.request.request.filter.SelectRequest; +import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest; +import org.opentripplanner.transit.model.basic.MainAndSubMode; + +public class ModePreferencesMapper { + + /** + * TODO this doesn't support multiple street modes yet + */ + static void setModes( + JourneyRequest journey, + GraphQLTypes.GraphQLPlanModesInput modesInput, + DataFetchingEnvironment environment + ) { + var direct = modesInput.getGraphQLDirect(); + if (Boolean.TRUE.equals(modesInput.getGraphQLTransitOnly())) { + journey.direct().setMode(StreetMode.NOT_SET); + } else if (!CollectionUtils.isEmpty(direct)) { + journey.direct().setMode(DirectModeMapper.map(direct.getFirst())); + } + + var transit = modesInput.getGraphQLTransit(); + if (Boolean.TRUE.equals(modesInput.getGraphQLDirectOnly())) { + journey.transit().disable(); + } else if (transit != null) { + var access = transit.getGraphQLAccess(); + if (!CollectionUtils.isEmpty(access)) { + journey.access().setMode(AccessModeMapper.map(access.getFirst())); + } + + var egress = transit.getGraphQLEgress(); + if (!CollectionUtils.isEmpty(egress)) { + journey.egress().setMode(EgressModeMapper.map(egress.getFirst())); + } + + var transfer = transit.getGraphQLTransfer(); + if (!CollectionUtils.isEmpty(transfer)) { + journey.transfer().setMode(TransferModeMapper.map(transfer.getFirst())); + } + validateStreetModes(journey); + + var transitModes = getTransitModes(environment); + if (!CollectionUtils.isEmpty(transitModes)) { + var filterRequestBuilder = TransitFilterRequest.of(); + var mainAndSubModes = transitModes + .stream() + .map(mode -> + new MainAndSubMode( + TransitModeMapper.map( + GraphQLTypes.GraphQLTransitMode.valueOf((String) mode.get("mode")) + ) + ) + ) + .toList(); + filterRequestBuilder.addSelect( + SelectRequest.of().withTransportModes(mainAndSubModes).build() + ); + journey.transit().setFilters(List.of(filterRequestBuilder.build())); + } + } + } + + /** + * TODO this doesn't support multiple street modes yet + */ + private static void validateStreetModes(JourneyRequest journey) { + Set modes = new HashSet(); + modes.add(journey.access().mode()); + modes.add(journey.egress().mode()); + modes.add(journey.transfer().mode()); + if (modes.contains(StreetMode.BIKE) && modes.size() != 1) { + throw new IllegalArgumentException( + "If BICYCLE is used for access, egress or transfer, then it should be used for all." + ); + } + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java index e729cd32821..3044577258f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java @@ -1,40 +1,23 @@ package org.opentripplanner.apis.gtfs.mapping.routerequest; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.BicyclePreferencesMapper.setBicyclePreferences; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.CarPreferencesMapper.setCarPreferences; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ModePreferencesMapper.setModes; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ScooterPreferencesMapper.setScooterPreferences; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.TransitPreferencesMapper.setTransitPreferences; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.WalkPreferencesMapper.setWalkPreferences; + import graphql.schema.DataFetchingEnvironment; import java.time.Instant; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; -import org.opentripplanner.apis.gtfs.mapping.TransitModeMapper; -import org.opentripplanner.framework.collection.CollectionUtils; import org.opentripplanner.framework.graphql.GraphQLUtils; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.model.GenericLocation; import org.opentripplanner.routing.api.request.RouteRequest; -import org.opentripplanner.routing.api.request.StreetMode; -import org.opentripplanner.routing.api.request.preference.BikePreferences; -import org.opentripplanner.routing.api.request.preference.CarPreferences; import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; -import org.opentripplanner.routing.api.request.preference.ScooterPreferences; -import org.opentripplanner.routing.api.request.preference.TransferPreferences; -import org.opentripplanner.routing.api.request.preference.TransitPreferences; -import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; -import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences; -import org.opentripplanner.routing.api.request.preference.VehicleWalkingPreferences; -import org.opentripplanner.routing.api.request.preference.WalkPreferences; -import org.opentripplanner.routing.api.request.request.JourneyRequest; -import org.opentripplanner.routing.api.request.request.filter.SelectRequest; -import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest; -import org.opentripplanner.transit.model.basic.MainAndSubMode; import org.opentripplanner.transit.model.framework.FeedScopedId; public class RouteRequestMapper { @@ -85,33 +68,6 @@ public static RouteRequest toRouteRequest( return request; } - static Set parseNotFilters(Collection> filters) { - return parseFilters(filters, "not"); - } - - static Set parseSelectFilters(Collection> filters) { - return parseFilters(filters, "select"); - } - - @Nonnull - private static Set parseFilters(Collection> filters, String key) { - return filters - .stream() - .flatMap(f -> - parseOperation((Collection>>) f.getOrDefault(key, List.of())) - ) - .collect(Collectors.toSet()); - } - - private static Stream parseOperation(Collection>> map) { - return map - .stream() - .flatMap(f -> { - var tags = f.getOrDefault("tags", List.of()); - return tags.stream(); - }); - } - private static void setPreferences( RoutingPreferences.Builder prefs, RouteRequest request, @@ -151,92 +107,6 @@ private static void setItineraryFilters( } } - private static void setTransitPreferences( - TransitPreferences.Builder transitPreferences, - TransferPreferences.Builder transferPreferences, - GraphQLTypes.GraphQLQueryTypePlanConnectionArgs args, - DataFetchingEnvironment environment - ) { - var modes = args.getGraphQLModes(); - var transit = getTransitModes(environment); - if (!Boolean.TRUE.equals(modes.getGraphQLDirectOnly()) && !CollectionUtils.isEmpty(transit)) { - var reluctanceForMode = transit - .stream() - .filter(mode -> mode.containsKey("cost")) - .collect( - Collectors.toMap( - mode -> - TransitModeMapper.map( - GraphQLTypes.GraphQLTransitMode.valueOf((String) mode.get("mode")) - ), - mode -> (Double) ((Map) mode.get("cost")).get("reluctance") - ) - ); - transitPreferences.setReluctanceForMode(reluctanceForMode); - } - var transitArgs = args.getGraphQLPreferences().getGraphQLTransit(); - if (transitArgs != null) { - var board = transitArgs.getGraphQLBoard(); - if (board != null) { - var slack = board.getGraphQLSlack(); - if (slack != null) { - transitPreferences.withDefaultBoardSlackSec( - (int) DurationUtils.requireNonNegativeMedium(slack, "board slack").toSeconds() - ); - } - var waitReluctance = board.getGraphQLWaitReluctance(); - if (waitReluctance != null) { - transferPreferences.withWaitReluctance(waitReluctance); - } - } - var alight = transitArgs.getGraphQLAlight(); - if (alight != null) { - var slack = alight.getGraphQLSlack(); - if (slack != null) { - transitPreferences.withDefaultAlightSlackSec( - (int) DurationUtils.requireNonNegativeMedium(slack, "alight slack").toSeconds() - ); - } - } - var transfer = transitArgs.getGraphQLTransfer(); - if (transfer != null) { - var cost = transfer.getGraphQLCost(); - if (cost != null) { - transferPreferences.withCost(cost.toSeconds()); - } - var slack = transfer.getGraphQLSlack(); - if (slack != null) { - transferPreferences.withSlack( - (int) DurationUtils.requireNonNegativeMedium(slack, "transfer slack").toSeconds() - ); - } - var maxTransfers = transfer.getGraphQLMaximumTransfers(); - if (maxTransfers != null) { - transferPreferences.withMaxTransfers(maxTransfers); - } - var additionalTransfers = transfer.getGraphQLMaximumAdditionalTransfers(); - if (additionalTransfers != null) { - transferPreferences.withMaxAdditionalTransfers(additionalTransfers); - } - } - var timetable = transitArgs.getGraphQLTimetable(); - if (timetable != null) { - var excludeUpdates = timetable.getGraphQLExcludeRealTimeUpdates(); - if (excludeUpdates != null) { - transitPreferences.setIgnoreRealtimeUpdates(excludeUpdates); - } - var includePlannedCancellations = timetable.getGraphQLIncludePlannedCancellations(); - if (includePlannedCancellations != null) { - transitPreferences.setIncludePlannedCancellations(includePlannedCancellations); - } - var includeRealtimeCancellations = timetable.getGraphQLIncludeRealTimeCancellations(); - if (includeRealtimeCancellations != null) { - transitPreferences.setIncludeRealtimeCancellations(includeRealtimeCancellations); - } - } - } - } - private static void setStreetPreferences( RoutingPreferences.Builder preferences, GraphQLTypes.GraphQLPlanStreetPreferencesInput args, @@ -252,358 +122,6 @@ private static void setStreetPreferences( } } - private static void setBicyclePreferences( - BikePreferences.Builder preferences, - GraphQLTypes.GraphQLBicyclePreferencesInput args, - DataFetchingEnvironment environment - ) { - if (args != null) { - var speed = args.getGraphQLSpeed(); - if (speed != null) { - preferences.withSpeed(speed); - } - var reluctance = args.getGraphQLReluctance(); - if (reluctance != null) { - preferences.withReluctance(reluctance); - } - var boardCost = args.getGraphQLBoardCost(); - if (boardCost != null) { - preferences.withBoardCost(boardCost.toSeconds()); - } - preferences.withWalking(walk -> setBicycleWalkPreferences(walk, args.getGraphQLWalk())); - preferences.withParking(parking -> - setBicycleParkingPreferences(parking, args.getGraphQLParking(), environment) - ); - preferences.withRental(rental -> setBicycleRentalPreferences(rental, args.getGraphQLRental()) - ); - setBicycleOptimization(preferences, args.getGraphQLOptimization()); - } - } - - private static void setBicycleWalkPreferences( - VehicleWalkingPreferences.Builder preferences, - GraphQLTypes.GraphQLBicycleWalkPreferencesInput args - ) { - if (args != null) { - var speed = args.getGraphQLSpeed(); - if (speed != null) { - preferences.withSpeed(speed); - } - var mountTime = args.getGraphQLMountDismountTime(); - if (mountTime != null) { - preferences.withMountDismountTime( - DurationUtils.requireNonNegativeShort(mountTime, "bicycle mount dismount time") - ); - } - var cost = args.getGraphQLCost(); - if (cost != null) { - var reluctance = cost.getGraphQLReluctance(); - if (reluctance != null) { - preferences.withReluctance(reluctance); - } - var mountCost = cost.getGraphQLMountDismountCost(); - if (mountCost != null) { - preferences.withMountDismountCost(mountCost.toSeconds()); - } - } - } - } - - private static void setBicycleParkingPreferences( - VehicleParkingPreferences.Builder preferences, - GraphQLTypes.GraphQLBicycleParkingPreferencesInput args, - DataFetchingEnvironment environment - ) { - if (args != null) { - var unpreferredCost = args.getGraphQLUnpreferredCost(); - if (unpreferredCost != null) { - preferences.withUnpreferredVehicleParkingTagCost(unpreferredCost.toSeconds()); - } - var filters = getParkingFilters(environment, "bicycle"); - preferences.withRequiredVehicleParkingTags(parseSelectFilters(filters)); - preferences.withBannedVehicleParkingTags(parseNotFilters(filters)); - var preferred = getParkingPreferred(environment, "bicycle"); - preferences.withPreferredVehicleParkingTags(parseSelectFilters(preferred)); - preferences.withNotPreferredVehicleParkingTags(parseNotFilters(preferred)); - } - } - - /** - * This methods returns required/banned parking tags of the given type from argument structure: - * preferences.street.type.parking.filters. This methods circumvents a bug in graphql-codegen as - * getting a list of input objects is not possible through using the generated types in - * {@link GraphQLTypes}. - *

- * TODO this ugliness can be removed when the bug gets fixed - */ - @Nonnull - private static Collection> getParkingFilters( - DataFetchingEnvironment environment, - String type - ) { - var parking = getParking(environment, type); - var filters = parking != null && parking.containsKey("filters") - ? getParking(environment, type).get("filters") - : null; - return filters != null ? (Collection>) filters : List.of(); - } - - /** - * This methods returns preferred/unpreferred parking tags of the given type from argument - * structure: preferences.street.type.parking.preferred. This methods circumvents a bug in - * graphql-codegen as getting a list of input objects is not possible through using the generated - * types in {@link GraphQLTypes}. - *

- * TODO this ugliness can be removed when the bug gets fixed - */ - @Nonnull - private static Collection> getParkingPreferred( - DataFetchingEnvironment environment, - String type - ) { - var parking = getParking(environment, type); - var preferred = parking != null && parking.containsKey("preferred") - ? getParking(environment, type).get("preferred") - : null; - return preferred != null ? (Collection>) preferred : List.of(); - } - - /** - * This methods returns parking preferences of the given type from argument structure: - * preferences.street.type.parking. This methods circumvents a bug in graphql-codegen as getting a - * list of input objects is not possible through using the generated types in - * {@link GraphQLTypes}. - *

- * TODO this ugliness can be removed when the bug gets fixed - */ - @Nullable - private static Map getParking(DataFetchingEnvironment environment, String type) { - return ( - (Map) ( - (Map) ( - (Map) ((Map) environment.getArgument("preferences")).get( - "street" - ) - ).get(type) - ).get("parking") - ); - } - - private static void setBicycleRentalPreferences( - VehicleRentalPreferences.Builder preferences, - GraphQLTypes.GraphQLBicycleRentalPreferencesInput args - ) { - if (args != null) { - var allowedNetworks = args.getGraphQLAllowedNetworks(); - if (!CollectionUtils.isEmpty(allowedNetworks)) { - preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); - } - var bannedNetworks = args.getGraphQLBannedNetworks(); - if (!CollectionUtils.isEmpty(bannedNetworks)) { - preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); - } - var destinationPolicy = args.getGraphQLDestinationBicyclePolicy(); - if (destinationPolicy != null) { - var allowed = destinationPolicy.getGraphQLAllowKeeping(); - if (allowed != null) { - preferences.withAllowArrivingInRentedVehicleAtDestination(allowed); - } - var cost = destinationPolicy.getGraphQLKeepingCost(); - if (cost != null) { - preferences.withArrivingInRentalVehicleAtDestinationCost(cost.toSeconds()); - } - } - } - } - - private static void setBicycleOptimization( - BikePreferences.Builder preferences, - GraphQLTypes.GraphQLCyclingOptimizationInput args - ) { - if (args != null) { - var type = args.getGraphQLType(); - var mappedType = type != null ? VehicleOptimizationTypeMapper.map(type) : null; - if (mappedType != null) { - preferences.withOptimizeType(mappedType); - } - var triangleArgs = args.getGraphQLTriangle(); - if (isBicycleTriangleSet(triangleArgs)) { - preferences.withForcedOptimizeTriangle(triangle -> { - triangle - .withSlope(triangleArgs.getGraphQLFlatness()) - .withSafety(triangleArgs.getGraphQLSafety()) - .withTime(triangleArgs.getGraphQLTime()); - }); - } - } - } - - private static boolean isBicycleTriangleSet( - GraphQLTypes.GraphQLTriangleCyclingFactorsInput args - ) { - return ( - args != null && - args.getGraphQLFlatness() != null && - args.getGraphQLSafety() != null && - args.getGraphQLTime() != null - ); - } - - private static void setCarPreferences( - CarPreferences.Builder preferences, - GraphQLTypes.GraphQLCarPreferencesInput args, - DataFetchingEnvironment environment - ) { - if (args != null) { - var reluctance = args.getGraphQLReluctance(); - if (reluctance != null) { - preferences.withReluctance(reluctance); - } - preferences.withParking(parking -> - setCarParkingPreferences(parking, args.getGraphQLParking(), environment) - ); - preferences.withRental(rental -> setCarRentalPreferences(rental, args.getGraphQLRental())); - } - } - - private static void setCarParkingPreferences( - VehicleParkingPreferences.Builder preferences, - GraphQLTypes.GraphQLCarParkingPreferencesInput args, - DataFetchingEnvironment environment - ) { - if (args != null) { - var unpreferredCost = args.getGraphQLUnpreferredCost(); - if (unpreferredCost != null) { - preferences.withUnpreferredVehicleParkingTagCost(unpreferredCost.toSeconds()); - } - var filters = getParkingFilters(environment, "car"); - preferences.withRequiredVehicleParkingTags(parseSelectFilters(filters)); - preferences.withBannedVehicleParkingTags(parseNotFilters(filters)); - var preferred = getParkingPreferred(environment, "car"); - preferences.withPreferredVehicleParkingTags(parseSelectFilters(preferred)); - preferences.withNotPreferredVehicleParkingTags(parseNotFilters(preferred)); - } - } - - private static void setCarRentalPreferences( - VehicleRentalPreferences.Builder preferences, - GraphQLTypes.GraphQLCarRentalPreferencesInput args - ) { - if (args != null) { - var allowedNetworks = args.getGraphQLAllowedNetworks(); - if (!CollectionUtils.isEmpty(allowedNetworks)) { - preferences.withBannedNetworks(Set.copyOf(allowedNetworks)); - } - var bannedNetworks = args.getGraphQLBannedNetworks(); - if (!CollectionUtils.isEmpty(bannedNetworks)) { - preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); - } - } - } - - private static void setScooterPreferences( - ScooterPreferences.Builder preferences, - GraphQLTypes.GraphQLScooterPreferencesInput args - ) { - if (args != null) { - var speed = args.getGraphQLSpeed(); - if (speed != null) { - preferences.withSpeed(speed); - } - var reluctance = args.getGraphQLReluctance(); - if (reluctance != null) { - preferences.withReluctance(reluctance); - } - preferences.withRental(rental -> setScooterRentalPreferences(rental, args.getGraphQLRental()) - ); - setScooterOptimization(preferences, args.getGraphQLOptimization()); - } - } - - private static void setScooterRentalPreferences( - VehicleRentalPreferences.Builder preferences, - GraphQLTypes.GraphQLScooterRentalPreferencesInput args - ) { - if (args != null) { - var allowedNetworks = args.getGraphQLAllowedNetworks(); - if (!CollectionUtils.isEmpty(allowedNetworks)) { - preferences.withBannedNetworks(Set.copyOf(allowedNetworks)); - } - var bannedNetworks = args.getGraphQLBannedNetworks(); - if (!CollectionUtils.isEmpty(bannedNetworks)) { - preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); - } - var destinationPolicy = args.getGraphQLDestinationScooterPolicy(); - if (destinationPolicy != null) { - var allowed = destinationPolicy.getGraphQLAllowKeeping(); - if (allowed != null) { - preferences.withAllowArrivingInRentedVehicleAtDestination(allowed); - } - var cost = destinationPolicy.getGraphQLKeepingCost(); - if (cost != null) { - preferences.withArrivingInRentalVehicleAtDestinationCost(cost.toSeconds()); - } - } - } - } - - private static void setScooterOptimization( - ScooterPreferences.Builder preferences, - GraphQLTypes.GraphQLScooterOptimizationInput args - ) { - if (args != null) { - var type = args.getGraphQLType(); - var mappedType = type != null ? VehicleOptimizationTypeMapper.map(type) : null; - if (mappedType != null) { - preferences.withOptimizeType(mappedType); - } - var triangleArgs = args.getGraphQLTriangle(); - if (isScooterTriangleSet(triangleArgs)) { - preferences.withForcedOptimizeTriangle(triangle -> { - triangle - .withSlope(triangleArgs.getGraphQLFlatness()) - .withSafety(triangleArgs.getGraphQLSafety()) - .withTime(triangleArgs.getGraphQLTime()); - }); - } - } - } - - private static boolean isScooterTriangleSet( - GraphQLTypes.GraphQLTriangleScooterFactorsInput args - ) { - return ( - args != null && - args.getGraphQLFlatness() != null && - args.getGraphQLSafety() != null && - args.getGraphQLTime() != null - ); - } - - private static void setWalkPreferences( - WalkPreferences.Builder preferences, - GraphQLTypes.GraphQLWalkPreferencesInput args - ) { - if (args != null) { - var speed = args.getGraphQLSpeed(); - if (speed != null) { - preferences.withSpeed(speed); - } - var reluctance = args.getGraphQLReluctance(); - if (reluctance != null) { - preferences.withReluctance(reluctance); - } - var walkSafetyFactor = args.getGraphQLWalkSafetyFactor(); - if (walkSafetyFactor != null) { - preferences.withSafetyFactor(walkSafetyFactor); - } - var boardCost = args.getGraphQLBoardCost(); - if (boardCost != null) { - preferences.withBoardCost(boardCost.toSeconds()); - } - } - } - private static void setAccessibilityPreferences( RouteRequest request, GraphQLTypes.GraphQLAccessibilityPreferencesInput preferenceArgs @@ -613,97 +131,6 @@ private static void setAccessibilityPreferences( } } - /** - * TODO this doesn't support multiple street modes yet - */ - private static void setModes( - JourneyRequest journey, - GraphQLTypes.GraphQLPlanModesInput modesInput, - DataFetchingEnvironment environment - ) { - var direct = modesInput.getGraphQLDirect(); - if (Boolean.TRUE.equals(modesInput.getGraphQLTransitOnly())) { - journey.direct().setMode(StreetMode.NOT_SET); - } else if (!CollectionUtils.isEmpty(direct)) { - journey.direct().setMode(DirectModeMapper.map(direct.getFirst())); - } - - var transit = modesInput.getGraphQLTransit(); - if (Boolean.TRUE.equals(modesInput.getGraphQLDirectOnly())) { - journey.transit().disable(); - } else if (transit != null) { - var access = transit.getGraphQLAccess(); - if (!CollectionUtils.isEmpty(access)) { - journey.access().setMode(AccessModeMapper.map(access.getFirst())); - } - - var egress = transit.getGraphQLEgress(); - if (!CollectionUtils.isEmpty(egress)) { - journey.egress().setMode(EgressModeMapper.map(egress.getFirst())); - } - - var transfer = transit.getGraphQLTransfer(); - if (!CollectionUtils.isEmpty(transfer)) { - journey.transfer().setMode(TransferModeMapper.map(transfer.getFirst())); - } - validateStreetModes(journey); - - var transitModes = getTransitModes(environment); - if (!CollectionUtils.isEmpty(transitModes)) { - var filterRequestBuilder = TransitFilterRequest.of(); - var mainAndSubModes = transitModes - .stream() - .map(mode -> - new MainAndSubMode( - TransitModeMapper.map( - GraphQLTypes.GraphQLTransitMode.valueOf((String) mode.get("mode")) - ) - ) - ) - .toList(); - filterRequestBuilder.addSelect( - SelectRequest.of().withTransportModes(mainAndSubModes).build() - ); - journey.transit().setFilters(List.of(filterRequestBuilder.build())); - } - } - } - - /** - * TODO this doesn't support multiple street modes yet - */ - private static void validateStreetModes(JourneyRequest journey) { - Set modes = new HashSet(); - modes.add(journey.access().mode()); - modes.add(journey.egress().mode()); - modes.add(journey.transfer().mode()); - if (modes.contains(StreetMode.BIKE) && modes.size() != 1) { - throw new IllegalArgumentException( - "If BICYCLE is used for access, egress or transfer, then it should be used for all." - ); - } - } - - /** - * This methods returns list of modes and their costs from the argument structure: - * modes.transit.transit. This methods circumvents a bug in graphql-codegen as getting a list of - * input objects is not possible through using the generated types in {@link GraphQLTypes}. - *

- * TODO this ugliness can be removed when the bug gets fixed - */ - private static List> getTransitModes(DataFetchingEnvironment environment) { - if (environment.containsArgument("modes")) { - Map modesArgs = environment.getArgument("modes"); - if (modesArgs.containsKey("transit")) { - Map transitArgs = (Map) modesArgs.get("transit"); - if (transitArgs.containsKey("transit")) { - return (List>) transitArgs.get("transit"); - } - } - } - return List.of(); - } - private static GenericLocation parseGenericLocation( GraphQLTypes.GraphQLPlanLabeledLocationInput locationInput ) { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java new file mode 100644 index 00000000000..c6e83d9af0a --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java @@ -0,0 +1,89 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import java.util.Set; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.framework.collection.CollectionUtils; +import org.opentripplanner.routing.api.request.preference.ScooterPreferences; +import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences; + +public class ScooterPreferencesMapper { + + static void setScooterPreferences( + ScooterPreferences.Builder preferences, + GraphQLTypes.GraphQLScooterPreferencesInput args + ) { + if (args != null) { + var speed = args.getGraphQLSpeed(); + if (speed != null) { + preferences.withSpeed(speed); + } + var reluctance = args.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); + } + preferences.withRental(rental -> setScooterRentalPreferences(rental, args.getGraphQLRental()) + ); + setScooterOptimization(preferences, args.getGraphQLOptimization()); + } + } + + private static void setScooterRentalPreferences( + VehicleRentalPreferences.Builder preferences, + GraphQLTypes.GraphQLScooterRentalPreferencesInput args + ) { + if (args != null) { + var allowedNetworks = args.getGraphQLAllowedNetworks(); + if (!CollectionUtils.isEmpty(allowedNetworks)) { + preferences.withBannedNetworks(Set.copyOf(allowedNetworks)); + } + var bannedNetworks = args.getGraphQLBannedNetworks(); + if (!CollectionUtils.isEmpty(bannedNetworks)) { + preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); + } + var destinationPolicy = args.getGraphQLDestinationScooterPolicy(); + if (destinationPolicy != null) { + var allowed = destinationPolicy.getGraphQLAllowKeeping(); + if (allowed != null) { + preferences.withAllowArrivingInRentedVehicleAtDestination(allowed); + } + var cost = destinationPolicy.getGraphQLKeepingCost(); + if (cost != null) { + preferences.withArrivingInRentalVehicleAtDestinationCost(cost.toSeconds()); + } + } + } + } + + private static void setScooterOptimization( + ScooterPreferences.Builder preferences, + GraphQLTypes.GraphQLScooterOptimizationInput args + ) { + if (args != null) { + var type = args.getGraphQLType(); + var mappedType = type != null ? VehicleOptimizationTypeMapper.map(type) : null; + if (mappedType != null) { + preferences.withOptimizeType(mappedType); + } + var triangleArgs = args.getGraphQLTriangle(); + if (isScooterTriangleSet(triangleArgs)) { + preferences.withForcedOptimizeTriangle(triangle -> { + triangle + .withSlope(triangleArgs.getGraphQLFlatness()) + .withSafety(triangleArgs.getGraphQLSafety()) + .withTime(triangleArgs.getGraphQLTime()); + }); + } + } + } + + private static boolean isScooterTriangleSet( + GraphQLTypes.GraphQLTriangleScooterFactorsInput args + ) { + return ( + args != null && + args.getGraphQLFlatness() != null && + args.getGraphQLSafety() != null && + args.getGraphQLTime() != null + ); + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java new file mode 100644 index 00000000000..792a23bb2a1 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java @@ -0,0 +1,102 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static org.opentripplanner.apis.gtfs.mapping.routerequest.ArgumentUtils.getTransitModes; + +import graphql.schema.DataFetchingEnvironment; +import java.util.Map; +import java.util.stream.Collectors; +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.apis.gtfs.mapping.TransitModeMapper; +import org.opentripplanner.framework.collection.CollectionUtils; +import org.opentripplanner.framework.time.DurationUtils; +import org.opentripplanner.routing.api.request.preference.TransferPreferences; +import org.opentripplanner.routing.api.request.preference.TransitPreferences; + +public class TransitPreferencesMapper { + + static void setTransitPreferences( + TransitPreferences.Builder transitPreferences, + TransferPreferences.Builder transferPreferences, + GraphQLTypes.GraphQLQueryTypePlanConnectionArgs args, + DataFetchingEnvironment environment + ) { + var modes = args.getGraphQLModes(); + var transit = getTransitModes(environment); + if (!Boolean.TRUE.equals(modes.getGraphQLDirectOnly()) && !CollectionUtils.isEmpty(transit)) { + var reluctanceForMode = transit + .stream() + .filter(mode -> mode.containsKey("cost")) + .collect( + Collectors.toMap( + mode -> + TransitModeMapper.map( + GraphQLTypes.GraphQLTransitMode.valueOf((String) mode.get("mode")) + ), + mode -> (Double) ((Map) mode.get("cost")).get("reluctance") + ) + ); + transitPreferences.setReluctanceForMode(reluctanceForMode); + } + var transitArgs = args.getGraphQLPreferences().getGraphQLTransit(); + if (transitArgs != null) { + var board = transitArgs.getGraphQLBoard(); + if (board != null) { + var slack = board.getGraphQLSlack(); + if (slack != null) { + transitPreferences.withDefaultBoardSlackSec( + (int) DurationUtils.requireNonNegativeMedium(slack, "board slack").toSeconds() + ); + } + var waitReluctance = board.getGraphQLWaitReluctance(); + if (waitReluctance != null) { + transferPreferences.withWaitReluctance(waitReluctance); + } + } + var alight = transitArgs.getGraphQLAlight(); + if (alight != null) { + var slack = alight.getGraphQLSlack(); + if (slack != null) { + transitPreferences.withDefaultAlightSlackSec( + (int) DurationUtils.requireNonNegativeMedium(slack, "alight slack").toSeconds() + ); + } + } + var transfer = transitArgs.getGraphQLTransfer(); + if (transfer != null) { + var cost = transfer.getGraphQLCost(); + if (cost != null) { + transferPreferences.withCost(cost.toSeconds()); + } + var slack = transfer.getGraphQLSlack(); + if (slack != null) { + transferPreferences.withSlack( + (int) DurationUtils.requireNonNegativeMedium(slack, "transfer slack").toSeconds() + ); + } + var maxTransfers = transfer.getGraphQLMaximumTransfers(); + if (maxTransfers != null) { + transferPreferences.withMaxTransfers(maxTransfers); + } + var additionalTransfers = transfer.getGraphQLMaximumAdditionalTransfers(); + if (additionalTransfers != null) { + transferPreferences.withMaxAdditionalTransfers(additionalTransfers); + } + } + var timetable = transitArgs.getGraphQLTimetable(); + if (timetable != null) { + var excludeUpdates = timetable.getGraphQLExcludeRealTimeUpdates(); + if (excludeUpdates != null) { + transitPreferences.setIgnoreRealtimeUpdates(excludeUpdates); + } + var includePlannedCancellations = timetable.getGraphQLIncludePlannedCancellations(); + if (includePlannedCancellations != null) { + transitPreferences.setIncludePlannedCancellations(includePlannedCancellations); + } + var includeRealtimeCancellations = timetable.getGraphQLIncludeRealTimeCancellations(); + if (includeRealtimeCancellations != null) { + transitPreferences.setIncludeRealtimeCancellations(includeRealtimeCancellations); + } + } + } + } +} diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/WalkPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/WalkPreferencesMapper.java new file mode 100644 index 00000000000..be2d520ec04 --- /dev/null +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/WalkPreferencesMapper.java @@ -0,0 +1,31 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; +import org.opentripplanner.routing.api.request.preference.WalkPreferences; + +public class WalkPreferencesMapper { + + static void setWalkPreferences( + WalkPreferences.Builder preferences, + GraphQLTypes.GraphQLWalkPreferencesInput args + ) { + if (args != null) { + var speed = args.getGraphQLSpeed(); + if (speed != null) { + preferences.withSpeed(speed); + } + var reluctance = args.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); + } + var walkSafetyFactor = args.getGraphQLWalkSafetyFactor(); + if (walkSafetyFactor != null) { + preferences.withSafetyFactor(walkSafetyFactor); + } + var boardCost = args.getGraphQLBoardCost(); + if (boardCost != null) { + preferences.withBoardCost(boardCost.toSeconds()); + } + } + } +} From 171a09060c1912fb285ea6a9a7b2dcde60b036f9 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 22 Mar 2024 16:48:51 +0200 Subject: [PATCH 144/165] Split bicycle tests into own class --- .../RouteRequestMapperBicycleTest.java | 314 +++++++++++++++++ .../routerequest/RouteRequestMapperTest.java | 323 ++---------------- 2 files changed, 351 insertions(+), 286 deletions(-) create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperBicycleTest.java diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperBicycleTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperBicycleTest.java new file mode 100644 index 00000000000..50f04f4d2a2 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperBicycleTest.java @@ -0,0 +1,314 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.createArgsCopy; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.executionContext; + +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.opentripplanner._support.time.ZoneIds; +import org.opentripplanner.apis.gtfs.GraphQLRequestContext; +import org.opentripplanner.apis.gtfs.TestRoutingService; +import org.opentripplanner.ext.fares.impl.DefaultFareService; +import org.opentripplanner.framework.model.Cost; +import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.routing.core.VehicleRoutingOptimizeType; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.graphfinder.GraphFinder; +import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; +import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; +import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.TransitModel; + +class RouteRequestMapperBicycleTest { + + private static final GraphQLRequestContext context; + private static final Map args = new HashMap<>(); + + static { + args.put( + "origin", + Map.ofEntries( + entry("location", Map.of("coordinate", Map.of("latitude", 1.0, "longitude", 2.0))) + ) + ); + args.put( + "destination", + Map.ofEntries( + entry("location", Map.of("coordinate", Map.of("latitude", 2.0, "longitude", 1.0))) + ) + ); + + Graph graph = new Graph(); + var transitModel = new TransitModel(); + transitModel.initTimeZone(ZoneIds.BERLIN); + final DefaultTransitService transitService = new DefaultTransitService(transitModel); + context = + new GraphQLRequestContext( + new TestRoutingService(List.of()), + transitService, + new DefaultFareService(), + graph.getVehicleParkingService(), + new DefaultVehicleRentalService(), + new DefaultRealtimeVehicleService(transitService), + GraphFinder.getInstance(graph, transitService::findRegularStops), + new RouteRequest() + ); + } + + @Test + void testBasicBikePreferences() { + var bicycleArgs = createArgsCopy(args); + var reluctance = 7.5; + var speed = 15d; + var boardCost = Cost.costOfSeconds(50); + bicycleArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries( + entry("reluctance", reluctance), + entry("speed", speed), + entry("boardCost", boardCost) + ) + ) + ) + ) + ) + ); + var env = executionContext(bicycleArgs, Locale.ENGLISH, context); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var bikePreferences = routeRequest.preferences().bike(); + assertEquals(reluctance, bikePreferences.reluctance()); + assertEquals(speed, bikePreferences.speed()); + assertEquals(boardCost.toSeconds(), bikePreferences.boardCost()); + } + + @Test + void testBikeWalkPreferences() { + var bicycleArgs = createArgsCopy(args); + var walkSpeed = 7d; + var mountDismountTime = Duration.ofSeconds(23); + var mountDismountCost = Cost.costOfSeconds(35); + var walkReluctance = 6.3; + bicycleArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries( + entry( + "walk", + Map.ofEntries( + entry("speed", walkSpeed), + entry("mountDismountTime", mountDismountTime), + entry( + "cost", + Map.ofEntries( + entry("mountDismountCost", mountDismountCost), + entry("reluctance", walkReluctance) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ); + var env = executionContext(bicycleArgs, Locale.ENGLISH, context); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var bikeWalkingPreferences = routeRequest.preferences().bike().walking(); + assertEquals(walkSpeed, bikeWalkingPreferences.speed()); + assertEquals(mountDismountTime, bikeWalkingPreferences.mountDismountTime()); + assertEquals(mountDismountCost, bikeWalkingPreferences.mountDismountCost()); + assertEquals(walkReluctance, bikeWalkingPreferences.reluctance()); + } + + @Test + void testBikeTrianglePreferences() { + var bicycleArgs = createArgsCopy(args); + var bikeSafety = 0.3; + var bikeFlatness = 0.5; + var bikeTime = 0.2; + bicycleArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries( + entry( + "optimization", + Map.ofEntries( + entry( + "triangle", + Map.ofEntries( + entry("safety", bikeSafety), + entry("flatness", bikeFlatness), + entry("time", bikeTime) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ); + var env = executionContext(bicycleArgs, Locale.ENGLISH, context); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var bikePreferences = routeRequest.preferences().bike(); + assertEquals(VehicleRoutingOptimizeType.TRIANGLE, bikePreferences.optimizeType()); + var bikeTrianglePreferences = bikePreferences.optimizeTriangle(); + assertEquals(bikeSafety, bikeTrianglePreferences.safety()); + assertEquals(bikeFlatness, bikeTrianglePreferences.slope()); + assertEquals(bikeTime, bikeTrianglePreferences.time()); + } + + @Test + void testBikeOptimizationPreferences() { + var bicycleArgs = createArgsCopy(args); + bicycleArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries(entry("optimization", Map.ofEntries(entry("type", "SAFEST_STREETS")))) + ) + ) + ) + ) + ); + var env = executionContext(bicycleArgs, Locale.ENGLISH, context); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var bikePreferences = routeRequest.preferences().bike(); + assertEquals(VehicleRoutingOptimizeType.SAFEST_STREETS, bikePreferences.optimizeType()); + } + + @Test + void testBikeRentalPreferences() { + var bicycleArgs = createArgsCopy(args); + var allowed = Set.of("foo", "bar"); + var banned = Set.of("not"); + var allowKeeping = true; + var keepingCost = Cost.costOfSeconds(150); + bicycleArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries( + entry( + "rental", + Map.ofEntries( + entry("allowedNetworks", allowed.stream().toList()), + entry("bannedNetworks", banned.stream().toList()), + entry( + "destinationBicyclePolicy", + Map.ofEntries( + entry("allowKeeping", allowKeeping), + entry("keepingCost", keepingCost) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ); + var env = executionContext(bicycleArgs, Locale.ENGLISH, context); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var bikeRentalPreferences = routeRequest.preferences().bike().rental(); + assertEquals(allowed, bikeRentalPreferences.allowedNetworks()); + assertEquals(banned, bikeRentalPreferences.bannedNetworks()); + assertEquals(allowKeeping, bikeRentalPreferences.allowArrivingInRentedVehicleAtDestination()); + assertEquals(keepingCost, bikeRentalPreferences.arrivingInRentalVehicleAtDestinationCost()); + } + + @Test + void testBikeParkingPreferences() { + var bicycleArgs = createArgsCopy(args); + var unpreferredCost = Cost.costOfSeconds(150); + var notFilter = List.of("wheelbender"); + var selectFilter = List.of("locker", "roof"); + var unpreferred = List.of("bad"); + var preferred = List.of("a", "b"); + bicycleArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries( + entry( + "parking", + Map.ofEntries( + entry("unpreferredCost", unpreferredCost), + entry( + "filters", + List.of( + Map.ofEntries( + entry("not", List.of(Map.of("tags", notFilter))), + entry("select", List.of(Map.of("tags", selectFilter))) + ) + ) + ), + entry( + "preferred", + List.of( + Map.ofEntries( + entry("not", List.of(Map.of("tags", unpreferred))), + entry("select", List.of(Map.of("tags", preferred))) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ); + var env = executionContext(bicycleArgs, Locale.ENGLISH, context); + var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var bikeParkingPreferences = routeRequest.preferences().bike().parking(); + assertEquals(unpreferredCost, bikeParkingPreferences.unpreferredVehicleParkingTagCost()); + assertEquals( + "VehicleParkingFilter{not: [tags=%s], select: [tags=%s]}".formatted(notFilter, selectFilter), + bikeParkingPreferences.filter().toString() + ); + assertEquals( + "VehicleParkingFilter{not: [tags=%s], select: [tags=%s]}".formatted(unpreferred, preferred), + bikeParkingPreferences.preferred().toString() + ); + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index 740ec2bc373..e8b38eb217b 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -20,19 +20,16 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Coordinate; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.apis.gtfs.GraphQLRequestContext; import org.opentripplanner.apis.gtfs.TestRoutingService; import org.opentripplanner.ext.fares.impl.DefaultFareService; -import org.opentripplanner.framework.model.Cost; import org.opentripplanner.model.plan.paging.cursor.PageCursor; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile; -import org.opentripplanner.routing.core.VehicleRoutingOptimizeType; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graphfinder.GraphFinder; import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; @@ -86,7 +83,7 @@ class RouteRequestMapperTest { @Test void testMinimalArgs() { - var env = executionContext(args); + var env = executionContext(args, LOCALE, context); var defaultRequest = new RouteRequest(); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); assertEquals(ORIGIN.x, routeRequest.from().lat); @@ -118,10 +115,10 @@ void testMinimalArgs() { @Test void testEarliestDeparture() { - var dateTimeArgs = createArgsCopy(); + var dateTimeArgs = createArgsCopy(args); var dateTime = OffsetDateTime.of(LocalDate.of(2020, 3, 15), LocalTime.MIDNIGHT, ZoneOffset.UTC); dateTimeArgs.put("dateTime", Map.ofEntries(entry("earliestDeparture", dateTime))); - var env = executionContext(dateTimeArgs); + var env = executionContext(dateTimeArgs, LOCALE, context); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); assertEquals(dateTime.toInstant(), routeRequest.dateTime()); assertFalse(routeRequest.arriveBy()); @@ -130,10 +127,10 @@ void testEarliestDeparture() { @Test void testLatestArrival() { - var dateTimeArgs = createArgsCopy(); + var dateTimeArgs = createArgsCopy(args); var dateTime = OffsetDateTime.of(LocalDate.of(2020, 3, 15), LocalTime.MIDNIGHT, ZoneOffset.UTC); dateTimeArgs.put("dateTime", Map.ofEntries(entry("latestArrival", dateTime))); - var env = executionContext(dateTimeArgs); + var env = executionContext(dateTimeArgs, LOCALE, context); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); assertEquals(dateTime.toInstant(), routeRequest.dateTime()); assertTrue(routeRequest.arriveBy()); @@ -161,7 +158,7 @@ void testStopLocationAndLabel() { entry("label", destinationLabel) ) ); - var env = executionContext(stopLocationArgs); + var env = executionContext(stopLocationArgs, LOCALE, context); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); assertEquals(FeedScopedId.parse(stopA), routeRequest.from().stopId); assertEquals(originLabel, routeRequest.from().label); @@ -172,9 +169,9 @@ void testStopLocationAndLabel() { @Test void testLocale() { var englishLocale = Locale.ENGLISH; - var localeArgs = createArgsCopy(); + var localeArgs = createArgsCopy(args); localeArgs.put("locale", englishLocale); - var env = executionContext(localeArgs); + var env = executionContext(localeArgs, LOCALE, context); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); assertEquals(englishLocale, routeRequest.locale()); } @@ -182,9 +179,9 @@ void testLocale() { @Test void testSearchWindow() { var searchWindow = Duration.ofHours(5); - var searchWindowArgs = createArgsCopy(); + var searchWindowArgs = createArgsCopy(args); searchWindowArgs.put("searchWindow", searchWindow); - var env = executionContext(searchWindowArgs); + var env = executionContext(searchWindowArgs, LOCALE, context); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); assertEquals(searchWindow, routeRequest.searchWindow()); } @@ -195,12 +192,12 @@ void testBefore() { "MXxQUkVWSU9VU19QQUdFfDIwMjQtMDMtMTVUMTM6MzU6MzlafHw0MG18U1RSRUVUX0FORF9BUlJJVkFMX1RJTUV8ZmFsc2V8MjAyNC0wMy0xNVQxNDoyODoxNFp8MjAyNC0wMy0xNVQxNToxNDoyMlp8MXw0MjUzfA==" ); var last = 8; - var beforeArgs = createArgsCopy(); + var beforeArgs = createArgsCopy(args); beforeArgs.put("before", before.encode()); beforeArgs.put("first", 3); beforeArgs.put("last", last); beforeArgs.put("numberOfItineraries", 3); - var env = executionContext(beforeArgs); + var env = executionContext(beforeArgs, LOCALE, context); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); assertEquals(before, routeRequest.pageCursor()); assertEquals(last, routeRequest.numItineraries()); @@ -212,12 +209,12 @@ void testAfter() { "MXxORVhUX1BBR0V8MjAyNC0wMy0xNVQxNDo0MzoxNFp8fDQwbXxTVFJFRVRfQU5EX0FSUklWQUxfVElNRXxmYWxzZXwyMDI0LTAzLTE1VDE0OjI4OjE0WnwyMDI0LTAzLTE1VDE1OjE0OjIyWnwxfDQyNTN8" ); var first = 8; - var afterArgs = createArgsCopy(); + var afterArgs = createArgsCopy(args); afterArgs.put("after", after.encode()); afterArgs.put("first", first); afterArgs.put("last", 3); afterArgs.put("numberOfItineraries", 3); - var env = executionContext(afterArgs); + var env = executionContext(afterArgs, LOCALE, context); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); assertEquals(after, routeRequest.pageCursor()); assertEquals(first, routeRequest.numItineraries()); @@ -226,34 +223,34 @@ void testAfter() { @Test void testNumberOfItineraries() { var itineraries = 8; - var itinArgs = createArgsCopy(); + var itinArgs = createArgsCopy(args); itinArgs.put("numberOfItineraries", itineraries); - var env = executionContext(itinArgs); + var env = executionContext(itinArgs, LOCALE, context); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); assertEquals(itineraries, routeRequest.numItineraries()); } @Test void testDirectOnly() { - var modesArgs = createArgsCopy(); + var modesArgs = createArgsCopy(args); modesArgs.put("modes", Map.ofEntries(entry("directOnly", true))); - var env = executionContext(modesArgs); + var env = executionContext(modesArgs, LOCALE, context); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); assertFalse(routeRequest.journey().transit().enabled()); } @Test void testTransitOnly() { - var modesArgs = createArgsCopy(); + var modesArgs = createArgsCopy(args); modesArgs.put("modes", Map.ofEntries(entry("transitOnly", true))); - var env = executionContext(modesArgs); + var env = executionContext(modesArgs, LOCALE, context); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); assertEquals(StreetMode.NOT_SET, routeRequest.journey().direct().mode()); } @Test void testStreetModes() { - var modesArgs = createArgsCopy(); + var modesArgs = createArgsCopy(args); var bicycle = List.of("BICYCLE"); modesArgs.put( "modes", @@ -269,7 +266,7 @@ void testStreetModes() { ) ) ); - var env = executionContext(modesArgs); + var env = executionContext(modesArgs, LOCALE, context); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); assertEquals(StreetMode.CAR, routeRequest.journey().direct().mode()); assertEquals(StreetMode.BIKE, routeRequest.journey().access().mode()); @@ -279,7 +276,7 @@ void testStreetModes() { @Test void testTransitModes() { - var modesArgs = createArgsCopy(); + var modesArgs = createArgsCopy(args); var tramCost = 1.5; modesArgs.put( "modes", @@ -301,7 +298,7 @@ void testTransitModes() { ) ) ); - var env = executionContext(modesArgs); + var env = executionContext(modesArgs, LOCALE, context); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); var reluctanceForMode = routeRequest.preferences().transit().reluctanceForMode(); assertEquals(tramCost, reluctanceForMode.get(TransitMode.TRAM)); @@ -314,7 +311,7 @@ void testTransitModes() { @Test void testItineraryFilters() { - var filterArgs = createArgsCopy(); + var filterArgs = createArgsCopy(args); var profile = ItineraryFilterDebugProfile.LIMIT_TO_NUM_OF_ITINERARIES; var keepOne = 0.4; var keepThree = 0.6; @@ -328,7 +325,7 @@ void testItineraryFilters() { entry("groupedOtherThanSameLegsMaxCostMultiplier", multiplier) ) ); - var env = executionContext(filterArgs); + var env = executionContext(filterArgs, LOCALE, context); var routeRequest = RouteRequestMapper.toRouteRequest(env, context); var itinFilter = routeRequest.preferences().itineraryFilter(); assertEquals(profile, itinFilter.debug()); @@ -337,279 +334,33 @@ void testItineraryFilters() { assertEquals(multiplier, itinFilter.groupedOtherThanSameLegsMaxCostMultiplier()); } - @Test - void testBasicBikePreferences() { - var bicycleArgs = createArgsCopy(); - var reluctance = 7.5; - var speed = 15d; - var boardCost = Cost.costOfSeconds(50); - bicycleArgs.put( - "preferences", - Map.ofEntries( - entry( - "street", - Map.ofEntries( - entry( - "bicycle", - Map.ofEntries( - entry("reluctance", reluctance), - entry("speed", speed), - entry("boardCost", boardCost) - ) - ) - ) - ) - ) - ); - var env = executionContext(bicycleArgs); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); - var bikePreferences = routeRequest.preferences().bike(); - assertEquals(reluctance, bikePreferences.reluctance()); - assertEquals(speed, bikePreferences.speed()); - assertEquals(boardCost.toSeconds(), bikePreferences.boardCost()); - } - - @Test - void testBikeWalkPreferences() { - var bicycleArgs = createArgsCopy(); - var walkSpeed = 7d; - var mountDismountTime = Duration.ofSeconds(23); - var mountDismountCost = Cost.costOfSeconds(35); - var walkReluctance = 6.3; - bicycleArgs.put( - "preferences", - Map.ofEntries( - entry( - "street", - Map.ofEntries( - entry( - "bicycle", - Map.ofEntries( - entry( - "walk", - Map.ofEntries( - entry("speed", walkSpeed), - entry("mountDismountTime", mountDismountTime), - entry( - "cost", - Map.ofEntries( - entry("mountDismountCost", mountDismountCost), - entry("reluctance", walkReluctance) - ) - ) - ) - ) - ) - ) - ) - ) - ) - ); - var env = executionContext(bicycleArgs); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); - var bikeWalkingPreferences = routeRequest.preferences().bike().walking(); - assertEquals(walkSpeed, bikeWalkingPreferences.speed()); - assertEquals(mountDismountTime, bikeWalkingPreferences.mountDismountTime()); - assertEquals(mountDismountCost, bikeWalkingPreferences.mountDismountCost()); - assertEquals(walkReluctance, bikeWalkingPreferences.reluctance()); - } - - @Test - void testBikeTrianglePreferences() { - var bicycleArgs = createArgsCopy(); - var bikeSafety = 0.3; - var bikeFlatness = 0.5; - var bikeTime = 0.2; - bicycleArgs.put( - "preferences", - Map.ofEntries( - entry( - "street", - Map.ofEntries( - entry( - "bicycle", - Map.ofEntries( - entry( - "optimization", - Map.ofEntries( - entry( - "triangle", - Map.ofEntries( - entry("safety", bikeSafety), - entry("flatness", bikeFlatness), - entry("time", bikeTime) - ) - ) - ) - ) - ) - ) - ) - ) - ) - ); - var env = executionContext(bicycleArgs); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); - var bikePreferences = routeRequest.preferences().bike(); - assertEquals(VehicleRoutingOptimizeType.TRIANGLE, bikePreferences.optimizeType()); - var bikeTrianglePreferences = bikePreferences.optimizeTriangle(); - assertEquals(bikeSafety, bikeTrianglePreferences.safety()); - assertEquals(bikeFlatness, bikeTrianglePreferences.slope()); - assertEquals(bikeTime, bikeTrianglePreferences.time()); - } - - @Test - void testBikeOptimizationPreferences() { - var bicycleArgs = createArgsCopy(); - bicycleArgs.put( - "preferences", - Map.ofEntries( - entry( - "street", - Map.ofEntries( - entry( - "bicycle", - Map.ofEntries(entry("optimization", Map.ofEntries(entry("type", "SAFEST_STREETS")))) - ) - ) - ) - ) - ); - var env = executionContext(bicycleArgs); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); - var bikePreferences = routeRequest.preferences().bike(); - assertEquals(VehicleRoutingOptimizeType.SAFEST_STREETS, bikePreferences.optimizeType()); - } - - @Test - void testBikeRentalPreferences() { - var bicycleArgs = createArgsCopy(); - var allowed = Set.of("foo", "bar"); - var banned = Set.of("not"); - var allowKeeping = true; - var keepingCost = Cost.costOfSeconds(150); - bicycleArgs.put( - "preferences", - Map.ofEntries( - entry( - "street", - Map.ofEntries( - entry( - "bicycle", - Map.ofEntries( - entry( - "rental", - Map.ofEntries( - entry("allowedNetworks", allowed.stream().toList()), - entry("bannedNetworks", banned.stream().toList()), - entry( - "destinationBicyclePolicy", - Map.ofEntries( - entry("allowKeeping", allowKeeping), - entry("keepingCost", keepingCost) - ) - ) - ) - ) - ) - ) - ) - ) - ) - ); - var env = executionContext(bicycleArgs); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); - var bikeRentalPreferences = routeRequest.preferences().bike().rental(); - assertEquals(allowed, bikeRentalPreferences.allowedNetworks()); - assertEquals(banned, bikeRentalPreferences.bannedNetworks()); - assertEquals(allowKeeping, bikeRentalPreferences.allowArrivingInRentedVehicleAtDestination()); - assertEquals(keepingCost, bikeRentalPreferences.arrivingInRentalVehicleAtDestinationCost()); - } - - @Test - void testBikeParkingPreferences() { - var bicycleArgs = createArgsCopy(); - var unpreferredCost = Cost.costOfSeconds(150); - var notFilter = List.of("wheelbender"); - var selectFilter = List.of("locker", "roof"); - var unpreferred = List.of("bad"); - var preferred = List.of("a", "b"); - bicycleArgs.put( - "preferences", - Map.ofEntries( - entry( - "street", - Map.ofEntries( - entry( - "bicycle", - Map.ofEntries( - entry( - "parking", - Map.ofEntries( - entry("unpreferredCost", unpreferredCost), - entry( - "filters", - List.of( - Map.ofEntries( - entry("not", List.of(Map.of("tags", notFilter))), - entry("select", List.of(Map.of("tags", selectFilter))) - ) - ) - ), - entry( - "preferred", - List.of( - Map.ofEntries( - entry("not", List.of(Map.of("tags", unpreferred))), - entry("select", List.of(Map.of("tags", preferred))) - ) - ) - ) - ) - ) - ) - ) - ) - ) - ) - ); - var env = executionContext(bicycleArgs); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); - var bikeParkingPreferences = routeRequest.preferences().bike().parking(); - assertEquals(unpreferredCost, bikeParkingPreferences.unpreferredVehicleParkingTagCost()); - assertEquals( - "VehicleParkingFilter{not: [tags=%s], select: [tags=%s]}".formatted(notFilter, selectFilter), - bikeParkingPreferences.filter().toString() - ); - assertEquals( - "VehicleParkingFilter{not: [tags=%s], select: [tags=%s]}".formatted(unpreferred, preferred), - bikeParkingPreferences.preferred().toString() - ); - } - - private Map createArgsCopy() { + static Map createArgsCopy(Map arguments) { Map newArgs = new HashMap<>(); - newArgs.putAll(args); + newArgs.putAll(arguments); return newArgs; } - private DataFetchingEnvironment executionContext(Map arguments) { + static DataFetchingEnvironment executionContext( + Map arguments, + Locale locale, + GraphQLRequestContext requestContext + ) { ExecutionInput executionInput = ExecutionInput .newExecutionInput() .query("") .operationName("planConnection") - .context(context) - .locale(LOCALE) + .context(requestContext) + .locale(locale) .build(); var executionContext = newExecutionContextBuilder() .executionInput(executionInput) - .executionId(ExecutionId.from(this.getClass().getName())) + .executionId(ExecutionId.from("planConnectionTest")) .build(); return DataFetchingEnvironmentImpl .newDataFetchingEnvironment(executionContext) .arguments(arguments) - .localContext(Map.of("locale", LOCALE)) + .localContext(Map.of("locale", locale)) .build(); } } From 38a2ffffe307955b4708ccd0693222832c11a708 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sat, 23 Mar 2024 22:07:16 +0200 Subject: [PATCH 145/165] Remove duplication --- .../RouteRequestMapperBicycleTest.java | 82 ++++------------ .../routerequest/RouteRequestMapperTest.java | 98 +++++++++---------- 2 files changed, 67 insertions(+), 113 deletions(-) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperBicycleTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperBicycleTest.java index 50f04f4d2a2..75c68c2a91a 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperBicycleTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperBicycleTest.java @@ -6,65 +6,19 @@ import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.executionContext; import java.time.Duration; -import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.junit.jupiter.api.Test; -import org.opentripplanner._support.time.ZoneIds; -import org.opentripplanner.apis.gtfs.GraphQLRequestContext; -import org.opentripplanner.apis.gtfs.TestRoutingService; -import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.framework.model.Cost; -import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.core.VehicleRoutingOptimizeType; -import org.opentripplanner.routing.graph.Graph; -import org.opentripplanner.routing.graphfinder.GraphFinder; -import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; -import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; -import org.opentripplanner.transit.service.DefaultTransitService; -import org.opentripplanner.transit.service.TransitModel; class RouteRequestMapperBicycleTest { - private static final GraphQLRequestContext context; - private static final Map args = new HashMap<>(); - - static { - args.put( - "origin", - Map.ofEntries( - entry("location", Map.of("coordinate", Map.of("latitude", 1.0, "longitude", 2.0))) - ) - ); - args.put( - "destination", - Map.ofEntries( - entry("location", Map.of("coordinate", Map.of("latitude", 2.0, "longitude", 1.0))) - ) - ); - - Graph graph = new Graph(); - var transitModel = new TransitModel(); - transitModel.initTimeZone(ZoneIds.BERLIN); - final DefaultTransitService transitService = new DefaultTransitService(transitModel); - context = - new GraphQLRequestContext( - new TestRoutingService(List.of()), - transitService, - new DefaultFareService(), - graph.getVehicleParkingService(), - new DefaultVehicleRentalService(), - new DefaultRealtimeVehicleService(transitService), - GraphFinder.getInstance(graph, transitService::findRegularStops), - new RouteRequest() - ); - } - @Test void testBasicBikePreferences() { - var bicycleArgs = createArgsCopy(args); + var bicycleArgs = createArgsCopy(RouteRequestMapperTest.ARGS); var reluctance = 7.5; var speed = 15d; var boardCost = Cost.costOfSeconds(50); @@ -86,8 +40,8 @@ void testBasicBikePreferences() { ) ) ); - var env = executionContext(bicycleArgs, Locale.ENGLISH, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(bicycleArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); var bikePreferences = routeRequest.preferences().bike(); assertEquals(reluctance, bikePreferences.reluctance()); assertEquals(speed, bikePreferences.speed()); @@ -96,7 +50,7 @@ void testBasicBikePreferences() { @Test void testBikeWalkPreferences() { - var bicycleArgs = createArgsCopy(args); + var bicycleArgs = createArgsCopy(RouteRequestMapperTest.ARGS); var walkSpeed = 7d; var mountDismountTime = Duration.ofSeconds(23); var mountDismountCost = Cost.costOfSeconds(35); @@ -130,8 +84,8 @@ void testBikeWalkPreferences() { ) ) ); - var env = executionContext(bicycleArgs, Locale.ENGLISH, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(bicycleArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); var bikeWalkingPreferences = routeRequest.preferences().bike().walking(); assertEquals(walkSpeed, bikeWalkingPreferences.speed()); assertEquals(mountDismountTime, bikeWalkingPreferences.mountDismountTime()); @@ -141,7 +95,7 @@ void testBikeWalkPreferences() { @Test void testBikeTrianglePreferences() { - var bicycleArgs = createArgsCopy(args); + var bicycleArgs = createArgsCopy(RouteRequestMapperTest.ARGS); var bikeSafety = 0.3; var bikeFlatness = 0.5; var bikeTime = 0.2; @@ -173,8 +127,8 @@ void testBikeTrianglePreferences() { ) ) ); - var env = executionContext(bicycleArgs, Locale.ENGLISH, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(bicycleArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); var bikePreferences = routeRequest.preferences().bike(); assertEquals(VehicleRoutingOptimizeType.TRIANGLE, bikePreferences.optimizeType()); var bikeTrianglePreferences = bikePreferences.optimizeTriangle(); @@ -185,7 +139,7 @@ void testBikeTrianglePreferences() { @Test void testBikeOptimizationPreferences() { - var bicycleArgs = createArgsCopy(args); + var bicycleArgs = createArgsCopy(RouteRequestMapperTest.ARGS); bicycleArgs.put( "preferences", Map.ofEntries( @@ -200,15 +154,15 @@ void testBikeOptimizationPreferences() { ) ) ); - var env = executionContext(bicycleArgs, Locale.ENGLISH, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(bicycleArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); var bikePreferences = routeRequest.preferences().bike(); assertEquals(VehicleRoutingOptimizeType.SAFEST_STREETS, bikePreferences.optimizeType()); } @Test void testBikeRentalPreferences() { - var bicycleArgs = createArgsCopy(args); + var bicycleArgs = createArgsCopy(RouteRequestMapperTest.ARGS); var allowed = Set.of("foo", "bar"); var banned = Set.of("not"); var allowKeeping = true; @@ -242,8 +196,8 @@ void testBikeRentalPreferences() { ) ) ); - var env = executionContext(bicycleArgs, Locale.ENGLISH, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(bicycleArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); var bikeRentalPreferences = routeRequest.preferences().bike().rental(); assertEquals(allowed, bikeRentalPreferences.allowedNetworks()); assertEquals(banned, bikeRentalPreferences.bannedNetworks()); @@ -253,7 +207,7 @@ void testBikeRentalPreferences() { @Test void testBikeParkingPreferences() { - var bicycleArgs = createArgsCopy(args); + var bicycleArgs = createArgsCopy(RouteRequestMapperTest.ARGS); var unpreferredCost = Cost.costOfSeconds(150); var notFilter = List.of("wheelbender"); var selectFilter = List.of("locker", "roof"); @@ -298,8 +252,8 @@ void testBikeParkingPreferences() { ) ) ); - var env = executionContext(bicycleArgs, Locale.ENGLISH, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(bicycleArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); var bikeParkingPreferences = routeRequest.preferences().bike().parking(); assertEquals(unpreferredCost, bikeParkingPreferences.unpreferredVehicleParkingTagCost()); assertEquals( diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index e8b38eb217b..d1cc96fdea0 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -44,17 +44,15 @@ class RouteRequestMapperTest { private static final Coordinate ORIGIN = new Coordinate(1.0, 2.0); private static final Coordinate DESTINATION = new Coordinate(2.0, 1.0); private static final Locale LOCALE = Locale.GERMAN; - private static final GraphQLRequestContext context; - private static final Map args = new HashMap<>(); - - static { - args.put( + static final GraphQLRequestContext CONTEXT; + static final Map ARGS = Map.ofEntries( + entry( "origin", Map.ofEntries( entry("location", Map.of("coordinate", Map.of("latitude", ORIGIN.x, "longitude", ORIGIN.y))) ) - ); - args.put( + ), + entry( "destination", Map.ofEntries( entry( @@ -62,13 +60,15 @@ class RouteRequestMapperTest { Map.of("coordinate", Map.of("latitude", DESTINATION.x, "longitude", DESTINATION.y)) ) ) - ); + ) + ); + static { Graph graph = new Graph(); var transitModel = new TransitModel(); transitModel.initTimeZone(ZoneIds.BERLIN); final DefaultTransitService transitService = new DefaultTransitService(transitModel); - context = + CONTEXT = new GraphQLRequestContext( new TestRoutingService(List.of()), transitService, @@ -83,9 +83,9 @@ class RouteRequestMapperTest { @Test void testMinimalArgs() { - var env = executionContext(args, LOCALE, context); + var env = executionContext(ARGS, LOCALE, CONTEXT); var defaultRequest = new RouteRequest(); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); assertEquals(ORIGIN.x, routeRequest.from().lat); assertEquals(ORIGIN.y, routeRequest.from().lng); assertEquals(DESTINATION.x, routeRequest.to().lat); @@ -115,11 +115,11 @@ void testMinimalArgs() { @Test void testEarliestDeparture() { - var dateTimeArgs = createArgsCopy(args); + var dateTimeArgs = createArgsCopy(ARGS); var dateTime = OffsetDateTime.of(LocalDate.of(2020, 3, 15), LocalTime.MIDNIGHT, ZoneOffset.UTC); dateTimeArgs.put("dateTime", Map.ofEntries(entry("earliestDeparture", dateTime))); - var env = executionContext(dateTimeArgs, LOCALE, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(dateTimeArgs, LOCALE, CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); assertEquals(dateTime.toInstant(), routeRequest.dateTime()); assertFalse(routeRequest.arriveBy()); assertFalse(routeRequest.isTripPlannedForNow()); @@ -127,11 +127,11 @@ void testEarliestDeparture() { @Test void testLatestArrival() { - var dateTimeArgs = createArgsCopy(args); + var dateTimeArgs = createArgsCopy(ARGS); var dateTime = OffsetDateTime.of(LocalDate.of(2020, 3, 15), LocalTime.MIDNIGHT, ZoneOffset.UTC); dateTimeArgs.put("dateTime", Map.ofEntries(entry("latestArrival", dateTime))); - var env = executionContext(dateTimeArgs, LOCALE, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(dateTimeArgs, LOCALE, CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); assertEquals(dateTime.toInstant(), routeRequest.dateTime()); assertTrue(routeRequest.arriveBy()); assertFalse(routeRequest.isTripPlannedForNow()); @@ -158,8 +158,8 @@ void testStopLocationAndLabel() { entry("label", destinationLabel) ) ); - var env = executionContext(stopLocationArgs, LOCALE, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(stopLocationArgs, LOCALE, CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); assertEquals(FeedScopedId.parse(stopA), routeRequest.from().stopId); assertEquals(originLabel, routeRequest.from().label); assertEquals(FeedScopedId.parse(stopB), routeRequest.to().stopId); @@ -169,20 +169,20 @@ void testStopLocationAndLabel() { @Test void testLocale() { var englishLocale = Locale.ENGLISH; - var localeArgs = createArgsCopy(args); + var localeArgs = createArgsCopy(ARGS); localeArgs.put("locale", englishLocale); - var env = executionContext(localeArgs, LOCALE, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(localeArgs, LOCALE, CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); assertEquals(englishLocale, routeRequest.locale()); } @Test void testSearchWindow() { var searchWindow = Duration.ofHours(5); - var searchWindowArgs = createArgsCopy(args); + var searchWindowArgs = createArgsCopy(ARGS); searchWindowArgs.put("searchWindow", searchWindow); - var env = executionContext(searchWindowArgs, LOCALE, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(searchWindowArgs, LOCALE, CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); assertEquals(searchWindow, routeRequest.searchWindow()); } @@ -192,13 +192,13 @@ void testBefore() { "MXxQUkVWSU9VU19QQUdFfDIwMjQtMDMtMTVUMTM6MzU6MzlafHw0MG18U1RSRUVUX0FORF9BUlJJVkFMX1RJTUV8ZmFsc2V8MjAyNC0wMy0xNVQxNDoyODoxNFp8MjAyNC0wMy0xNVQxNToxNDoyMlp8MXw0MjUzfA==" ); var last = 8; - var beforeArgs = createArgsCopy(args); + var beforeArgs = createArgsCopy(ARGS); beforeArgs.put("before", before.encode()); beforeArgs.put("first", 3); beforeArgs.put("last", last); beforeArgs.put("numberOfItineraries", 3); - var env = executionContext(beforeArgs, LOCALE, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(beforeArgs, LOCALE, CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); assertEquals(before, routeRequest.pageCursor()); assertEquals(last, routeRequest.numItineraries()); } @@ -209,13 +209,13 @@ void testAfter() { "MXxORVhUX1BBR0V8MjAyNC0wMy0xNVQxNDo0MzoxNFp8fDQwbXxTVFJFRVRfQU5EX0FSUklWQUxfVElNRXxmYWxzZXwyMDI0LTAzLTE1VDE0OjI4OjE0WnwyMDI0LTAzLTE1VDE1OjE0OjIyWnwxfDQyNTN8" ); var first = 8; - var afterArgs = createArgsCopy(args); + var afterArgs = createArgsCopy(ARGS); afterArgs.put("after", after.encode()); afterArgs.put("first", first); afterArgs.put("last", 3); afterArgs.put("numberOfItineraries", 3); - var env = executionContext(afterArgs, LOCALE, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(afterArgs, LOCALE, CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); assertEquals(after, routeRequest.pageCursor()); assertEquals(first, routeRequest.numItineraries()); } @@ -223,34 +223,34 @@ void testAfter() { @Test void testNumberOfItineraries() { var itineraries = 8; - var itinArgs = createArgsCopy(args); + var itinArgs = createArgsCopy(ARGS); itinArgs.put("numberOfItineraries", itineraries); - var env = executionContext(itinArgs, LOCALE, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(itinArgs, LOCALE, CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); assertEquals(itineraries, routeRequest.numItineraries()); } @Test void testDirectOnly() { - var modesArgs = createArgsCopy(args); + var modesArgs = createArgsCopy(ARGS); modesArgs.put("modes", Map.ofEntries(entry("directOnly", true))); - var env = executionContext(modesArgs, LOCALE, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(modesArgs, LOCALE, CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); assertFalse(routeRequest.journey().transit().enabled()); } @Test void testTransitOnly() { - var modesArgs = createArgsCopy(args); + var modesArgs = createArgsCopy(ARGS); modesArgs.put("modes", Map.ofEntries(entry("transitOnly", true))); - var env = executionContext(modesArgs, LOCALE, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(modesArgs, LOCALE, CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); assertEquals(StreetMode.NOT_SET, routeRequest.journey().direct().mode()); } @Test void testStreetModes() { - var modesArgs = createArgsCopy(args); + var modesArgs = createArgsCopy(ARGS); var bicycle = List.of("BICYCLE"); modesArgs.put( "modes", @@ -266,8 +266,8 @@ void testStreetModes() { ) ) ); - var env = executionContext(modesArgs, LOCALE, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(modesArgs, LOCALE, CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); assertEquals(StreetMode.CAR, routeRequest.journey().direct().mode()); assertEquals(StreetMode.BIKE, routeRequest.journey().access().mode()); assertEquals(StreetMode.BIKE, routeRequest.journey().egress().mode()); @@ -276,7 +276,7 @@ void testStreetModes() { @Test void testTransitModes() { - var modesArgs = createArgsCopy(args); + var modesArgs = createArgsCopy(ARGS); var tramCost = 1.5; modesArgs.put( "modes", @@ -298,8 +298,8 @@ void testTransitModes() { ) ) ); - var env = executionContext(modesArgs, LOCALE, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(modesArgs, LOCALE, CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); var reluctanceForMode = routeRequest.preferences().transit().reluctanceForMode(); assertEquals(tramCost, reluctanceForMode.get(TransitMode.TRAM)); assertNull(reluctanceForMode.get(TransitMode.FERRY)); @@ -311,7 +311,7 @@ void testTransitModes() { @Test void testItineraryFilters() { - var filterArgs = createArgsCopy(args); + var filterArgs = createArgsCopy(ARGS); var profile = ItineraryFilterDebugProfile.LIMIT_TO_NUM_OF_ITINERARIES; var keepOne = 0.4; var keepThree = 0.6; @@ -325,8 +325,8 @@ void testItineraryFilters() { entry("groupedOtherThanSameLegsMaxCostMultiplier", multiplier) ) ); - var env = executionContext(filterArgs, LOCALE, context); - var routeRequest = RouteRequestMapper.toRouteRequest(env, context); + var env = executionContext(filterArgs, LOCALE, CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); var itinFilter = routeRequest.preferences().itineraryFilter(); assertEquals(profile, itinFilter.debug()); assertEquals(keepOne, itinFilter.groupSimilarityKeepOne()); From 7b4fcf202fd26c0dbd48d4cc99ed09105dc1da44 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sat, 23 Mar 2024 22:19:58 +0200 Subject: [PATCH 146/165] Fix allowedNetworks for scooter and car as well --- .../apis/gtfs/mapping/routerequest/CarPreferencesMapper.java | 2 +- .../gtfs/mapping/routerequest/ScooterPreferencesMapper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java index b815144c1d0..b0ba8e9776c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java @@ -58,7 +58,7 @@ private static void setCarRentalPreferences( if (args != null) { var allowedNetworks = args.getGraphQLAllowedNetworks(); if (!CollectionUtils.isEmpty(allowedNetworks)) { - preferences.withBannedNetworks(Set.copyOf(allowedNetworks)); + preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); } var bannedNetworks = args.getGraphQLBannedNetworks(); if (!CollectionUtils.isEmpty(bannedNetworks)) { diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java index c6e83d9af0a..f3da4c22463 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java @@ -34,7 +34,7 @@ private static void setScooterRentalPreferences( if (args != null) { var allowedNetworks = args.getGraphQLAllowedNetworks(); if (!CollectionUtils.isEmpty(allowedNetworks)) { - preferences.withBannedNetworks(Set.copyOf(allowedNetworks)); + preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); } var bannedNetworks = args.getGraphQLBannedNetworks(); if (!CollectionUtils.isEmpty(bannedNetworks)) { From c358ba46d302e3032a83b8148c47a7b779e4afcd Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sat, 23 Mar 2024 22:41:13 +0200 Subject: [PATCH 147/165] Rename walkSafetyFactor -> safetyFactor --- .../apis/gtfs/generated/GraphQLTypes.java | 20 +++++++++---------- .../routerequest/WalkPreferencesMapper.java | 2 +- .../opentripplanner/apis/gtfs/schema.graphqls | 2 +- .../apis/gtfs/queries/planConnection.graphql | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 3cb0eb1e5a2..0a65edef4a8 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -4985,15 +4985,15 @@ public static class GraphQLWalkPreferencesInput { private org.opentripplanner.framework.model.Cost boardCost; private Double reluctance; + private Double safetyFactor; private Double speed; - private Double walkSafetyFactor; public GraphQLWalkPreferencesInput(Map args) { if (args != null) { this.boardCost = (org.opentripplanner.framework.model.Cost) args.get("boardCost"); this.reluctance = (Double) args.get("reluctance"); + this.safetyFactor = (Double) args.get("safetyFactor"); this.speed = (Double) args.get("speed"); - this.walkSafetyFactor = (Double) args.get("walkSafetyFactor"); } } @@ -5005,12 +5005,12 @@ public Double getGraphQLReluctance() { return this.reluctance; } - public Double getGraphQLSpeed() { - return this.speed; + public Double getGraphQLSafetyFactor() { + return this.safetyFactor; } - public Double getGraphQLWalkSafetyFactor() { - return this.walkSafetyFactor; + public Double getGraphQLSpeed() { + return this.speed; } public void setGraphQLBoardCost(org.opentripplanner.framework.model.Cost boardCost) { @@ -5021,12 +5021,12 @@ public void setGraphQLReluctance(Double reluctance) { this.reluctance = reluctance; } - public void setGraphQLSpeed(Double speed) { - this.speed = speed; + public void setGraphQLSafetyFactor(Double safetyFactor) { + this.safetyFactor = safetyFactor; } - public void setGraphQLWalkSafetyFactor(Double walkSafetyFactor) { - this.walkSafetyFactor = walkSafetyFactor; + public void setGraphQLSpeed(Double speed) { + this.speed = speed; } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/WalkPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/WalkPreferencesMapper.java index be2d520ec04..b202d9e7300 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/WalkPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/WalkPreferencesMapper.java @@ -18,7 +18,7 @@ static void setWalkPreferences( if (reluctance != null) { preferences.withReluctance(reluctance); } - var walkSafetyFactor = args.getGraphQLWalkSafetyFactor(); + var walkSafetyFactor = args.getGraphQLSafetyFactor(); if (walkSafetyFactor != null) { preferences.withSafetyFactor(walkSafetyFactor); } diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index 1011c98c91b..4d7fdbb78bb 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -5536,7 +5536,7 @@ input WalkPreferencesInput { Factor for how much the walk safety is considered in routing. Value should be between 0 and 1. If the value is set to be 0, safety is ignored. """ - walkSafetyFactor: Ratio + safetyFactor: Ratio """ The cost of boarding a vehicle while walking. diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql index 39691b349f8..aec99442d3b 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql @@ -111,7 +111,7 @@ walk: { speed: 2.4 reluctance: 1.5 - walkSafetyFactor: 0.5 + safetyFactor: 0.5 boardCost: 200 } } From 427a7b2a436a38825cc42c91ff9a713aad220bf7 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sat, 23 Mar 2024 22:43:36 +0200 Subject: [PATCH 148/165] Add tests for other street preferences --- .../RouteRequestMapperCarTest.java | 125 ++++++++++++++ .../RouteRequestMapperScooterTest.java | 154 ++++++++++++++++++ .../RouteRequestMapperWalkTest.java | 49 ++++++ 3 files changed, 328 insertions(+) create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperCarTest.java create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperScooterTest.java create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperWalkTest.java diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperCarTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperCarTest.java new file mode 100644 index 00000000000..0fe8f76c26d --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperCarTest.java @@ -0,0 +1,125 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.createArgsCopy; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.executionContext; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; + +class RouteRequestMapperCarTest { + + @Test + void testBasicCarPreferences() { + var carArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var reluctance = 7.5; + carArgs.put( + "preferences", + Map.ofEntries(entry("street", Map.ofEntries(entry("car", Map.of("reluctance", reluctance))))) + ); + var env = executionContext(carArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + var carPreferences = routeRequest.preferences().car(); + assertEquals(reluctance, carPreferences.reluctance()); + } + + @Test + void testCarRentalPreferences() { + var carArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var allowed = Set.of("foo", "bar"); + var banned = Set.of("not"); + carArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "car", + Map.ofEntries( + entry( + "rental", + Map.ofEntries( + entry("allowedNetworks", allowed.stream().toList()), + entry("bannedNetworks", banned.stream().toList()) + ) + ) + ) + ) + ) + ) + ) + ); + var env = executionContext(carArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + var carRentalPreferences = routeRequest.preferences().car().rental(); + assertEquals(allowed, carRentalPreferences.allowedNetworks()); + assertEquals(banned, carRentalPreferences.bannedNetworks()); + } + + @Test + void testCarParkingPreferences() { + var carArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var unpreferredCost = Cost.costOfSeconds(150); + var notFilter = List.of("wheelbender"); + var selectFilter = List.of("locker", "roof"); + var unpreferred = List.of("bad"); + var preferred = List.of("a", "b"); + carArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "car", + Map.ofEntries( + entry( + "parking", + Map.ofEntries( + entry("unpreferredCost", unpreferredCost), + entry( + "filters", + List.of( + Map.ofEntries( + entry("not", List.of(Map.of("tags", notFilter))), + entry("select", List.of(Map.of("tags", selectFilter))) + ) + ) + ), + entry( + "preferred", + List.of( + Map.ofEntries( + entry("not", List.of(Map.of("tags", unpreferred))), + entry("select", List.of(Map.of("tags", preferred))) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ); + var env = executionContext(carArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + var carParkingPreferences = routeRequest.preferences().car().parking(); + assertEquals(unpreferredCost, carParkingPreferences.unpreferredVehicleParkingTagCost()); + assertEquals( + "VehicleParkingFilter{not: [tags=%s], select: [tags=%s]}".formatted(notFilter, selectFilter), + carParkingPreferences.filter().toString() + ); + assertEquals( + "VehicleParkingFilter{not: [tags=%s], select: [tags=%s]}".formatted(unpreferred, preferred), + carParkingPreferences.preferred().toString() + ); + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperScooterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperScooterTest.java new file mode 100644 index 00000000000..507f7276df8 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperScooterTest.java @@ -0,0 +1,154 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.createArgsCopy; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.executionContext; + +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; +import org.opentripplanner.routing.core.VehicleRoutingOptimizeType; + +class RouteRequestMapperScooterTest { + + @Test + void testBasicScooterPreferences() { + var scooterArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var reluctance = 7.5; + var speed = 15d; + scooterArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry("scooter", Map.ofEntries(entry("reluctance", reluctance), entry("speed", speed))) + ) + ) + ) + ); + var env = executionContext(scooterArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + var scooterPreferences = routeRequest.preferences().scooter(); + assertEquals(reluctance, scooterPreferences.reluctance()); + assertEquals(speed, scooterPreferences.speed()); + } + + @Test + void testScooterTrianglePreferences() { + var scooterArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var scooterSafety = 0.3; + var scooterFlatness = 0.5; + var scooterTime = 0.2; + scooterArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "scooter", + Map.ofEntries( + entry( + "optimization", + Map.ofEntries( + entry( + "triangle", + Map.ofEntries( + entry("safety", scooterSafety), + entry("flatness", scooterFlatness), + entry("time", scooterTime) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ); + var env = executionContext(scooterArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + var scooterPreferences = routeRequest.preferences().scooter(); + assertEquals(VehicleRoutingOptimizeType.TRIANGLE, scooterPreferences.optimizeType()); + var scooterTrianglePreferences = scooterPreferences.optimizeTriangle(); + assertEquals(scooterSafety, scooterTrianglePreferences.safety()); + assertEquals(scooterFlatness, scooterTrianglePreferences.slope()); + assertEquals(scooterTime, scooterTrianglePreferences.time()); + } + + @Test + void testScooterOptimizationPreferences() { + var scooterArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + scooterArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "scooter", + Map.ofEntries(entry("optimization", Map.ofEntries(entry("type", "SAFEST_STREETS")))) + ) + ) + ) + ) + ); + var env = executionContext(scooterArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + var scooterPreferences = routeRequest.preferences().scooter(); + assertEquals(VehicleRoutingOptimizeType.SAFEST_STREETS, scooterPreferences.optimizeType()); + } + + @Test + void testScooterRentalPreferences() { + var scooterArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var allowed = Set.of("foo", "bar"); + var banned = Set.of("not"); + var allowKeeping = true; + var keepingCost = Cost.costOfSeconds(150); + scooterArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "scooter", + Map.ofEntries( + entry( + "rental", + Map.ofEntries( + entry("allowedNetworks", allowed.stream().toList()), + entry("bannedNetworks", banned.stream().toList()), + entry( + "destinationScooterPolicy", + Map.ofEntries( + entry("allowKeeping", allowKeeping), + entry("keepingCost", keepingCost) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ); + var env = executionContext(scooterArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + var scooterRentalPreferences = routeRequest.preferences().scooter().rental(); + assertEquals(allowed, scooterRentalPreferences.allowedNetworks()); + assertEquals(banned, scooterRentalPreferences.bannedNetworks()); + assertEquals( + allowKeeping, + scooterRentalPreferences.allowArrivingInRentedVehicleAtDestination() + ); + assertEquals(keepingCost, scooterRentalPreferences.arrivingInRentalVehicleAtDestinationCost()); + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperWalkTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperWalkTest.java new file mode 100644 index 00000000000..86d249419e1 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperWalkTest.java @@ -0,0 +1,49 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.createArgsCopy; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.executionContext; + +import java.util.Locale; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; + +class RouteRequestMapperWalkTest { + + @Test + void testWalkPreferences() { + var walkArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var reluctance = 7.5; + var speed = 15d; + var boardCost = Cost.costOfSeconds(50); + var safetyFactor = 0.4; + walkArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "walk", + Map.ofEntries( + entry("reluctance", reluctance), + entry("speed", speed), + entry("boardCost", boardCost), + entry("safetyFactor", safetyFactor) + ) + ) + ) + ) + ) + ); + var env = executionContext(walkArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + var walkPreferences = routeRequest.preferences().walk(); + assertEquals(reluctance, walkPreferences.reluctance()); + assertEquals(speed, walkPreferences.speed()); + assertEquals(boardCost.toSeconds(), walkPreferences.boardCost()); + assertEquals(safetyFactor, walkPreferences.safetyFactor()); + } +} From dd68ea1b29627099559bf851fab304733edf75d0 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Sun, 24 Mar 2024 00:03:43 +0200 Subject: [PATCH 149/165] Add transit and accesibility tests --- .../RouteRequestMapperAccessibilityTest.java | 31 +++++ .../RouteRequestMapperTransitTest.java | 126 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperAccessibilityTest.java create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperAccessibilityTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperAccessibilityTest.java new file mode 100644 index 00000000000..99377aa7e46 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperAccessibilityTest.java @@ -0,0 +1,31 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.createArgsCopy; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.executionContext; + +import java.util.Locale; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class RouteRequestMapperAccessibilityTest { + + @Test + void testWheelchairPreferences() { + var args = createArgsCopy(RouteRequestMapperTest.ARGS); + var wheelchairEnabled = true; + args.put( + "preferences", + Map.ofEntries( + entry( + "accessibility", + Map.ofEntries(entry("wheelchair", Map.ofEntries(entry("enabled", wheelchairEnabled)))) + ) + ) + ); + var env = executionContext(args, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + assertEquals(wheelchairEnabled, routeRequest.wheelchair()); + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java new file mode 100644 index 00000000000..a8b5c10b381 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java @@ -0,0 +1,126 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.createArgsCopy; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.executionContext; + +import java.time.Duration; +import java.util.Locale; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.opentripplanner.framework.model.Cost; +import org.opentripplanner.transit.model.basic.TransitMode; + +class RouteRequestMapperTransitTest { + + @Test + void testBoardPreferences() { + var args = createArgsCopy(RouteRequestMapperTest.ARGS); + var reluctance = 7.5; + var slack = Duration.ofSeconds(125); + args.put( + "preferences", + Map.ofEntries( + entry( + "transit", + Map.ofEntries( + entry( + "board", + Map.ofEntries(entry("waitReluctance", reluctance), entry("slack", slack)) + ) + ) + ) + ) + ); + var env = executionContext(args, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + var transferPreferences = routeRequest.preferences().transfer(); + assertEquals(reluctance, transferPreferences.waitReluctance()); + var transitPreferences = routeRequest.preferences().transit(); + assertEquals(slack, transitPreferences.boardSlack().valueOf(TransitMode.BUS)); + } + + @Test + void testAlightPreferences() { + var args = createArgsCopy(RouteRequestMapperTest.ARGS); + var slack = Duration.ofSeconds(125); + args.put( + "preferences", + Map.ofEntries( + entry("transit", Map.ofEntries(entry("alight", Map.ofEntries(entry("slack", slack))))) + ) + ); + var env = executionContext(args, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + var transitPreferences = routeRequest.preferences().transit(); + assertEquals(slack, transitPreferences.alightSlack().valueOf(TransitMode.BUS)); + } + + @Test + void testTransferPreferences() { + var args = createArgsCopy(RouteRequestMapperTest.ARGS); + var cost = Cost.costOfSeconds(75); + var slack = Duration.ofSeconds(125); + var maximumAdditionalTransfers = 1; + var maximumTransfers = 3; + args.put( + "preferences", + Map.ofEntries( + entry( + "transit", + Map.ofEntries( + entry( + "transfer", + Map.ofEntries( + entry("cost", cost), + entry("slack", slack), + entry("maximumAdditionalTransfers", maximumAdditionalTransfers), + entry("maximumTransfers", maximumTransfers) + ) + ) + ) + ) + ) + ); + var env = executionContext(args, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + var transferPreferences = routeRequest.preferences().transfer(); + assertEquals(cost.toSeconds(), transferPreferences.cost()); + assertEquals(slack.toSeconds(), transferPreferences.slack()); + assertEquals(maximumAdditionalTransfers, transferPreferences.maxAdditionalTransfers()); + assertEquals(maximumTransfers, transferPreferences.maxTransfers()); + } + + @Test + void testTimetablePreferences() { + var args = createArgsCopy(RouteRequestMapperTest.ARGS); + var excludeRealTimeUpdates = true; + var includePlannedCancellations = true; + var includeRealTimeCancellations = true; + args.put( + "preferences", + Map.ofEntries( + entry( + "transit", + Map.ofEntries( + entry( + "timetable", + Map.ofEntries( + entry("excludeRealTimeUpdates", excludeRealTimeUpdates), + entry("includePlannedCancellations", includePlannedCancellations), + entry("includeRealTimeCancellations", includeRealTimeCancellations) + ) + ) + ) + ) + ) + ); + var env = executionContext(args, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + var transitPreferences = routeRequest.preferences().transit(); + assertEquals(excludeRealTimeUpdates, transitPreferences.ignoreRealtimeUpdates()); + assertEquals(includePlannedCancellations, transitPreferences.includePlannedCancellations()); + assertEquals(includeRealTimeCancellations, transitPreferences.includeRealtimeCancellations()); + } +} From c78619b4185fc0c9ddded0e451bf2b2ebfb59be5 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 25 Mar 2024 13:09:12 +0200 Subject: [PATCH 150/165] Add tests for utils and rename duration util method --- .../routerequest/RouteRequestMapper.java | 2 +- .../framework/time/DurationUtils.java | 2 +- .../framework/graphql/GraphQLUtilsTest.java | 6 ++- .../framework/time/DurationUtilsTest.java | 42 +++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java index 3044577258f..cc45e87c35e 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java @@ -43,7 +43,7 @@ public static RouteRequest toRouteRequest( request.setLocale(GraphQLUtils.getLocale(environment, args.getGraphQLLocale())); if (args.getGraphQLSearchWindow() != null) { request.setSearchWindow( - DurationUtils.requireNonNegativeLarge(args.getGraphQLSearchWindow(), "searchWindow") + DurationUtils.requireNonNegativeLong(args.getGraphQLSearchWindow(), "searchWindow") ); } diff --git a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java index 8f50240d693..9d16ed7d269 100644 --- a/src/main/java/org/opentripplanner/framework/time/DurationUtils.java +++ b/src/main/java/org/opentripplanner/framework/time/DurationUtils.java @@ -191,7 +191,7 @@ public static Duration requireNonNegative(Duration value) { * * @param subject used to identify name of the problematic value when throwing an exception. */ - public static Duration requireNonNegativeLarge(Duration value, String subject) { + public static Duration requireNonNegativeLong(Duration value, String subject) { Objects.requireNonNull(value); if (value.isNegative()) { throw new IllegalArgumentException( diff --git a/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java b/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java index 9a1e2a2ba90..b72cb6e5a0d 100644 --- a/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/graphql/GraphQLUtilsTest.java @@ -73,9 +73,11 @@ void testGetLocaleWithDefinedLocaleArg() { var frenchLocale = Locale.FRENCH; - var locale = GraphQLUtils.getLocale(env, frenchLocale.toString()); + var localeWithString = GraphQLUtils.getLocale(env, frenchLocale.toString()); + assertEquals(frenchLocale, localeWithString); - assertEquals(frenchLocale, locale); + var localeWithLocale = GraphQLUtils.getLocale(env, frenchLocale); + assertEquals(frenchLocale, localeWithLocale); } @Test diff --git a/src/test/java/org/opentripplanner/framework/time/DurationUtilsTest.java b/src/test/java/org/opentripplanner/framework/time/DurationUtilsTest.java index 2457aa14b60..97b8afc52c0 100644 --- a/src/test/java/org/opentripplanner/framework/time/DurationUtilsTest.java +++ b/src/test/java/org/opentripplanner/framework/time/DurationUtilsTest.java @@ -4,6 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.params.provider.Arguments.of; import static org.opentripplanner.framework.time.DurationUtils.requireNonNegative; +import static org.opentripplanner.framework.time.DurationUtils.requireNonNegativeLong; +import static org.opentripplanner.framework.time.DurationUtils.requireNonNegativeMedium; +import static org.opentripplanner.framework.time.DurationUtils.requireNonNegativeShort; import static org.opentripplanner.framework.time.DurationUtils.toIntMilliseconds; import java.time.Duration; @@ -124,6 +127,45 @@ public void testRequireNonNegative() { assertThrows(IllegalArgumentException.class, () -> requireNonNegative(Duration.ofSeconds(-1))); } + @Test + public void testRequireNonNegativeLong() { + assertThrows(NullPointerException.class, () -> requireNonNegativeLong(null, "test")); + assertThrows( + IllegalArgumentException.class, + () -> requireNonNegativeLong(Duration.ofSeconds(-1), "test") + ); + assertThrows( + IllegalArgumentException.class, + () -> requireNonNegativeLong(Duration.ofDays(3), "test") + ); + } + + @Test + public void testRequireNonNegativeMedium() { + assertThrows(NullPointerException.class, () -> requireNonNegativeMedium(null, "test")); + assertThrows( + IllegalArgumentException.class, + () -> requireNonNegativeMedium(Duration.ofSeconds(-1), "test") + ); + assertThrows( + IllegalArgumentException.class, + () -> requireNonNegativeMedium(Duration.ofHours(3), "test") + ); + } + + @Test + public void testRequireNonNegativeShort() { + assertThrows(NullPointerException.class, () -> requireNonNegativeShort(null, "test")); + assertThrows( + IllegalArgumentException.class, + () -> requireNonNegativeShort(Duration.ofSeconds(-1), "test") + ); + assertThrows( + IllegalArgumentException.class, + () -> requireNonNegativeShort(Duration.ofMinutes(31), "test") + ); + } + @Test public void testToIntMilliseconds() { assertEquals(20, toIntMilliseconds(null, 20)); From be91f29acc886b6796365893a63f162ae47737d6 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 25 Mar 2024 17:04:07 +0200 Subject: [PATCH 151/165] Implement access/egress/transfer/direct mode validation --- .../routerequest/ModePreferencesMapper.java | 76 ++++++++++++++++++- .../apis/gtfs/queries/planConnection.graphql | 4 +- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ModePreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ModePreferencesMapper.java index 28e884acbde..0bd4e53eb35 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ModePreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ModePreferencesMapper.java @@ -29,7 +29,8 @@ static void setModes( if (Boolean.TRUE.equals(modesInput.getGraphQLTransitOnly())) { journey.direct().setMode(StreetMode.NOT_SET); } else if (!CollectionUtils.isEmpty(direct)) { - journey.direct().setMode(DirectModeMapper.map(direct.getFirst())); + var streetModes = direct.stream().map(DirectModeMapper::map).toList(); + journey.direct().setMode(getStreetMode(streetModes)); } var transit = modesInput.getGraphQLTransit(); @@ -38,17 +39,20 @@ static void setModes( } else if (transit != null) { var access = transit.getGraphQLAccess(); if (!CollectionUtils.isEmpty(access)) { - journey.access().setMode(AccessModeMapper.map(access.getFirst())); + var streetModes = access.stream().map(AccessModeMapper::map).toList(); + journey.access().setMode(getStreetMode(streetModes)); } var egress = transit.getGraphQLEgress(); if (!CollectionUtils.isEmpty(egress)) { - journey.egress().setMode(EgressModeMapper.map(egress.getFirst())); + var streetModes = egress.stream().map(EgressModeMapper::map).toList(); + journey.egress().setMode(getStreetMode(streetModes)); } var transfer = transit.getGraphQLTransfer(); if (!CollectionUtils.isEmpty(transfer)) { - journey.transfer().setMode(TransferModeMapper.map(transfer.getFirst())); + var streetModes = transfer.stream().map(TransferModeMapper::map).toList(); + journey.transfer().setMode(getStreetMode(streetModes)); } validateStreetModes(journey); @@ -73,6 +77,70 @@ static void setModes( } } + /** + * Current support: + * 1. If only one mode is defined, it needs to be WALK, BICYCLE, CAR or some parking mode. + * 2. If two modes are defined, they can't be BICYCLE or CAR, and WALK needs to be one of them. + * 3. More than two modes can't be defined for the same leg. + *

+ * TODO future support: + * 1. Any mode can be defined alone. If it's not used in a leg, the leg gets filtered away. + * 2. If two modes are defined, they can't be BICYCLE or CAR. Usually WALK is required as the second + * mode but in some cases it's possible to define other modes as well such as BICYCLE_RENTAL together + * with SCOOTER_RENTAL. In that case, legs which don't use BICYCLE_RENTAL or SCOOTER_RENTAL would be filtered + * out. + * 3. When more than two modes are used, some combinations are supported such as WALK, BICYCLE_RENTAL and SCOOTER_RENTAL. + */ + private static StreetMode getStreetMode(List modes) { + if (modes.size() > 2) { + throw new IllegalArgumentException( + "Only one or two modes can be specified for a leg, got: %.".formatted(modes) + ); + } + if (modes.size() == 1) { + var mode = modes.getFirst(); + // TODO in the future, we will support defining other modes alone as well and filter out legs + // which don't contain the only specified mode as opposed to also returning legs which contain + // only walking. + if (!isAlwaysPresentInLeg(mode)) { + throw new IllegalArgumentException( + "For the time being, %s needs to be combined with WALK mode for the same leg.".formatted( + mode + ) + ); + } + return mode; + } + if (modes.contains(StreetMode.BIKE)) { + throw new IllegalArgumentException( + "Bicycle can't be combined with other modes for the same leg: %s.".formatted(modes) + ); + } + if (modes.contains(StreetMode.CAR)) { + throw new IllegalArgumentException( + "Car can't be combined with other modes for the same leg: %s.".formatted(modes) + ); + } + if (!modes.contains(StreetMode.WALK)) { + throw new IllegalArgumentException( + "For the time being, WALK needs to be added as a mode for a leg when using %s and these two can't be used in the same leg.".formatted( + modes + ) + ); + } + // Walk is currently always used as an implied mode when mode is not car. + return modes.stream().filter(mode -> mode != StreetMode.WALK).findFirst().get(); + } + + private static boolean isAlwaysPresentInLeg(StreetMode mode) { + return ( + mode == StreetMode.BIKE || + mode == StreetMode.CAR || + mode == StreetMode.WALK || + mode.includesParking() + ); + } + /** * TODO this doesn't support multiple street modes yet */ diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql index aec99442d3b..2e87c4dec01 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql @@ -28,9 +28,9 @@ transitOnly: false direct: [WALK] transit: { - access: [BICYCLE_RENTAL] + access: [BICYCLE_RENTAL, WALK] transfer: [WALK] - egress: [BICYCLE_RENTAL] + egress: [BICYCLE_RENTAL, WALK] transit: [ { mode: TRAM From 451ea2ee0baf3077b4b4c5dcef7d29719f66c420 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 25 Mar 2024 17:24:22 +0200 Subject: [PATCH 152/165] Add more tests for street modes --- .../RouteRequestMapperModesTest.java | 186 ++++++++++++++++++ .../routerequest/RouteRequestMapperTest.java | 82 -------- 2 files changed, 186 insertions(+), 82 deletions(-) create mode 100644 src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperModesTest.java diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperModesTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperModesTest.java new file mode 100644 index 00000000000..0d9285f93b9 --- /dev/null +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperModesTest.java @@ -0,0 +1,186 @@ +package org.opentripplanner.apis.gtfs.mapping.routerequest; + +import static java.util.Map.entry; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.createArgsCopy; +import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.executionContext; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.opentripplanner.routing.api.request.StreetMode; +import org.opentripplanner.transit.model.basic.TransitMode; + +class RouteRequestMapperModesTest { + + @Test + void testDirectOnly() { + var modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + modesArgs.put("modes", Map.ofEntries(entry("directOnly", true))); + var env = executionContext(modesArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + assertFalse(routeRequest.journey().transit().enabled()); + } + + @Test + void testTransitOnly() { + var modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + modesArgs.put("modes", Map.ofEntries(entry("transitOnly", true))); + var env = executionContext(modesArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + assertEquals(StreetMode.NOT_SET, routeRequest.journey().direct().mode()); + } + + @Test + void testStreetModesWithOneValidMode() { + var modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var bicycle = List.of("BICYCLE"); + modesArgs.put( + "modes", + Map.ofEntries( + entry("direct", List.of("CAR")), + entry( + "transit", + Map.ofEntries( + entry("access", bicycle), + entry("egress", bicycle), + entry("transfer", bicycle) + ) + ) + ) + ); + var env = executionContext(modesArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + assertEquals(StreetMode.CAR, routeRequest.journey().direct().mode()); + assertEquals(StreetMode.BIKE, routeRequest.journey().access().mode()); + assertEquals(StreetMode.BIKE, routeRequest.journey().egress().mode()); + assertEquals(StreetMode.BIKE, routeRequest.journey().transfer().mode()); + } + + @Test + void testStreetModesWithOneInvalidMode() { + var modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var bicycleRental = List.of("BICYCLE_RENTAL"); + modesArgs.put("modes", Map.ofEntries(entry("direct", bicycleRental))); + var env = executionContext(modesArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + assertThrows( + IllegalArgumentException.class, + () -> RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT) + ); + } + + @Test + void testStreetModesWithTwoValidModes() { + var modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var bicycleRental = List.of("BICYCLE_RENTAL", "WALK"); + modesArgs.put( + "modes", + Map.ofEntries( + entry("direct", bicycleRental), + entry( + "transit", + Map.ofEntries(entry("access", bicycleRental), entry("egress", bicycleRental)) + ) + ) + ); + var env = executionContext(modesArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + assertEquals(StreetMode.BIKE_RENTAL, routeRequest.journey().direct().mode()); + assertEquals(StreetMode.BIKE_RENTAL, routeRequest.journey().access().mode()); + assertEquals(StreetMode.BIKE_RENTAL, routeRequest.journey().egress().mode()); + assertEquals(StreetMode.WALK, routeRequest.journey().transfer().mode()); + } + + @Test + void testStreetModesWithTwoInvalidModes() { + var modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var rentals = List.of("BICYCLE_RENTAL", "CAR_RENTAL"); + modesArgs.put( + "modes", + Map.ofEntries( + entry("direct", rentals), + entry("transit", Map.ofEntries(entry("access", rentals), entry("egress", rentals))) + ) + ); + var rentalEnv = executionContext(modesArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + assertThrows( + IllegalArgumentException.class, + () -> RouteRequestMapper.toRouteRequest(rentalEnv, RouteRequestMapperTest.CONTEXT) + ); + + modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var bicycleWalk = List.of("BICYCLE", "WALK"); + modesArgs.put( + "modes", + Map.ofEntries( + entry("transit", Map.ofEntries(entry("access", bicycleWalk), entry("egress", bicycleWalk))) + ) + ); + var bicycleWalkEnv = executionContext( + modesArgs, + Locale.ENGLISH, + RouteRequestMapperTest.CONTEXT + ); + assertThrows( + IllegalArgumentException.class, + () -> RouteRequestMapper.toRouteRequest(bicycleWalkEnv, RouteRequestMapperTest.CONTEXT) + ); + } + + @Test + void testStreetModesWithThreeModes() { + var modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var rentals = List.of("WALK", "BICYCLE_RENTAL", "CAR_RENTAL"); + modesArgs.put( + "modes", + Map.ofEntries( + entry("direct", rentals), + entry("transit", Map.ofEntries(entry("access", rentals), entry("egress", rentals))) + ) + ); + var env = executionContext(modesArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + assertThrows( + IllegalArgumentException.class, + () -> RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT) + ); + } + + @Test + void testTransitModes() { + var modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var tramCost = 1.5; + modesArgs.put( + "modes", + Map.ofEntries( + entry( + "transit", + Map.ofEntries( + entry( + "transit", + List.of( + Map.ofEntries( + entry("mode", "TRAM"), + entry("cost", Map.ofEntries(entry("reluctance", tramCost))) + ), + Map.ofEntries(entry("mode", "FERRY")) + ) + ) + ) + ) + ) + ); + var env = executionContext(modesArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(env, RouteRequestMapperTest.CONTEXT); + var reluctanceForMode = routeRequest.preferences().transit().reluctanceForMode(); + assertEquals(tramCost, reluctanceForMode.get(TransitMode.TRAM)); + assertNull(reluctanceForMode.get(TransitMode.FERRY)); + assertEquals( + "[TransitFilterRequest{select: [SelectRequest{transportModes: [FERRY, TRAM]}]}]", + routeRequest.journey().transit().filters().toString() + ); + } +} diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index d1cc96fdea0..731941ad48c 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -5,7 +5,6 @@ import static java.util.Map.entry; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; import graphql.ExecutionInput; import graphql.execution.ExecutionId; @@ -28,13 +27,11 @@ import org.opentripplanner.ext.fares.impl.DefaultFareService; import org.opentripplanner.model.plan.paging.cursor.PageCursor; import org.opentripplanner.routing.api.request.RouteRequest; -import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graphfinder.GraphFinder; import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService; import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService; -import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.TransitModel; @@ -230,85 +227,6 @@ void testNumberOfItineraries() { assertEquals(itineraries, routeRequest.numItineraries()); } - @Test - void testDirectOnly() { - var modesArgs = createArgsCopy(ARGS); - modesArgs.put("modes", Map.ofEntries(entry("directOnly", true))); - var env = executionContext(modesArgs, LOCALE, CONTEXT); - var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); - assertFalse(routeRequest.journey().transit().enabled()); - } - - @Test - void testTransitOnly() { - var modesArgs = createArgsCopy(ARGS); - modesArgs.put("modes", Map.ofEntries(entry("transitOnly", true))); - var env = executionContext(modesArgs, LOCALE, CONTEXT); - var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); - assertEquals(StreetMode.NOT_SET, routeRequest.journey().direct().mode()); - } - - @Test - void testStreetModes() { - var modesArgs = createArgsCopy(ARGS); - var bicycle = List.of("BICYCLE"); - modesArgs.put( - "modes", - Map.ofEntries( - entry("direct", List.of("CAR")), - entry( - "transit", - Map.ofEntries( - entry("access", bicycle), - entry("egress", bicycle), - entry("transfer", bicycle) - ) - ) - ) - ); - var env = executionContext(modesArgs, LOCALE, CONTEXT); - var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); - assertEquals(StreetMode.CAR, routeRequest.journey().direct().mode()); - assertEquals(StreetMode.BIKE, routeRequest.journey().access().mode()); - assertEquals(StreetMode.BIKE, routeRequest.journey().egress().mode()); - assertEquals(StreetMode.BIKE, routeRequest.journey().transfer().mode()); - } - - @Test - void testTransitModes() { - var modesArgs = createArgsCopy(ARGS); - var tramCost = 1.5; - modesArgs.put( - "modes", - Map.ofEntries( - entry( - "transit", - Map.ofEntries( - entry( - "transit", - List.of( - Map.ofEntries( - entry("mode", "TRAM"), - entry("cost", Map.ofEntries(entry("reluctance", tramCost))) - ), - Map.ofEntries(entry("mode", "FERRY")) - ) - ) - ) - ) - ) - ); - var env = executionContext(modesArgs, LOCALE, CONTEXT); - var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); - var reluctanceForMode = routeRequest.preferences().transit().reluctanceForMode(); - assertEquals(tramCost, reluctanceForMode.get(TransitMode.TRAM)); - assertNull(reluctanceForMode.get(TransitMode.FERRY)); - assertEquals( - "[TransitFilterRequest{select: [SelectRequest{transportModes: [FERRY, TRAM]}]}]", - routeRequest.journey().transit().filters().toString() - ); - } - @Test void testItineraryFilters() { var filterArgs = createArgsCopy(ARGS); From 50ff507fb433dfb79566f58d3effdb7831ade39c Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 28 Mar 2024 18:10:31 +0200 Subject: [PATCH 153/165] Better empty array arg handling --- .../mapping/routerequest/ArgumentUtils.java | 3 ++- .../BicyclePreferencesMapper.java | 8 +++--- .../routerequest/CarPreferencesMapper.java | 8 +++--- .../routerequest/ModePreferencesMapper.java | 26 ++++++++++++++----- .../ScooterPreferencesMapper.java | 8 +++--- 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ArgumentUtils.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ArgumentUtils.java index c9235afe185..b04affeaaf2 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ArgumentUtils.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ArgumentUtils.java @@ -20,6 +20,7 @@ public class ArgumentUtils { *

* TODO this ugliness can be removed when the bug gets fixed */ + @Nullable static List> getTransitModes(DataFetchingEnvironment environment) { if (environment.containsArgument("modes")) { Map modesArgs = environment.getArgument("modes"); @@ -30,7 +31,7 @@ static List> getTransitModes(DataFetchingEnvironment environ } } } - return List.of(); + return null; } /** diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/BicyclePreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/BicyclePreferencesMapper.java index cd0f1ef16da..8bafc24b2c4 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/BicyclePreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/BicyclePreferencesMapper.java @@ -8,7 +8,6 @@ import graphql.schema.DataFetchingEnvironment; import java.util.Set; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; -import org.opentripplanner.framework.collection.CollectionUtils; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.routing.api.request.preference.BikePreferences; import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; @@ -99,11 +98,14 @@ private static void setBicycleRentalPreferences( ) { if (args != null) { var allowedNetworks = args.getGraphQLAllowedNetworks(); - if (!CollectionUtils.isEmpty(allowedNetworks)) { + if (allowedNetworks != null) { + if (allowedNetworks.isEmpty()) { + throw new IllegalArgumentException("Allowed bicycle rental networks must not be empty."); + } preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); } var bannedNetworks = args.getGraphQLBannedNetworks(); - if (!CollectionUtils.isEmpty(bannedNetworks)) { + if (bannedNetworks != null) { preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); } var destinationPolicy = args.getGraphQLDestinationBicyclePolicy(); diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java index b0ba8e9776c..ddf086bdac5 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java @@ -8,7 +8,6 @@ import graphql.schema.DataFetchingEnvironment; import java.util.Set; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; -import org.opentripplanner.framework.collection.CollectionUtils; import org.opentripplanner.routing.api.request.preference.CarPreferences; import org.opentripplanner.routing.api.request.preference.VehicleParkingPreferences; import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences; @@ -57,11 +56,14 @@ private static void setCarRentalPreferences( ) { if (args != null) { var allowedNetworks = args.getGraphQLAllowedNetworks(); - if (!CollectionUtils.isEmpty(allowedNetworks)) { + if (allowedNetworks != null) { + if (allowedNetworks.isEmpty()) { + throw new IllegalArgumentException("Allowed car rental networks must not be empty."); + } preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); } var bannedNetworks = args.getGraphQLBannedNetworks(); - if (!CollectionUtils.isEmpty(bannedNetworks)) { + if (bannedNetworks != null) { preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ModePreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ModePreferencesMapper.java index 0bd4e53eb35..32d3456df57 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ModePreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ModePreferencesMapper.java @@ -8,7 +8,6 @@ import java.util.Set; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; import org.opentripplanner.apis.gtfs.mapping.TransitModeMapper; -import org.opentripplanner.framework.collection.CollectionUtils; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.request.JourneyRequest; import org.opentripplanner.routing.api.request.request.filter.SelectRequest; @@ -28,7 +27,10 @@ static void setModes( var direct = modesInput.getGraphQLDirect(); if (Boolean.TRUE.equals(modesInput.getGraphQLTransitOnly())) { journey.direct().setMode(StreetMode.NOT_SET); - } else if (!CollectionUtils.isEmpty(direct)) { + } else if (direct != null) { + if (direct.isEmpty()) { + throw new IllegalArgumentException("Direct modes must not be empty."); + } var streetModes = direct.stream().map(DirectModeMapper::map).toList(); journey.direct().setMode(getStreetMode(streetModes)); } @@ -38,26 +40,38 @@ static void setModes( journey.transit().disable(); } else if (transit != null) { var access = transit.getGraphQLAccess(); - if (!CollectionUtils.isEmpty(access)) { + if (access != null) { + if (access.isEmpty()) { + throw new IllegalArgumentException("Access modes must not be empty."); + } var streetModes = access.stream().map(AccessModeMapper::map).toList(); journey.access().setMode(getStreetMode(streetModes)); } var egress = transit.getGraphQLEgress(); - if (!CollectionUtils.isEmpty(egress)) { + if (egress != null) { + if (egress.isEmpty()) { + throw new IllegalArgumentException("Egress modes must not be empty."); + } var streetModes = egress.stream().map(EgressModeMapper::map).toList(); journey.egress().setMode(getStreetMode(streetModes)); } var transfer = transit.getGraphQLTransfer(); - if (!CollectionUtils.isEmpty(transfer)) { + if (transfer != null) { + if (transfer.isEmpty()) { + throw new IllegalArgumentException("Transfer modes must not be empty."); + } var streetModes = transfer.stream().map(TransferModeMapper::map).toList(); journey.transfer().setMode(getStreetMode(streetModes)); } validateStreetModes(journey); var transitModes = getTransitModes(environment); - if (!CollectionUtils.isEmpty(transitModes)) { + if (transitModes != null) { + if (transitModes.isEmpty()) { + throw new IllegalArgumentException("Transit modes must not be empty."); + } var filterRequestBuilder = TransitFilterRequest.of(); var mainAndSubModes = transitModes .stream() diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java index f3da4c22463..81e481f2571 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java @@ -2,7 +2,6 @@ import java.util.Set; import org.opentripplanner.apis.gtfs.generated.GraphQLTypes; -import org.opentripplanner.framework.collection.CollectionUtils; import org.opentripplanner.routing.api.request.preference.ScooterPreferences; import org.opentripplanner.routing.api.request.preference.VehicleRentalPreferences; @@ -33,11 +32,14 @@ private static void setScooterRentalPreferences( ) { if (args != null) { var allowedNetworks = args.getGraphQLAllowedNetworks(); - if (!CollectionUtils.isEmpty(allowedNetworks)) { + if (allowedNetworks != null) { + if (allowedNetworks.isEmpty()) { + throw new IllegalArgumentException("Allowed scooter rental networks must not be empty."); + } preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); } var bannedNetworks = args.getGraphQLBannedNetworks(); - if (!CollectionUtils.isEmpty(bannedNetworks)) { + if (bannedNetworks != null) { preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); } var destinationPolicy = args.getGraphQLDestinationScooterPolicy(); From 27e533b631a5a5eadfdbac2145bffea38ead1902 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 2 Apr 2024 10:49:04 +0300 Subject: [PATCH 154/165] Add tests --- .../RouteRequestMapperBicycleTest.java | 50 +++++++++++++++++++ .../RouteRequestMapperCarTest.java | 50 +++++++++++++++++++ .../RouteRequestMapperModesTest.java | 47 +++++++++++++++++ .../RouteRequestMapperScooterTest.java | 50 +++++++++++++++++++ 4 files changed, 197 insertions(+) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperBicycleTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperBicycleTest.java index 75c68c2a91a..698390557e1 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperBicycleTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperBicycleTest.java @@ -2,6 +2,7 @@ import static java.util.Map.entry; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.createArgsCopy; import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.executionContext; @@ -205,6 +206,55 @@ void testBikeRentalPreferences() { assertEquals(keepingCost, bikeRentalPreferences.arrivingInRentalVehicleAtDestinationCost()); } + @Test + void testEmptyBikeRentalPreferences() { + var bikeArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var empty = Set.of(); + bikeArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries( + entry("rental", Map.ofEntries(entry("allowedNetworks", empty.stream().toList()))) + ) + ) + ) + ) + ) + ); + var allowedEnv = executionContext(bikeArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + assertThrows( + IllegalArgumentException.class, + () -> RouteRequestMapper.toRouteRequest(allowedEnv, RouteRequestMapperTest.CONTEXT) + ); + + bikeArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + bikeArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "bicycle", + Map.ofEntries( + entry("rental", Map.ofEntries(entry("bannedNetworks", empty.stream().toList()))) + ) + ) + ) + ) + ) + ); + var bannedEnv = executionContext(bikeArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(bannedEnv, RouteRequestMapperTest.CONTEXT); + var bikeRentalPreferences = routeRequest.preferences().bike().rental(); + assertEquals(empty, bikeRentalPreferences.bannedNetworks()); + } + @Test void testBikeParkingPreferences() { var bicycleArgs = createArgsCopy(RouteRequestMapperTest.ARGS); diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperCarTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperCarTest.java index 0fe8f76c26d..6c8109676f0 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperCarTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperCarTest.java @@ -2,6 +2,7 @@ import static java.util.Map.entry; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.createArgsCopy; import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.executionContext; @@ -62,6 +63,55 @@ void testCarRentalPreferences() { assertEquals(banned, carRentalPreferences.bannedNetworks()); } + @Test + void testEmptyCarRentalPreferences() { + var carArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var empty = Set.of(); + carArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "car", + Map.ofEntries( + entry("rental", Map.ofEntries(entry("allowedNetworks", empty.stream().toList()))) + ) + ) + ) + ) + ) + ); + var allowedEnv = executionContext(carArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + assertThrows( + IllegalArgumentException.class, + () -> RouteRequestMapper.toRouteRequest(allowedEnv, RouteRequestMapperTest.CONTEXT) + ); + + carArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + carArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "car", + Map.ofEntries( + entry("rental", Map.ofEntries(entry("bannedNetworks", empty.stream().toList()))) + ) + ) + ) + ) + ) + ); + var bannedEnv = executionContext(carArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(bannedEnv, RouteRequestMapperTest.CONTEXT); + var carRentalPreferences = routeRequest.preferences().car().rental(); + assertEquals(empty, carRentalPreferences.bannedNetworks()); + } + @Test void testCarParkingPreferences() { var carArgs = createArgsCopy(RouteRequestMapperTest.ARGS); diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperModesTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperModesTest.java index 0d9285f93b9..910bb916a7b 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperModesTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperModesTest.java @@ -183,4 +183,51 @@ void testTransitModes() { routeRequest.journey().transit().filters().toString() ); } + + @Test + void testStreetModesWithEmptyModes() { + var modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var empty = List.of(); + modesArgs.put("modes", Map.ofEntries(entry("direct", empty))); + var directEnv = executionContext(modesArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + assertThrows( + IllegalArgumentException.class, + () -> RouteRequestMapper.toRouteRequest(directEnv, RouteRequestMapperTest.CONTEXT) + ); + + modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + modesArgs.put("modes", Map.ofEntries(entry("transit", Map.ofEntries(entry("access", empty))))); + var accessEnv = executionContext(modesArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + assertThrows( + IllegalArgumentException.class, + () -> RouteRequestMapper.toRouteRequest(accessEnv, RouteRequestMapperTest.CONTEXT) + ); + + modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + modesArgs.put("modes", Map.ofEntries(entry("transit", Map.ofEntries(entry("egress", empty))))); + var egressEnv = executionContext(modesArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + assertThrows( + IllegalArgumentException.class, + () -> RouteRequestMapper.toRouteRequest(egressEnv, RouteRequestMapperTest.CONTEXT) + ); + + modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + modesArgs.put( + "modes", + Map.ofEntries(entry("transit", Map.ofEntries(entry("transfer", empty)))) + ); + var transferEnv = executionContext(modesArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + assertThrows( + IllegalArgumentException.class, + () -> RouteRequestMapper.toRouteRequest(transferEnv, RouteRequestMapperTest.CONTEXT) + ); + + modesArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + modesArgs.put("modes", Map.ofEntries(entry("transit", Map.ofEntries(entry("transit", empty))))); + var transitEnv = executionContext(modesArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + assertThrows( + IllegalArgumentException.class, + () -> RouteRequestMapper.toRouteRequest(transitEnv, RouteRequestMapperTest.CONTEXT) + ); + } } diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperScooterTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperScooterTest.java index 507f7276df8..de15a45d0c9 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperScooterTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperScooterTest.java @@ -2,6 +2,7 @@ import static java.util.Map.entry; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.createArgsCopy; import static org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapperTest.executionContext; @@ -151,4 +152,53 @@ void testScooterRentalPreferences() { ); assertEquals(keepingCost, scooterRentalPreferences.arrivingInRentalVehicleAtDestinationCost()); } + + @Test + void testEmptyScooterRentalPreferences() { + var scooterArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + var empty = Set.of(); + scooterArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "scooter", + Map.ofEntries( + entry("rental", Map.ofEntries(entry("allowedNetworks", empty.stream().toList()))) + ) + ) + ) + ) + ) + ); + var allowedEnv = executionContext(scooterArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + assertThrows( + IllegalArgumentException.class, + () -> RouteRequestMapper.toRouteRequest(allowedEnv, RouteRequestMapperTest.CONTEXT) + ); + + scooterArgs = createArgsCopy(RouteRequestMapperTest.ARGS); + scooterArgs.put( + "preferences", + Map.ofEntries( + entry( + "street", + Map.ofEntries( + entry( + "scooter", + Map.ofEntries( + entry("rental", Map.ofEntries(entry("bannedNetworks", empty.stream().toList()))) + ) + ) + ) + ) + ) + ); + var bannedEnv = executionContext(scooterArgs, Locale.ENGLISH, RouteRequestMapperTest.CONTEXT); + var routeRequest = RouteRequestMapper.toRouteRequest(bannedEnv, RouteRequestMapperTest.CONTEXT); + var scooterRentalPreferences = routeRequest.preferences().scooter().rental(); + assertEquals(empty, scooterRentalPreferences.bannedNetworks()); + } } From ffeadea772a236400e9912c2f3f141e4621eb07b Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 11 Apr 2024 16:11:06 +0300 Subject: [PATCH 155/165] Fix rental availability use --- .../routerequest/RouteRequestMapper.java | 22 ++++++++++- .../routerequest/RouteRequestMapperTest.java | 37 ++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java index cc45e87c35e..fb7785e0fda 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java @@ -81,7 +81,7 @@ private static void setPreferences( prefs.withTransit(transit -> { prefs.withTransfer(transfer -> setTransitPreferences(transit, transfer, args, environment)); }); - setStreetPreferences(prefs, preferenceArgs.getGraphQLStreet(), environment); + setStreetPreferences(prefs, request, preferenceArgs.getGraphQLStreet(), environment); setAccessibilityPreferences(request, preferenceArgs.getGraphQLAccessibility()); } @@ -109,9 +109,11 @@ private static void setItineraryFilters( private static void setStreetPreferences( RoutingPreferences.Builder preferences, + RouteRequest request, GraphQLTypes.GraphQLPlanStreetPreferencesInput args, DataFetchingEnvironment environment ) { + setRentalAvailabilityPreferences(preferences, request); if (args != null) { preferences.withBike(bicycle -> setBicyclePreferences(bicycle, args.getGraphQLBicycle(), environment) @@ -122,6 +124,24 @@ private static void setStreetPreferences( } } + private static void setRentalAvailabilityPreferences( + RoutingPreferences.Builder preferences, + RouteRequest request + ) { + preferences.withBike(bike -> + bike.withRental(rental -> rental.withUseAvailabilityInformation(request.isTripPlannedForNow()) + ) + ); + preferences.withCar(car -> + car.withRental(rental -> rental.withUseAvailabilityInformation(request.isTripPlannedForNow())) + ); + preferences.withScooter(scooter -> + scooter.withRental(rental -> + rental.withUseAvailabilityInformation(request.isTripPlannedForNow()) + ) + ); + } + private static void setAccessibilityPreferences( RouteRequest request, GraphQLTypes.GraphQLAccessibilityPreferencesInput preferenceArgs diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index 731941ad48c..1ed4a99edb7 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -88,7 +88,6 @@ void testMinimalArgs() { assertEquals(DESTINATION.x, routeRequest.to().lat); assertEquals(DESTINATION.y, routeRequest.to().lng); assertEquals(LOCALE, routeRequest.locale()); - assertEquals(defaultRequest.preferences(), routeRequest.preferences()); assertEquals(defaultRequest.wheelchair(), routeRequest.wheelchair()); assertEquals(defaultRequest.arriveBy(), routeRequest.arriveBy()); assertEquals(defaultRequest.isTripPlannedForNow(), routeRequest.isTripPlannedForNow()); @@ -108,6 +107,19 @@ void testMinimalArgs() { .compareTo(Duration.ofSeconds(10)) < 0 ); + + // Using current time as datetime changes rental availability use preferences, therefore to + // check that the preferences are equal, we need to use a future time. + var futureArgs = createArgsCopy(ARGS); + var futureTime = OffsetDateTime.of( + LocalDate.of(3000, 3, 15), + LocalTime.MIDNIGHT, + ZoneOffset.UTC + ); + futureArgs.put("dateTime", Map.ofEntries(entry("earliestDeparture", futureTime))); + var futureEnv = executionContext(futureArgs, LOCALE, CONTEXT); + var futureRequest = RouteRequestMapper.toRouteRequest(futureEnv, CONTEXT); + assertEquals(defaultRequest.preferences(), futureRequest.preferences()); } @Test @@ -134,6 +146,29 @@ void testLatestArrival() { assertFalse(routeRequest.isTripPlannedForNow()); } + @Test + void testRentalAvailability() { + var nowArgs = createArgsCopy(ARGS); + var nowEnv = executionContext(nowArgs, LOCALE, CONTEXT); + var nowRequest = RouteRequestMapper.toRouteRequest(nowEnv, CONTEXT); + assertTrue(nowRequest.preferences().bike().rental().useAvailabilityInformation()); + assertTrue(nowRequest.preferences().car().rental().useAvailabilityInformation()); + assertTrue(nowRequest.preferences().scooter().rental().useAvailabilityInformation()); + + var futureArgs = createArgsCopy(ARGS); + var futureTime = OffsetDateTime.of( + LocalDate.of(3000, 3, 15), + LocalTime.MIDNIGHT, + ZoneOffset.UTC + ); + futureArgs.put("dateTime", Map.ofEntries(entry("earliestDeparture", futureTime))); + var futureEnv = executionContext(futureArgs, LOCALE, CONTEXT); + var futureRequest = RouteRequestMapper.toRouteRequest(futureEnv, CONTEXT); + assertFalse(futureRequest.preferences().bike().rental().useAvailabilityInformation()); + assertFalse(futureRequest.preferences().car().rental().useAvailabilityInformation()); + assertFalse(futureRequest.preferences().scooter().rental().useAvailabilityInformation()); + } + @Test void testStopLocationAndLabel() { Map stopLocationArgs = new HashMap<>(); From c5f4f5bb3faabd2891d3eeba5df992cd1dcb6a10 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 11 Apr 2024 17:41:52 +0300 Subject: [PATCH 156/165] Fix handling of Integer values in scalars --- .../apis/gtfs/GraphQLScalars.java | 36 ++++++++++++++----- ...st.java => CoordinateValueScalarTest.java} | 4 +++ .../apis/gtfs/RatioScalarTest.java | 4 +-- .../apis/gtfs/ReluctanceScalarTest.java | 8 +++-- 4 files changed, 39 insertions(+), 13 deletions(-) rename src/test/java/org/opentripplanner/apis/gtfs/{CoordinateValueTest.java => CoordinateValueScalarTest.java} (94%) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java index 29d3e2c5575..e0f6ea8d130 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/GraphQLScalars.java @@ -145,6 +145,10 @@ public Double parseValue(Object input) throws CoercingParseValueException { return validateCoordinate(doubleValue) .orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE)); } + if (input instanceof Integer intValue) { + return validateCoordinate(intValue) + .orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE)); + } throw new CoercingParseValueException( "Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input) ); @@ -317,20 +321,28 @@ public Double serialize(Object dataFetcherResult) throws CoercingSerializeExcept @Override public Grams parseValue(Object input) throws CoercingParseValueException { - if (input instanceof Double) { - var grams = (Double) input; - return new Grams(grams); + if (input instanceof Double doubleValue) { + return new Grams(doubleValue); } - return null; + if (input instanceof Integer intValue) { + return new Grams(intValue); + } + throw new CoercingParseValueException( + "Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input) + ); } @Override public Grams parseLiteral(Object input) throws CoercingParseLiteralException { - if (input instanceof Double) { - var grams = (Double) input; - return new Grams(grams); + if (input instanceof FloatValue coordinate) { + return new Grams(coordinate.getValue().doubleValue()); } - return null; + if (input instanceof IntValue coordinate) { + return new Grams(coordinate.getValue().doubleValue()); + } + throw new CoercingParseLiteralException( + "Expected a number, got: " + input.getClass().getSimpleName() + ); } } ) @@ -367,6 +379,10 @@ public Double parseValue(Object input) throws CoercingParseValueException { return validateRatio(doubleValue) .orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE)); } + if (input instanceof Integer intValue) { + return validateRatio(intValue) + .orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE)); + } throw new CoercingParseValueException( "Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input) ); @@ -429,6 +445,10 @@ public Double parseValue(Object input) throws CoercingParseValueException { return validateReluctance(doubleValue) .orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE)); } + if (input instanceof Integer intValue) { + return validateReluctance(intValue) + .orElseThrow(() -> new CoercingParseValueException(VALIDATION_ERROR_MESSAGE)); + } throw new CoercingParseValueException( "Expected a number, got %s %s".formatted(input.getClass().getSimpleName(), input) ); diff --git a/src/test/java/org/opentripplanner/apis/gtfs/CoordinateValueTest.java b/src/test/java/org/opentripplanner/apis/gtfs/CoordinateValueScalarTest.java similarity index 94% rename from src/test/java/org/opentripplanner/apis/gtfs/CoordinateValueTest.java rename to src/test/java/org/opentripplanner/apis/gtfs/CoordinateValueScalarTest.java index 60028174b5c..99042498342 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/CoordinateValueTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/CoordinateValueScalarTest.java @@ -16,6 +16,7 @@ class CoordinateValueScalarTest { private static final Double COORDINATE = 10.0; + private static final Integer COORDINATE_INT = 10; private static final double COORDINATE_MAX = 180.0; private static final double COORDINATE_MIN = 180.0; private static final double TOO_HIGH = 190; @@ -52,6 +53,9 @@ void testParseValue() { coordinate = (Double) GraphQLScalars.COORDINATE_VALUE_SCALAR.getCoercing().parseValue(COORDINATE_MAX); assertEquals(COORDINATE_MAX, coordinate, DELTA); + coordinate = + (Double) GraphQLScalars.COORDINATE_VALUE_SCALAR.getCoercing().parseValue(COORDINATE_INT); + assertEquals(COORDINATE_INT, coordinate, DELTA); assertThrows( CoercingParseValueException.class, () -> GraphQLScalars.COORDINATE_VALUE_SCALAR.getCoercing().parseValue(TEXT) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/RatioScalarTest.java b/src/test/java/org/opentripplanner/apis/gtfs/RatioScalarTest.java index 5af93dcef1d..b628ef3f5d1 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/RatioScalarTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/RatioScalarTest.java @@ -16,8 +16,8 @@ class RatioScalarTest { private static final Double HALF = 0.5; - private static final double ZERO = 0; - private static final double ONE = 1; + private static final Integer ZERO = 0; + private static final Integer ONE = 1; private static final double TOO_HIGH = 1.1; private static final double TOO_LOW = -1.1; private static final String TEXT = "foo"; diff --git a/src/test/java/org/opentripplanner/apis/gtfs/ReluctanceScalarTest.java b/src/test/java/org/opentripplanner/apis/gtfs/ReluctanceScalarTest.java index 6a1bb7124a9..40684e85885 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/ReluctanceScalarTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/ReluctanceScalarTest.java @@ -16,7 +16,7 @@ class ReluctanceScalarTest { private static final Double HALF = 0.5; - private static final int ONE = 1; + private static final Integer ONE = 1; private static final double TOO_HIGH = 100001; private static final double TOO_LOW = 0; private static final String TEXT = "foo"; @@ -37,8 +37,10 @@ void testSerialize() { @Test void testParseValue() { - var reluctance = (Double) GraphQLScalars.RELUCTANCE_SCALAR.getCoercing().parseValue(HALF); - assertEquals(HALF, reluctance, DELTA); + var reluctanceDouble = (Double) GraphQLScalars.RELUCTANCE_SCALAR.getCoercing().parseValue(HALF); + assertEquals(HALF, reluctanceDouble, DELTA); + var reluctanceInt = (Double) GraphQLScalars.RELUCTANCE_SCALAR.getCoercing().parseValue(ONE); + assertEquals(ONE, reluctanceInt, DELTA); assertThrows( CoercingParseValueException.class, () -> GraphQLScalars.RELUCTANCE_SCALAR.getCoercing().parseValue(TEXT) From 087c436091c002afda636de5860cd72f6fa3c4fc Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 16 Apr 2024 14:31:09 +0300 Subject: [PATCH 157/165] Update graphql-java-extended-scalars --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1d53526ed8f..bf474226bdc 100644 --- a/pom.xml +++ b/pom.xml @@ -857,7 +857,7 @@ com.graphql-java graphql-java-extended-scalars - 21.0 + 22.0 org.apache.httpcomponents.client5 From 18bbe02120594d62fd206b0f163cc713ea2e5636 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Tue, 16 Apr 2024 14:36:02 +0300 Subject: [PATCH 158/165] Fix maxTransfers --- .../mapping/routerequest/TransitPreferencesMapper.java | 10 +++++++++- .../routerequest/RouteRequestMapperTransitTest.java | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java index 792a23bb2a1..3c6a53eac2a 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java @@ -75,10 +75,18 @@ static void setTransitPreferences( } var maxTransfers = transfer.getGraphQLMaximumTransfers(); if (maxTransfers != null) { - transferPreferences.withMaxTransfers(maxTransfers); + if (maxTransfers < 0) { + throw new IllegalArgumentException("Maximum transfers must be non-negative."); + } + transferPreferences.withMaxTransfers(maxTransfers + 1); } var additionalTransfers = transfer.getGraphQLMaximumAdditionalTransfers(); if (additionalTransfers != null) { + if (additionalTransfers < 0) { + throw new IllegalArgumentException( + "Maximum additional transfers must be non-negative." + ); + } transferPreferences.withMaxAdditionalTransfers(additionalTransfers); } } diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java index a8b5c10b381..5b2149afe32 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTransitTest.java @@ -89,7 +89,7 @@ void testTransferPreferences() { assertEquals(cost.toSeconds(), transferPreferences.cost()); assertEquals(slack.toSeconds(), transferPreferences.slack()); assertEquals(maximumAdditionalTransfers, transferPreferences.maxAdditionalTransfers()); - assertEquals(maximumTransfers, transferPreferences.maxTransfers()); + assertEquals(maximumTransfers + 1, transferPreferences.maxTransfers()); } @Test From cad90d3ee6074dc804caf6727468735d1edbfff3 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 25 Apr 2024 17:04:57 +0300 Subject: [PATCH 159/165] Get rid of numberOfItineraries, use first instead --- .../apis/gtfs/generated/GraphQLTypes.java | 10 --------- .../routerequest/RouteRequestMapper.java | 4 ++-- .../opentripplanner/apis/gtfs/schema.graphqls | 22 +++++-------------- .../routerequest/RouteRequestMapperTest.java | 5 +++-- .../apis/gtfs/queries/planConnection.graphql | 2 +- 5 files changed, 11 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java index 0a65edef4a8..3c187ca3bbe 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java @@ -3248,7 +3248,6 @@ public static class GraphQLQueryTypePlanConnectionArgs { private Integer last; private java.util.Locale locale; private GraphQLPlanModesInput modes; - private Integer numberOfItineraries; private GraphQLPlanLabeledLocationInput origin; private GraphQLPlanPreferencesInput preferences; private java.time.Duration searchWindow; @@ -3266,7 +3265,6 @@ public GraphQLQueryTypePlanConnectionArgs(Map args) { this.last = (Integer) args.get("last"); this.locale = (java.util.Locale) args.get("locale"); this.modes = new GraphQLPlanModesInput((Map) args.get("modes")); - this.numberOfItineraries = (Integer) args.get("numberOfItineraries"); this.origin = new GraphQLPlanLabeledLocationInput((Map) args.get("origin")); this.preferences = new GraphQLPlanPreferencesInput((Map) args.get("preferences")); @@ -3310,10 +3308,6 @@ public GraphQLPlanModesInput getGraphQLModes() { return this.modes; } - public Integer getGraphQLNumberOfItineraries() { - return this.numberOfItineraries; - } - public GraphQLPlanLabeledLocationInput getGraphQLOrigin() { return this.origin; } @@ -3362,10 +3356,6 @@ public void setGraphQLModes(GraphQLPlanModesInput modes) { this.modes = modes; } - public void setGraphQLNumberOfItineraries(Integer numberOfItineraries) { - this.numberOfItineraries = numberOfItineraries; - } - public void setGraphQLOrigin(GraphQLPlanLabeledLocationInput origin) { this.origin = origin; } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java index fb7785e0fda..feef589232c 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java @@ -57,8 +57,8 @@ public static RouteRequest toRouteRequest( if (args.getGraphQLFirst() != null) { request.setNumItineraries(args.getGraphQLFirst()); } - } else if (args.getGraphQLNumberOfItineraries() != null) { - request.setNumItineraries(args.getGraphQLNumberOfItineraries()); + } else if (args.getGraphQLFirst() != null) { + request.setNumItineraries(args.getGraphQLFirst()); } request.withPreferences(preferences -> setPreferences(preferences, request, args, environment)); diff --git a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls index ee841a170aa..aada7e9ff68 100644 --- a/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls +++ b/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls @@ -4237,18 +4237,6 @@ type QueryType { """ searchWindow: Duration - """ - Maximum number of itineraries that will be returned. It's possible that no itineraries are - found, paging for earlier or later itineraries can yield to results in that case. The returned - itineraries are either closest to the defined earliest departure time or to the latest arrival - time. It can make sense to search for more itineraries that what is immediately needed if there - is a possibility that more itineraries are used later on (limiting the number of returned itineraries - does not affect the performance by a lot but can affect the network bandwidth usage). During the - search for next or previous pages with `after` or `before` cursors, this field is ignored and - `first` or `last` should be used instead. - """ - numberOfItineraries: Int - """ The origin where the search starts. Usually coordinates but can also be a stop location. """ @@ -4298,11 +4286,11 @@ type QueryType { after: String """ - How many new itineraries should at maximum be returned in forward pagination. It's recommended to - use the same value as was used for the `numberOfItineraries` in the original search for optimal - performance. This parameter is part of the + How many new itineraries should at maximum be returned in either the first search or with + forward pagination. This parameter is part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) - and should be used together with the `after` parameter. + and should be used together with the `after` parameter (although `after` shouldn't be defined + in the first search). """ first: Int @@ -4317,7 +4305,7 @@ type QueryType { """ How many new itineraries should at maximum be returned in backwards pagination. It's recommended to - use the same value as was used for the `numberOfItineraries` in the original search for optimal + use the same value as was used for the `first` parameter in the original search for optimal performance. This parameter is part of the [GraphQL Cursor Connections Specification](https://relay.dev/graphql/connections.htm) and should be used together with the `before` parameter. diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index 1ed4a99edb7..19ac4326f9a 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -253,10 +253,11 @@ void testAfter() { } @Test - void testNumberOfItineraries() { + void testNumberOfItinerariesForSearchWithoutPaging() { var itineraries = 8; var itinArgs = createArgsCopy(ARGS); - itinArgs.put("numberOfItineraries", itineraries); + itinArgs.put("first", itineraries); + itinArgs.put("last", 3); var env = executionContext(itinArgs, LOCALE, CONTEXT); var routeRequest = RouteRequestMapper.toRouteRequest(env, CONTEXT); assertEquals(itineraries, routeRequest.numItineraries()); diff --git a/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql b/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql index 2e87c4dec01..4013479ded0 100644 --- a/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql +++ b/src/test/resources/org/opentripplanner/apis/gtfs/queries/planConnection.graphql @@ -4,7 +4,7 @@ earliestDeparture: "2023-06-13T14:30+03:00" } searchWindow: "PT2H30M" - numberOfItineraries: 5 + first: 5 origin: { location: { coordinate: { From f8dc495a83a89ba628e2a899330615dc589405e2 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 25 Apr 2024 17:17:18 +0300 Subject: [PATCH 160/165] Minor code style change --- .../BicyclePreferencesMapper.java | 181 +++++++++--------- .../routerequest/CarPreferencesMapper.java | 68 ++++--- .../routerequest/RouteRequestMapper.java | 17 +- .../ScooterPreferencesMapper.java | 99 +++++----- .../TransitPreferencesMapper.java | 122 ++++++------ .../routerequest/WalkPreferencesMapper.java | 36 ++-- 6 files changed, 274 insertions(+), 249 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/BicyclePreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/BicyclePreferencesMapper.java index 8bafc24b2c4..7205757a569 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/BicyclePreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/BicyclePreferencesMapper.java @@ -21,54 +21,57 @@ static void setBicyclePreferences( GraphQLTypes.GraphQLBicyclePreferencesInput args, DataFetchingEnvironment environment ) { - if (args != null) { - var speed = args.getGraphQLSpeed(); - if (speed != null) { - preferences.withSpeed(speed); - } - var reluctance = args.getGraphQLReluctance(); - if (reluctance != null) { - preferences.withReluctance(reluctance); - } - var boardCost = args.getGraphQLBoardCost(); - if (boardCost != null) { - preferences.withBoardCost(boardCost.toSeconds()); - } - preferences.withWalking(walk -> setBicycleWalkPreferences(walk, args.getGraphQLWalk())); - preferences.withParking(parking -> - setBicycleParkingPreferences(parking, args.getGraphQLParking(), environment) - ); - preferences.withRental(rental -> setBicycleRentalPreferences(rental, args.getGraphQLRental()) - ); - setBicycleOptimization(preferences, args.getGraphQLOptimization()); + if (args == null) { + return; + } + + var speed = args.getGraphQLSpeed(); + if (speed != null) { + preferences.withSpeed(speed); } + var reluctance = args.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); + } + var boardCost = args.getGraphQLBoardCost(); + if (boardCost != null) { + preferences.withBoardCost(boardCost.toSeconds()); + } + preferences.withWalking(walk -> setBicycleWalkPreferences(walk, args.getGraphQLWalk())); + preferences.withParking(parking -> + setBicycleParkingPreferences(parking, args.getGraphQLParking(), environment) + ); + preferences.withRental(rental -> setBicycleRentalPreferences(rental, args.getGraphQLRental())); + setBicycleOptimization(preferences, args.getGraphQLOptimization()); } private static void setBicycleWalkPreferences( VehicleWalkingPreferences.Builder preferences, GraphQLTypes.GraphQLBicycleWalkPreferencesInput args ) { - if (args != null) { - var speed = args.getGraphQLSpeed(); - if (speed != null) { - preferences.withSpeed(speed); - } - var mountTime = args.getGraphQLMountDismountTime(); - if (mountTime != null) { - preferences.withMountDismountTime( - DurationUtils.requireNonNegativeShort(mountTime, "bicycle mount dismount time") - ); + if (args == null) { + return; + } + + var speed = args.getGraphQLSpeed(); + if (speed != null) { + preferences.withSpeed(speed); + } + var mountTime = args.getGraphQLMountDismountTime(); + if (mountTime != null) { + preferences.withMountDismountTime( + DurationUtils.requireNonNegativeShort(mountTime, "bicycle mount dismount time") + ); + } + var cost = args.getGraphQLCost(); + if (cost != null) { + var reluctance = cost.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); } - var cost = args.getGraphQLCost(); - if (cost != null) { - var reluctance = cost.getGraphQLReluctance(); - if (reluctance != null) { - preferences.withReluctance(reluctance); - } - var mountCost = cost.getGraphQLMountDismountCost(); - if (mountCost != null) { - preferences.withMountDismountCost(mountCost.toSeconds()); - } + var mountCost = cost.getGraphQLMountDismountCost(); + if (mountCost != null) { + preferences.withMountDismountCost(mountCost.toSeconds()); } } } @@ -78,46 +81,50 @@ private static void setBicycleParkingPreferences( GraphQLTypes.GraphQLBicycleParkingPreferencesInput args, DataFetchingEnvironment environment ) { - if (args != null) { - var unpreferredCost = args.getGraphQLUnpreferredCost(); - if (unpreferredCost != null) { - preferences.withUnpreferredVehicleParkingTagCost(unpreferredCost.toSeconds()); - } - var filters = getParkingFilters(environment, "bicycle"); - preferences.withRequiredVehicleParkingTags(parseSelectFilters(filters)); - preferences.withBannedVehicleParkingTags(parseNotFilters(filters)); - var preferred = getParkingPreferred(environment, "bicycle"); - preferences.withPreferredVehicleParkingTags(parseSelectFilters(preferred)); - preferences.withNotPreferredVehicleParkingTags(parseNotFilters(preferred)); + if (args == null) { + return; } + + var unpreferredCost = args.getGraphQLUnpreferredCost(); + if (unpreferredCost != null) { + preferences.withUnpreferredVehicleParkingTagCost(unpreferredCost.toSeconds()); + } + var filters = getParkingFilters(environment, "bicycle"); + preferences.withRequiredVehicleParkingTags(parseSelectFilters(filters)); + preferences.withBannedVehicleParkingTags(parseNotFilters(filters)); + var preferred = getParkingPreferred(environment, "bicycle"); + preferences.withPreferredVehicleParkingTags(parseSelectFilters(preferred)); + preferences.withNotPreferredVehicleParkingTags(parseNotFilters(preferred)); } private static void setBicycleRentalPreferences( VehicleRentalPreferences.Builder preferences, GraphQLTypes.GraphQLBicycleRentalPreferencesInput args ) { - if (args != null) { - var allowedNetworks = args.getGraphQLAllowedNetworks(); - if (allowedNetworks != null) { - if (allowedNetworks.isEmpty()) { - throw new IllegalArgumentException("Allowed bicycle rental networks must not be empty."); - } - preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); + if (args == null) { + return; + } + + var allowedNetworks = args.getGraphQLAllowedNetworks(); + if (allowedNetworks != null) { + if (allowedNetworks.isEmpty()) { + throw new IllegalArgumentException("Allowed bicycle rental networks must not be empty."); } - var bannedNetworks = args.getGraphQLBannedNetworks(); - if (bannedNetworks != null) { - preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); + preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); + } + var bannedNetworks = args.getGraphQLBannedNetworks(); + if (bannedNetworks != null) { + preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); + } + var destinationPolicy = args.getGraphQLDestinationBicyclePolicy(); + if (destinationPolicy != null) { + var allowed = destinationPolicy.getGraphQLAllowKeeping(); + if (allowed != null) { + preferences.withAllowArrivingInRentedVehicleAtDestination(allowed); } - var destinationPolicy = args.getGraphQLDestinationBicyclePolicy(); - if (destinationPolicy != null) { - var allowed = destinationPolicy.getGraphQLAllowKeeping(); - if (allowed != null) { - preferences.withAllowArrivingInRentedVehicleAtDestination(allowed); - } - var cost = destinationPolicy.getGraphQLKeepingCost(); - if (cost != null) { - preferences.withArrivingInRentalVehicleAtDestinationCost(cost.toSeconds()); - } + var cost = destinationPolicy.getGraphQLKeepingCost(); + if (cost != null) { + preferences.withArrivingInRentalVehicleAtDestinationCost(cost.toSeconds()); } } } @@ -126,21 +133,23 @@ private static void setBicycleOptimization( BikePreferences.Builder preferences, GraphQLTypes.GraphQLCyclingOptimizationInput args ) { - if (args != null) { - var type = args.getGraphQLType(); - var mappedType = type != null ? VehicleOptimizationTypeMapper.map(type) : null; - if (mappedType != null) { - preferences.withOptimizeType(mappedType); - } - var triangleArgs = args.getGraphQLTriangle(); - if (isBicycleTriangleSet(triangleArgs)) { - preferences.withForcedOptimizeTriangle(triangle -> { - triangle - .withSlope(triangleArgs.getGraphQLFlatness()) - .withSafety(triangleArgs.getGraphQLSafety()) - .withTime(triangleArgs.getGraphQLTime()); - }); - } + if (args == null) { + return; + } + + var type = args.getGraphQLType(); + var mappedType = type != null ? VehicleOptimizationTypeMapper.map(type) : null; + if (mappedType != null) { + preferences.withOptimizeType(mappedType); + } + var triangleArgs = args.getGraphQLTriangle(); + if (isBicycleTriangleSet(triangleArgs)) { + preferences.withForcedOptimizeTriangle(triangle -> { + triangle + .withSlope(triangleArgs.getGraphQLFlatness()) + .withSafety(triangleArgs.getGraphQLSafety()) + .withTime(triangleArgs.getGraphQLTime()); + }); } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java index ddf086bdac5..01a78153c9b 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/CarPreferencesMapper.java @@ -19,16 +19,18 @@ static void setCarPreferences( GraphQLTypes.GraphQLCarPreferencesInput args, DataFetchingEnvironment environment ) { - if (args != null) { - var reluctance = args.getGraphQLReluctance(); - if (reluctance != null) { - preferences.withReluctance(reluctance); - } - preferences.withParking(parking -> - setCarParkingPreferences(parking, args.getGraphQLParking(), environment) - ); - preferences.withRental(rental -> setCarRentalPreferences(rental, args.getGraphQLRental())); + if (args == null) { + return; + } + + var reluctance = args.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); } + preferences.withParking(parking -> + setCarParkingPreferences(parking, args.getGraphQLParking(), environment) + ); + preferences.withRental(rental -> setCarRentalPreferences(rental, args.getGraphQLRental())); } private static void setCarParkingPreferences( @@ -36,36 +38,40 @@ private static void setCarParkingPreferences( GraphQLTypes.GraphQLCarParkingPreferencesInput args, DataFetchingEnvironment environment ) { - if (args != null) { - var unpreferredCost = args.getGraphQLUnpreferredCost(); - if (unpreferredCost != null) { - preferences.withUnpreferredVehicleParkingTagCost(unpreferredCost.toSeconds()); - } - var filters = getParkingFilters(environment, "car"); - preferences.withRequiredVehicleParkingTags(parseSelectFilters(filters)); - preferences.withBannedVehicleParkingTags(parseNotFilters(filters)); - var preferred = getParkingPreferred(environment, "car"); - preferences.withPreferredVehicleParkingTags(parseSelectFilters(preferred)); - preferences.withNotPreferredVehicleParkingTags(parseNotFilters(preferred)); + if (args == null) { + return; + } + + var unpreferredCost = args.getGraphQLUnpreferredCost(); + if (unpreferredCost != null) { + preferences.withUnpreferredVehicleParkingTagCost(unpreferredCost.toSeconds()); } + var filters = getParkingFilters(environment, "car"); + preferences.withRequiredVehicleParkingTags(parseSelectFilters(filters)); + preferences.withBannedVehicleParkingTags(parseNotFilters(filters)); + var preferred = getParkingPreferred(environment, "car"); + preferences.withPreferredVehicleParkingTags(parseSelectFilters(preferred)); + preferences.withNotPreferredVehicleParkingTags(parseNotFilters(preferred)); } private static void setCarRentalPreferences( VehicleRentalPreferences.Builder preferences, GraphQLTypes.GraphQLCarRentalPreferencesInput args ) { - if (args != null) { - var allowedNetworks = args.getGraphQLAllowedNetworks(); - if (allowedNetworks != null) { - if (allowedNetworks.isEmpty()) { - throw new IllegalArgumentException("Allowed car rental networks must not be empty."); - } - preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); - } - var bannedNetworks = args.getGraphQLBannedNetworks(); - if (bannedNetworks != null) { - preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); + if (args == null) { + return; + } + + var allowedNetworks = args.getGraphQLAllowedNetworks(); + if (allowedNetworks != null) { + if (allowedNetworks.isEmpty()) { + throw new IllegalArgumentException("Allowed car rental networks must not be empty."); } + preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); + } + var bannedNetworks = args.getGraphQLBannedNetworks(); + if (bannedNetworks != null) { + preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); } } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java index feef589232c..ada358d98b3 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java @@ -114,14 +114,17 @@ private static void setStreetPreferences( DataFetchingEnvironment environment ) { setRentalAvailabilityPreferences(preferences, request); - if (args != null) { - preferences.withBike(bicycle -> - setBicyclePreferences(bicycle, args.getGraphQLBicycle(), environment) - ); - preferences.withCar(car -> setCarPreferences(car, args.getGraphQLCar(), environment)); - preferences.withScooter(scooter -> setScooterPreferences(scooter, args.getGraphQLScooter())); - preferences.withWalk(walk -> setWalkPreferences(walk, args.getGraphQLWalk())); + + if (args == null) { + return; } + + preferences.withBike(bicycle -> + setBicyclePreferences(bicycle, args.getGraphQLBicycle(), environment) + ); + preferences.withCar(car -> setCarPreferences(car, args.getGraphQLCar(), environment)); + preferences.withScooter(scooter -> setScooterPreferences(scooter, args.getGraphQLScooter())); + preferences.withWalk(walk -> setWalkPreferences(walk, args.getGraphQLWalk())); } private static void setRentalAvailabilityPreferences( diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java index 81e481f2571..f29ebd0de63 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ScooterPreferencesMapper.java @@ -11,47 +11,50 @@ static void setScooterPreferences( ScooterPreferences.Builder preferences, GraphQLTypes.GraphQLScooterPreferencesInput args ) { - if (args != null) { - var speed = args.getGraphQLSpeed(); - if (speed != null) { - preferences.withSpeed(speed); - } - var reluctance = args.getGraphQLReluctance(); - if (reluctance != null) { - preferences.withReluctance(reluctance); - } - preferences.withRental(rental -> setScooterRentalPreferences(rental, args.getGraphQLRental()) - ); - setScooterOptimization(preferences, args.getGraphQLOptimization()); + if (args == null) { + return; + } + + var speed = args.getGraphQLSpeed(); + if (speed != null) { + preferences.withSpeed(speed); } + var reluctance = args.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); + } + preferences.withRental(rental -> setScooterRentalPreferences(rental, args.getGraphQLRental())); + setScooterOptimization(preferences, args.getGraphQLOptimization()); } private static void setScooterRentalPreferences( VehicleRentalPreferences.Builder preferences, GraphQLTypes.GraphQLScooterRentalPreferencesInput args ) { - if (args != null) { - var allowedNetworks = args.getGraphQLAllowedNetworks(); - if (allowedNetworks != null) { - if (allowedNetworks.isEmpty()) { - throw new IllegalArgumentException("Allowed scooter rental networks must not be empty."); - } - preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); + if (args == null) { + return; + } + + var allowedNetworks = args.getGraphQLAllowedNetworks(); + if (allowedNetworks != null) { + if (allowedNetworks.isEmpty()) { + throw new IllegalArgumentException("Allowed scooter rental networks must not be empty."); } - var bannedNetworks = args.getGraphQLBannedNetworks(); - if (bannedNetworks != null) { - preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); + preferences.withAllowedNetworks(Set.copyOf(allowedNetworks)); + } + var bannedNetworks = args.getGraphQLBannedNetworks(); + if (bannedNetworks != null) { + preferences.withBannedNetworks(Set.copyOf(bannedNetworks)); + } + var destinationPolicy = args.getGraphQLDestinationScooterPolicy(); + if (destinationPolicy != null) { + var allowed = destinationPolicy.getGraphQLAllowKeeping(); + if (allowed != null) { + preferences.withAllowArrivingInRentedVehicleAtDestination(allowed); } - var destinationPolicy = args.getGraphQLDestinationScooterPolicy(); - if (destinationPolicy != null) { - var allowed = destinationPolicy.getGraphQLAllowKeeping(); - if (allowed != null) { - preferences.withAllowArrivingInRentedVehicleAtDestination(allowed); - } - var cost = destinationPolicy.getGraphQLKeepingCost(); - if (cost != null) { - preferences.withArrivingInRentalVehicleAtDestinationCost(cost.toSeconds()); - } + var cost = destinationPolicy.getGraphQLKeepingCost(); + if (cost != null) { + preferences.withArrivingInRentalVehicleAtDestinationCost(cost.toSeconds()); } } } @@ -60,21 +63,23 @@ private static void setScooterOptimization( ScooterPreferences.Builder preferences, GraphQLTypes.GraphQLScooterOptimizationInput args ) { - if (args != null) { - var type = args.getGraphQLType(); - var mappedType = type != null ? VehicleOptimizationTypeMapper.map(type) : null; - if (mappedType != null) { - preferences.withOptimizeType(mappedType); - } - var triangleArgs = args.getGraphQLTriangle(); - if (isScooterTriangleSet(triangleArgs)) { - preferences.withForcedOptimizeTriangle(triangle -> { - triangle - .withSlope(triangleArgs.getGraphQLFlatness()) - .withSafety(triangleArgs.getGraphQLSafety()) - .withTime(triangleArgs.getGraphQLTime()); - }); - } + if (args == null) { + return; + } + + var type = args.getGraphQLType(); + var mappedType = type != null ? VehicleOptimizationTypeMapper.map(type) : null; + if (mappedType != null) { + preferences.withOptimizeType(mappedType); + } + var triangleArgs = args.getGraphQLTriangle(); + if (isScooterTriangleSet(triangleArgs)) { + preferences.withForcedOptimizeTriangle(triangle -> { + triangle + .withSlope(triangleArgs.getGraphQLFlatness()) + .withSafety(triangleArgs.getGraphQLSafety()) + .withTime(triangleArgs.getGraphQLTime()); + }); } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java index 3c6a53eac2a..f542639ae36 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/TransitPreferencesMapper.java @@ -38,72 +38,72 @@ static void setTransitPreferences( transitPreferences.setReluctanceForMode(reluctanceForMode); } var transitArgs = args.getGraphQLPreferences().getGraphQLTransit(); - if (transitArgs != null) { - var board = transitArgs.getGraphQLBoard(); - if (board != null) { - var slack = board.getGraphQLSlack(); - if (slack != null) { - transitPreferences.withDefaultBoardSlackSec( - (int) DurationUtils.requireNonNegativeMedium(slack, "board slack").toSeconds() - ); - } - var waitReluctance = board.getGraphQLWaitReluctance(); - if (waitReluctance != null) { - transferPreferences.withWaitReluctance(waitReluctance); - } + if (transitArgs == null) { + return; + } + + var board = transitArgs.getGraphQLBoard(); + if (board != null) { + var slack = board.getGraphQLSlack(); + if (slack != null) { + transitPreferences.withDefaultBoardSlackSec( + (int) DurationUtils.requireNonNegativeMedium(slack, "board slack").toSeconds() + ); } - var alight = transitArgs.getGraphQLAlight(); - if (alight != null) { - var slack = alight.getGraphQLSlack(); - if (slack != null) { - transitPreferences.withDefaultAlightSlackSec( - (int) DurationUtils.requireNonNegativeMedium(slack, "alight slack").toSeconds() - ); - } + var waitReluctance = board.getGraphQLWaitReluctance(); + if (waitReluctance != null) { + transferPreferences.withWaitReluctance(waitReluctance); } - var transfer = transitArgs.getGraphQLTransfer(); - if (transfer != null) { - var cost = transfer.getGraphQLCost(); - if (cost != null) { - transferPreferences.withCost(cost.toSeconds()); - } - var slack = transfer.getGraphQLSlack(); - if (slack != null) { - transferPreferences.withSlack( - (int) DurationUtils.requireNonNegativeMedium(slack, "transfer slack").toSeconds() - ); - } - var maxTransfers = transfer.getGraphQLMaximumTransfers(); - if (maxTransfers != null) { - if (maxTransfers < 0) { - throw new IllegalArgumentException("Maximum transfers must be non-negative."); - } - transferPreferences.withMaxTransfers(maxTransfers + 1); - } - var additionalTransfers = transfer.getGraphQLMaximumAdditionalTransfers(); - if (additionalTransfers != null) { - if (additionalTransfers < 0) { - throw new IllegalArgumentException( - "Maximum additional transfers must be non-negative." - ); - } - transferPreferences.withMaxAdditionalTransfers(additionalTransfers); - } + } + var alight = transitArgs.getGraphQLAlight(); + if (alight != null) { + var slack = alight.getGraphQLSlack(); + if (slack != null) { + transitPreferences.withDefaultAlightSlackSec( + (int) DurationUtils.requireNonNegativeMedium(slack, "alight slack").toSeconds() + ); } - var timetable = transitArgs.getGraphQLTimetable(); - if (timetable != null) { - var excludeUpdates = timetable.getGraphQLExcludeRealTimeUpdates(); - if (excludeUpdates != null) { - transitPreferences.setIgnoreRealtimeUpdates(excludeUpdates); - } - var includePlannedCancellations = timetable.getGraphQLIncludePlannedCancellations(); - if (includePlannedCancellations != null) { - transitPreferences.setIncludePlannedCancellations(includePlannedCancellations); + } + var transfer = transitArgs.getGraphQLTransfer(); + if (transfer != null) { + var cost = transfer.getGraphQLCost(); + if (cost != null) { + transferPreferences.withCost(cost.toSeconds()); + } + var slack = transfer.getGraphQLSlack(); + if (slack != null) { + transferPreferences.withSlack( + (int) DurationUtils.requireNonNegativeMedium(slack, "transfer slack").toSeconds() + ); + } + var maxTransfers = transfer.getGraphQLMaximumTransfers(); + if (maxTransfers != null) { + if (maxTransfers < 0) { + throw new IllegalArgumentException("Maximum transfers must be non-negative."); } - var includeRealtimeCancellations = timetable.getGraphQLIncludeRealTimeCancellations(); - if (includeRealtimeCancellations != null) { - transitPreferences.setIncludeRealtimeCancellations(includeRealtimeCancellations); + transferPreferences.withMaxTransfers(maxTransfers + 1); + } + var additionalTransfers = transfer.getGraphQLMaximumAdditionalTransfers(); + if (additionalTransfers != null) { + if (additionalTransfers < 0) { + throw new IllegalArgumentException("Maximum additional transfers must be non-negative."); } + transferPreferences.withMaxAdditionalTransfers(additionalTransfers); + } + } + var timetable = transitArgs.getGraphQLTimetable(); + if (timetable != null) { + var excludeUpdates = timetable.getGraphQLExcludeRealTimeUpdates(); + if (excludeUpdates != null) { + transitPreferences.setIgnoreRealtimeUpdates(excludeUpdates); + } + var includePlannedCancellations = timetable.getGraphQLIncludePlannedCancellations(); + if (includePlannedCancellations != null) { + transitPreferences.setIncludePlannedCancellations(includePlannedCancellations); + } + var includeRealtimeCancellations = timetable.getGraphQLIncludeRealTimeCancellations(); + if (includeRealtimeCancellations != null) { + transitPreferences.setIncludeRealtimeCancellations(includeRealtimeCancellations); } } } diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/WalkPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/WalkPreferencesMapper.java index b202d9e7300..1795e999ac6 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/WalkPreferencesMapper.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/WalkPreferencesMapper.java @@ -9,23 +9,25 @@ static void setWalkPreferences( WalkPreferences.Builder preferences, GraphQLTypes.GraphQLWalkPreferencesInput args ) { - if (args != null) { - var speed = args.getGraphQLSpeed(); - if (speed != null) { - preferences.withSpeed(speed); - } - var reluctance = args.getGraphQLReluctance(); - if (reluctance != null) { - preferences.withReluctance(reluctance); - } - var walkSafetyFactor = args.getGraphQLSafetyFactor(); - if (walkSafetyFactor != null) { - preferences.withSafetyFactor(walkSafetyFactor); - } - var boardCost = args.getGraphQLBoardCost(); - if (boardCost != null) { - preferences.withBoardCost(boardCost.toSeconds()); - } + if (args == null) { + return; + } + + var speed = args.getGraphQLSpeed(); + if (speed != null) { + preferences.withSpeed(speed); + } + var reluctance = args.getGraphQLReluctance(); + if (reluctance != null) { + preferences.withReluctance(reluctance); + } + var walkSafetyFactor = args.getGraphQLSafetyFactor(); + if (walkSafetyFactor != null) { + preferences.withSafetyFactor(walkSafetyFactor); + } + var boardCost = args.getGraphQLBoardCost(); + if (boardCost != null) { + preferences.withBoardCost(boardCost.toSeconds()); } } } From ec8a81e3622bd55add5f335b077bdd7df5752545 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Thu, 25 Apr 2024 17:25:54 +0300 Subject: [PATCH 161/165] Use more sensible test method for testing size --- .../gtfs/mapping/routerequest/RouteRequestMapperTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index 19ac4326f9a..f47f7a7004a 100644 --- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -94,8 +94,8 @@ void testMinimalArgs() { assertEquals(defaultRequest.numItineraries(), routeRequest.numItineraries()); assertEquals(defaultRequest.searchWindow(), routeRequest.searchWindow()); assertEquals(defaultRequest.journey().modes(), routeRequest.journey().modes()); - assertTrue(defaultRequest.journey().transit().filters().size() == 1); - assertTrue(routeRequest.journey().transit().filters().size() == 1); + assertEquals(1, defaultRequest.journey().transit().filters().size()); + assertEquals(1, routeRequest.journey().transit().filters().size()); assertTrue(routeRequest.journey().transit().enabled()); assertEquals( defaultRequest.journey().transit().filters().toString(), From 22a976301224299c0e1061775b6ec12ff78d192b Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 26 Apr 2024 20:14:57 +0300 Subject: [PATCH 162/165] Refactor PlanPageInfo to be a class --- .../apis/gtfs/model/PlanPageInfo.java | 79 +++++++++++++++++-- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java index e4953227a6c..1575933b924 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java @@ -2,11 +2,76 @@ import graphql.relay.ConnectionCursor; import java.time.Duration; +import java.util.Objects; -public record PlanPageInfo( - ConnectionCursor startCursor, - ConnectionCursor endCursor, - boolean hasPreviousPage, - boolean hasNextPage, - Duration searchWindowUsed -) {} +public class PlanPageInfo { + + private final ConnectionCursor startCursor; + private final ConnectionCursor endCursor; + private final boolean hasPreviousPage; + private final boolean hasNextPage; + private final Duration searchWindowUsed; + + public PlanPageInfo( + ConnectionCursor startCursor, + ConnectionCursor endCursor, + boolean hasPreviousPage, + boolean hasNextPage, + Duration searchWindowUsed + ) { + this.startCursor = startCursor; + this.endCursor = endCursor; + this.hasPreviousPage = hasPreviousPage; + this.hasNextPage = hasNextPage; + this.searchWindowUsed = searchWindowUsed; + } + + public ConnectionCursor startCursor() { + return startCursor; + } + + public ConnectionCursor endCursor() { + return endCursor; + } + + public boolean hasPreviousPage() { + return hasPreviousPage; + } + + public boolean hasNextPage() { + return hasNextPage; + } + + public Duration searchWindowUsed() { + return searchWindowUsed; + } + + @Override + public int hashCode() { + return Objects.hash( + startCursor.getValue(), + endCursor.getValue(), + hasPreviousPage, + hasNextPage, + searchWindowUsed + ); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlanPageInfo that = (PlanPageInfo) o; + return ( + Objects.equals(startCursor.getValue(), that.startCursor.getValue()) && + Objects.equals(endCursor.getValue(), that.endCursor.getValue()) && + Objects.equals(searchWindowUsed, that.searchWindowUsed) && + hasPreviousPage == that.hasPreviousPage && + hasNextPage == that.hasNextPage + ); + } +} From e9d5d3dd509dde428f7bacfdee0232c8ba5b8c33 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Fri, 17 May 2024 14:16:50 +0300 Subject: [PATCH 163/165] Make eq/hc null safe --- .../apis/gtfs/model/PlanPageInfo.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java index 1575933b924..6eebad77580 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java @@ -49,8 +49,8 @@ public Duration searchWindowUsed() { @Override public int hashCode() { return Objects.hash( - startCursor.getValue(), - endCursor.getValue(), + startCursor != null ? startCursor.getValue() : null, + endCursor != null ? endCursor.getValue() : null, hasPreviousPage, hasNextPage, searchWindowUsed @@ -67,8 +67,22 @@ public boolean equals(Object o) { } PlanPageInfo that = (PlanPageInfo) o; return ( - Objects.equals(startCursor.getValue(), that.startCursor.getValue()) && - Objects.equals(endCursor.getValue(), that.endCursor.getValue()) && + ( + (startCursor == null && that.startCursor == null) || + ( + startCursor != null && + that.startCursor != null && + Objects.equals(startCursor.getValue(), that.startCursor.getValue()) + ) + ) && + ( + (endCursor == null && that.endCursor == null) || + ( + endCursor != null && + that.endCursor != null && + Objects.equals(endCursor.getValue(), that.endCursor.getValue()) + ) + ) && Objects.equals(searchWindowUsed, that.searchWindowUsed) && hasPreviousPage == that.hasPreviousPage && hasNextPage == that.hasNextPage From d2a80c5bda8b36a4d5e6687a31dd67be1672bea5 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 3 Jun 2024 20:26:14 +0300 Subject: [PATCH 164/165] Update src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java Co-authored-by: Thomas Gran --- .../org/opentripplanner/apis/gtfs/model/PlanPageInfo.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java index 6eebad77580..f7859f6a65f 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java @@ -49,8 +49,8 @@ public Duration searchWindowUsed() { @Override public int hashCode() { return Objects.hash( - startCursor != null ? startCursor.getValue() : null, - endCursor != null ? endCursor.getValue() : null, + startCursor == null ? null : startCursor.getValue(), + endCursor == null ? null : endCursor.getValue(), hasPreviousPage, hasNextPage, searchWindowUsed From e1552c2014217551613a8c895eed9ea272609270 Mon Sep 17 00:00:00 2001 From: Joel Lappalainen Date: Mon, 3 Jun 2024 20:39:27 +0300 Subject: [PATCH 165/165] Make equals more readable --- .../apis/gtfs/model/PlanPageInfo.java | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java index f7859f6a65f..dd017971f8a 100644 --- a/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java +++ b/src/main/java/org/opentripplanner/apis/gtfs/model/PlanPageInfo.java @@ -67,25 +67,21 @@ public boolean equals(Object o) { } PlanPageInfo that = (PlanPageInfo) o; return ( - ( - (startCursor == null && that.startCursor == null) || - ( - startCursor != null && - that.startCursor != null && - Objects.equals(startCursor.getValue(), that.startCursor.getValue()) - ) - ) && - ( - (endCursor == null && that.endCursor == null) || - ( - endCursor != null && - that.endCursor != null && - Objects.equals(endCursor.getValue(), that.endCursor.getValue()) - ) - ) && + equalsCursors(startCursor, that.startCursor) && + equalsCursors(endCursor, that.endCursor) && Objects.equals(searchWindowUsed, that.searchWindowUsed) && hasPreviousPage == that.hasPreviousPage && hasNextPage == that.hasNextPage ); } + + /** + * Only checks that the values of the cursors are equal and ignores rest of the fields. + */ + private static boolean equalsCursors(ConnectionCursor a, ConnectionCursor b) { + return ( + (a == null && b == null) || + (a != null && b != null && Objects.equals(a.getValue(), b.getValue())) + ); + } }