@opentripplanner/core-utils 8.1.1 → 8.2.0

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