@opentripplanner/core-utils 8.1.1 → 8.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,167 @@
1
+ query PlanQuery(
2
+ $fromPlace: String!
3
+ $toPlace: String!
4
+ $modes: [TransportMode]
5
+ $time: String
6
+ $date: String
7
+ $wheelchair: Boolean
8
+ $bikeReluctance: Float
9
+ $carReluctance: Float
10
+ $walkReluctance: Float
11
+ $arriveBy: Boolean
12
+ $intermediatePlaces: [InputCoordinates]
13
+ $preferred: InputPreferred
14
+ $unpreferred: InputUnpreferred
15
+ $banned: InputBanned
16
+ $numItineraries: Int
17
+ ) {
18
+ plan(
19
+ fromPlace: $fromPlace
20
+ toPlace: $toPlace
21
+ transportModes: $modes
22
+ # Currently only supporting EN locale, used for times and text
23
+ locale: "en"
24
+ time: $time
25
+ date: $date
26
+ wheelchair: $wheelchair
27
+ bikeReluctance: $bikeReluctance
28
+ carReluctance: $carReluctance
29
+ walkReluctance: $walkReluctance
30
+ arriveBy: $arriveBy
31
+ intermediatePlaces: $intermediatePlaces
32
+ preferred: $preferred
33
+ unpreferred: $unpreferred
34
+ banned: $banned
35
+ numItineraries: $numItineraries
36
+ ) {
37
+ routingErrors {
38
+ code
39
+ inputField
40
+ description
41
+ }
42
+ itineraries {
43
+ duration
44
+ endTime
45
+ startTime
46
+ waitingTime
47
+ walkTime
48
+ legs {
49
+ rentedBike
50
+ interlineWithPreviousLeg
51
+ departureDelay
52
+ arrivalDelay
53
+ distance
54
+ duration
55
+ endTime
56
+ mode
57
+ realTime
58
+ realtimeState
59
+ startTime
60
+ transitLeg
61
+ trip {
62
+ id
63
+ gtfsId
64
+ tripHeadsign
65
+ }
66
+ agency {
67
+ name
68
+ id
69
+ timezone
70
+ url
71
+ alerts {
72
+ alertHeaderText
73
+ alertDescriptionText
74
+ alertUrl
75
+ effectiveStartDate
76
+ }
77
+ }
78
+ legGeometry {
79
+ length
80
+ points
81
+ }
82
+ intermediateStops {
83
+ lat
84
+ lon
85
+ name
86
+ stopCode: code
87
+ stopId: id
88
+ locationType
89
+ }
90
+ route {
91
+ shortName
92
+ longName
93
+ color
94
+ textColor
95
+ id
96
+ type
97
+ alerts {
98
+ alertHeaderText
99
+ alertDescriptionText
100
+ alertUrl
101
+ effectiveStartDate
102
+ }
103
+ }
104
+ from {
105
+ lat
106
+ lon
107
+ name
108
+ vertexType
109
+ rentalVehicle {
110
+ network
111
+ }
112
+ stop {
113
+ id
114
+ code
115
+ gtfsId
116
+ alerts {
117
+ alertHeaderText
118
+ alertDescriptionText
119
+ alertUrl
120
+ effectiveStartDate
121
+ }
122
+ }
123
+ }
124
+ to {
125
+ lat
126
+ lon
127
+ name
128
+ vertexType
129
+ rentalVehicle {
130
+ network
131
+ }
132
+ stop {
133
+ id
134
+ code
135
+ gtfsId
136
+ alerts {
137
+ alertHeaderText
138
+ alertDescriptionText
139
+ alertUrl
140
+ effectiveStartDate
141
+ }
142
+ }
143
+ }
144
+ steps {
145
+ distance
146
+ lat
147
+ lon
148
+ relativeDirection
149
+ absoluteDirection
150
+ stayOn
151
+ streetName
152
+ area
153
+ alerts {
154
+ alertHeaderText
155
+ alertDescriptionText
156
+ alertUrl
157
+ effectiveStartDate
158
+ }
159
+ elevationProfile {
160
+ distance
161
+ elevation
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
@@ -0,0 +1,244 @@
1
+ import { LonLatOutput } from "@conveyal/lonlat";
2
+ import { print } from "graphql";
3
+ import {
4
+ ModeSetting,
5
+ ModeSettingValues,
6
+ TransportMode
7
+ } from "@opentripplanner/types";
8
+
9
+ import PlanQuery from "./planQuery.graphql";
10
+
11
+ type InputBanned = {
12
+ routes?: string;
13
+ agencies?: string;
14
+ trips?: string;
15
+ stops?: string;
16
+ stopsHard?: string;
17
+ };
18
+
19
+ type InputPreferred = {
20
+ routes?: string;
21
+ agencies?: string;
22
+ unpreferredCost?: string;
23
+ };
24
+
25
+ type OTPQueryParams = {
26
+ arriveBy: boolean;
27
+ date?: string;
28
+ from: LonLatOutput & { name?: string };
29
+ modes: TransportMode[];
30
+ modeSettings: ModeSetting[];
31
+ time?: string;
32
+ numItineraries?: number;
33
+ to: LonLatOutput & { name?: string };
34
+ banned?: InputBanned;
35
+ preferred?: InputPreferred;
36
+ };
37
+
38
+ type GraphQLQuery = {
39
+ query: string;
40
+ variables: Record<string, unknown>;
41
+ };
42
+
43
+ /**
44
+ * Mode Settings can contain additional modes to add to the query,
45
+ * this function extracts those additional modes from the settings
46
+ * and returns them in an array.
47
+ * @param modeSettings List of mode settings with values populated
48
+ * @returns Additional transport modes to add to query
49
+ */
50
+ export function extractAdditionalModes(
51
+ modeSettings: ModeSetting[],
52
+ enabledModes: TransportMode[]
53
+ ): TransportMode[] {
54
+ return modeSettings.reduce<TransportMode[]>((prev, cur) => {
55
+ // First, ensure that the mode associated with this setting is even enabled
56
+ if (!enabledModes.map(m => m.mode).includes(cur.applicableMode)) {
57
+ return prev;
58
+ }
59
+
60
+ // In checkboxes, mode must be enabled and have a transport mode in it
61
+ if (cur.type === "CHECKBOX" && cur.addTransportMode && cur.value) {
62
+ return [...prev, cur.addTransportMode];
63
+ }
64
+ if (cur.type === "DROPDOWN") {
65
+ const transportMode = cur.options.find(o => o.value === cur.value)
66
+ .addTransportMode;
67
+ if (transportMode) {
68
+ return [...prev, transportMode];
69
+ }
70
+ }
71
+ return prev;
72
+ }, []);
73
+ }
74
+
75
+ /**
76
+ * Generates every possible mathematical subset of the input TransportModes.
77
+ * Uses code from:
78
+ * https://stackoverflow.com/questions/5752002/find-all-possible-subset-combos-in-an-array
79
+ * @param array Array of input transport modes
80
+ * @returns 2D array representing every possible subset of transport modes from input
81
+ */
82
+ function combinations(array: TransportMode[]): TransportMode[][] {
83
+ if (!array) return [];
84
+ return (
85
+ // eslint-disable-next-line no-bitwise
86
+ new Array(1 << array.length)
87
+ .fill(null)
88
+ // eslint-disable-next-line no-bitwise
89
+ .map((e1, i) => array.filter((e2, j) => i & (1 << j)))
90
+ );
91
+ }
92
+
93
+ /**
94
+ * This constant maps all the transport mode to a broader mode type,
95
+ * which is used to determine the valid combinations of modes used in query generation.
96
+ */
97
+ export const SIMPLIFICATIONS = {
98
+ AIRPLANE: "TRANSIT",
99
+ BICYCLE: "PERSONAL",
100
+ BUS: "TRANSIT",
101
+ CABLE_CAR: "TRANSIT",
102
+ CAR: "CAR",
103
+ FERRY: "TRANSIT",
104
+ FLEX: "SHARED", // TODO: this allows FLEX+WALK. Is this reasonable?
105
+ FUNICULAR: "TRANSIT",
106
+ GONDOLA: "TRANSIT",
107
+ RAIL: "TRANSIT",
108
+ SCOOTER: "PERSONAL",
109
+ SUBWAY: "TRANSIT",
110
+ TRAM: "TRANSIT",
111
+ TRANSIT: "TRANSIT",
112
+ WALK: "WALK"
113
+ };
114
+
115
+ // Inclusion of "TRANSIT" alone automatically implies "WALK" in OTP
116
+ const VALID_COMBOS = [
117
+ ["WALK"],
118
+ ["PERSONAL"],
119
+ ["TRANSIT", "SHARED"],
120
+ ["WALK", "SHARED"],
121
+ ["TRANSIT"],
122
+ ["TRANSIT", "PERSONAL"],
123
+ ["TRANSIT", "CAR"]
124
+ ];
125
+
126
+ const BANNED_TOGETHER = ["SCOOTER", "BICYCLE"];
127
+
128
+ export const TRANSIT_SUBMODES = Object.keys(SIMPLIFICATIONS).filter(
129
+ mode => SIMPLIFICATIONS[mode] === "TRANSIT" && mode !== "TRANSIT"
130
+ );
131
+ export const TRANSIT_SUBMODES_AND_TRANSIT = Object.keys(SIMPLIFICATIONS).filter(
132
+ mode => SIMPLIFICATIONS[mode] === "TRANSIT"
133
+ );
134
+
135
+ function isCombinationValid(
136
+ combo: TransportMode[],
137
+ queryTransitSubmodes: string[]
138
+ ): boolean {
139
+ if (combo.length === 0) return false;
140
+
141
+ // All current qualifiers currently simplify to "SHARED"
142
+ const simplifiedModes = Array.from(
143
+ new Set(combo.map(c => (c.qualifier ? "SHARED" : SIMPLIFICATIONS[c.mode])))
144
+ );
145
+
146
+ // Ensure that if we have one transit mode, then we include ALL transit modes
147
+ if (simplifiedModes.includes("TRANSIT")) {
148
+ // Don't allow TRANSIT along with any other submodes
149
+ if (queryTransitSubmodes.length && combo.find(c => c.mode === "TRANSIT")) {
150
+ return false;
151
+ }
152
+
153
+ if (
154
+ combo.reduce((prev, cur) => {
155
+ if (queryTransitSubmodes.includes(cur.mode)) {
156
+ return prev - 1;
157
+ }
158
+ return prev;
159
+ }, queryTransitSubmodes.length) !== 0
160
+ ) {
161
+ return false;
162
+ }
163
+ // Continue to the other checks
164
+ }
165
+
166
+ // OTP doesn't support multiple non-walk modes
167
+ if (BANNED_TOGETHER.every(m => combo.find(c => c.mode === m))) return false;
168
+
169
+ return !!VALID_COMBOS.find(
170
+ vc =>
171
+ simplifiedModes.every(m => vc.includes(m)) &&
172
+ vc.every(m => simplifiedModes.includes(m))
173
+ );
174
+ }
175
+
176
+ /**
177
+ * Generates a list of queries for OTP to get a comprehensive
178
+ * set of results based on the modes input.
179
+ * @param params OTP Query Params
180
+ * @returns Set of parameters to generate queries
181
+ */
182
+ export function generateCombinations(params: OTPQueryParams): OTPQueryParams[] {
183
+ const completeModeList = [
184
+ ...extractAdditionalModes(params.modeSettings, params.modes),
185
+ ...params.modes
186
+ ];
187
+
188
+ // List of the transit *submodes* that are included in the input params
189
+ const queryTransitSubmodes = completeModeList
190
+ .filter(mode => TRANSIT_SUBMODES.includes(mode.mode))
191
+ .map(mode => mode.mode);
192
+
193
+ return combinations(completeModeList)
194
+ .filter(combo => isCombinationValid(combo, queryTransitSubmodes))
195
+ .map(combo => ({ ...params, modes: combo }));
196
+ }
197
+
198
+ export function generateOtp2Query({
199
+ arriveBy,
200
+ banned,
201
+ date,
202
+ from,
203
+ modes,
204
+ modeSettings,
205
+ numItineraries,
206
+ preferred,
207
+ time,
208
+ to
209
+ }: OTPQueryParams): GraphQLQuery {
210
+ // This extracts the values from the mode settings to key value pairs
211
+ const modeSettingValues = modeSettings.reduce((prev, cur) => {
212
+ if (cur.type === "SLIDER" && cur.inverseKey) {
213
+ prev[cur.inverseKey] = cur.high - cur.value + cur.low;
214
+ }
215
+ prev[cur.key] = cur.value;
216
+ return prev;
217
+ }, {}) as ModeSettingValues;
218
+
219
+ const {
220
+ bikeReluctance,
221
+ carReluctance,
222
+ walkReluctance,
223
+ wheelchair
224
+ } = modeSettingValues;
225
+
226
+ return {
227
+ query: print(PlanQuery),
228
+ variables: {
229
+ arriveBy,
230
+ banned,
231
+ bikeReluctance,
232
+ carReluctance,
233
+ date,
234
+ fromPlace: `${from.name}::${from.lat},${from.lon}}`,
235
+ modes,
236
+ numItineraries,
237
+ preferred,
238
+ time,
239
+ toPlace: `${to.name}::${to.lat},${to.lon}}`,
240
+ walkReluctance,
241
+ wheelchair
242
+ }
243
+ };
244
+ }
package/tsconfig.json CHANGED
@@ -2,10 +2,12 @@
2
2
  "extends": "../../tsconfig.json",
3
3
  "compilerOptions": {
4
4
  "composite": true,
5
+ "target": "es2019",
5
6
  "outDir": "./lib",
6
7
  "rootDir": "./src",
7
8
  "lib": ["es2019", "dom"],
8
9
  "skipLibCheck": true
9
10
  },
10
- "include": ["src/**/*"]
11
+ "include": ["src/**/*"],
12
+ "exclude": ["src/__tests__/**/*"]
11
13
  }