@opentripplanner/core-utils 9.0.0-alpha.2 → 9.0.0-alpha.3

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.
@@ -1,5 +1,6 @@
1
1
  import { reduceOtpFlexModes } from "../query";
2
2
  import queryParams, { getCustomQueryParams } from "../query-params";
3
+ import { generateCombinations } from "../query-gen";
3
4
 
4
5
  const customWalkDistanceOptions = [
5
6
  {
@@ -12,6 +13,130 @@ const customWalkDistanceOptions = [
12
13
  }
13
14
  ];
14
15
 
16
+ function modeStrToTransportMode(m) {
17
+ const splitVals = m.split("_");
18
+ return {
19
+ mode: splitVals[0],
20
+ qualifier: splitVals?.[1] || null
21
+ };
22
+ }
23
+
24
+ const mockLatLon = {
25
+ lat: 1,
26
+ lon: 2
27
+ };
28
+
29
+ function expectModes(modes, expectedModes) {
30
+ const generatedModesList = generateCombinations({
31
+ modes: modes.map(modeStrToTransportMode),
32
+ to: mockLatLon,
33
+ from: mockLatLon,
34
+ modeSettings: []
35
+ });
36
+ const expandedExpectedModesList = expectedModes.map(em => ({
37
+ modes: em.map(modeStrToTransportMode),
38
+ to: mockLatLon,
39
+ from: mockLatLon,
40
+ modeSettings: []
41
+ }));
42
+ return it(
43
+ modes.join(" "),
44
+ () =>
45
+ expect(generatedModesList.length === expandedExpectedModesList.length) &&
46
+ expect(new Set(generatedModesList)).toEqual(
47
+ new Set(expandedExpectedModesList)
48
+ )
49
+ );
50
+ }
51
+
52
+ describe("query-gen", () => {
53
+ describe("generateCombinations", () => {
54
+ expectModes(["WALK"], [["WALK"]]);
55
+ expectModes(["WALK", "TRANSIT"], [["WALK"], ["TRANSIT"]]);
56
+ expectModes(
57
+ ["WALK", "TRANSIT", "BICYCLE"],
58
+ [["WALK"], ["TRANSIT"], ["BICYCLE"], ["TRANSIT", "BICYCLE"]]
59
+ );
60
+ expectModes(
61
+ ["WALK", "TRANSIT", "CAR"],
62
+ [["WALK"], ["TRANSIT"], ["TRANSIT", "CAR"]]
63
+ );
64
+ expectModes(["TRANSIT", "CAR"], [["TRANSIT"], ["TRANSIT", "CAR"]]);
65
+ expectModes(["CAR"], []);
66
+ expectModes(
67
+ ["WALK", "TRANSIT", "BICYCLE", "CAR"],
68
+ [
69
+ ["WALK"],
70
+ ["TRANSIT"],
71
+ ["TRANSIT", "BICYCLE"],
72
+ ["BICYCLE"],
73
+ ["TRANSIT", "CAR"]
74
+ ]
75
+ );
76
+ expectModes(
77
+ ["BICYCLE_RENT", "TRANSIT", "WALK"],
78
+ [
79
+ ["TRANSIT"],
80
+ ["BICYCLE_RENT", "TRANSIT"],
81
+ ["BICYCLE_RENT", "WALK"],
82
+ ["WALK"]
83
+ ]
84
+ );
85
+ expectModes(
86
+ ["BICYCLE_RENT", "BICYCLE", "TRANSIT", "WALK"],
87
+ [
88
+ ["TRANSIT"],
89
+ ["BICYCLE_RENT", "TRANSIT"],
90
+ ["BICYCLE", "TRANSIT"],
91
+ ["BICYCLE_RENT", "WALK"],
92
+ ["BICYCLE"],
93
+ ["WALK"]
94
+ ]
95
+ );
96
+ expectModes(
97
+ ["SCOOTER_RENT", "BICYCLE_RENT", "TRANSIT", "WALK"],
98
+ [
99
+ ["TRANSIT"],
100
+ ["BICYCLE_RENT", "TRANSIT"],
101
+ ["BICYCLE_RENT", "WALK"],
102
+ ["SCOOTER_RENT", "TRANSIT"],
103
+ ["SCOOTER_RENT", "WALK"],
104
+ ["WALK"]
105
+ ]
106
+ );
107
+ expectModes(
108
+ ["FLEX", "TRANSIT", "WALK"],
109
+ [["TRANSIT"], ["FLEX", "TRANSIT"], ["FLEX", "WALK"], ["WALK"]]
110
+ );
111
+ expectModes(
112
+ ["FLEX", "SCOOTER_RENT", "TRANSIT", "WALK"],
113
+ [
114
+ ["TRANSIT"],
115
+ ["FLEX", "TRANSIT"],
116
+ ["WALK"],
117
+ ["FLEX", "WALK"],
118
+ ["FLEX", "SCOOTER_RENT", "WALK"], // Is this sensible?
119
+ ["FLEX", "SCOOTER_RENT", "TRANSIT"],
120
+ ["SCOOTER_RENT", "WALK"],
121
+ ["SCOOTER_RENT", "TRANSIT"]
122
+ ]
123
+ );
124
+ expectModes(
125
+ ["FLEX", "SCOOTER_RENT", "TRANSIT"],
126
+ [
127
+ ["TRANSIT"],
128
+ ["FLEX", "TRANSIT"],
129
+ ["FLEX", "SCOOTER_RENT", "TRANSIT"],
130
+ ["SCOOTER_RENT", "TRANSIT"]
131
+ ]
132
+ );
133
+ expectModes(
134
+ ["BUS", "RAIL", "GONDOLA", "TRAM"],
135
+ [["BUS", "RAIL", "GONDOLA", "TRAM"]]
136
+ );
137
+ });
138
+ });
139
+
15
140
  describe("query-params", () => {
16
141
  describe("getCustomQueryParams", () => {
17
142
  it("should return the original unmodified queryParams if no customizations", () => {
@@ -0,0 +1,7 @@
1
+ declare module "*.graphql" {
2
+ import { DocumentNode } from "graphql";
3
+
4
+ const Schema: DocumentNode;
5
+
6
+ export = Schema;
7
+ }
package/src/index.ts CHANGED
@@ -7,12 +7,14 @@ import * as route from "./route";
7
7
  import * as storage from "./storage";
8
8
  import * as time from "./time";
9
9
  import * as ui from "./ui";
10
+ import * as queryGen from "./query-gen";
10
11
 
11
12
  const core = {
12
13
  itinerary,
13
14
  map,
14
15
  profile,
15
16
  query,
17
+ queryGen,
16
18
  queryParams,
17
19
  route,
18
20
  storage,
@@ -0,0 +1,107 @@
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
+ locale: "en",
22
+ time: $time,
23
+ date: $date,
24
+ wheelchair: $wheelchair,
25
+ bikeReluctance: $bikeReluctance,
26
+ carReluctance: $carReluctance,
27
+ walkReluctance: $walkReluctance,
28
+ arriveBy: $arriveBy,
29
+ intermediatePlaces: $intermediatePlaces,
30
+ preferred: $preferred,
31
+ unpreferred: $unpreferred,
32
+ banned: $banned,
33
+ ) {
34
+ itineraries {
35
+ duration
36
+ endTime
37
+ startTime
38
+ waitingTime
39
+ walkTime
40
+ legs {
41
+ distance
42
+ duration
43
+ endTime
44
+ mode
45
+ realTime
46
+ realtimeState
47
+ startTime
48
+ transitLeg
49
+ agency {
50
+ name
51
+ id
52
+ timezone
53
+ url
54
+ }
55
+ legGeometry {
56
+ length
57
+ points
58
+ }
59
+ intermediateStops {
60
+ lat
61
+ lon
62
+ name
63
+ stopCode: code
64
+ stopId: id
65
+ locationType
66
+ }
67
+ route {
68
+ shortName
69
+ color
70
+ textColor
71
+ id
72
+ type
73
+ }
74
+ from {
75
+ lat
76
+ lon
77
+ name
78
+ vertexType
79
+ stop {
80
+ id
81
+ code
82
+
83
+ }
84
+ }
85
+ to {
86
+ lat
87
+ lon
88
+ name
89
+ vertexType
90
+ stop {
91
+ id
92
+ code
93
+ }
94
+ }
95
+ steps {
96
+ distance
97
+ lat
98
+ lon
99
+ elevationProfile {
100
+ distance
101
+ elevation
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,152 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { print } from "graphql";
3
+ import {
4
+ ModeSetting,
5
+ ModeSettingValues,
6
+ TransportMode
7
+ } from "@opentripplanner/types";
8
+ import { LonLatOutput } from "@conveyal/lonlat";
9
+ import PlanQuery from "./planQuery.graphql";
10
+
11
+ type OTPQueryParams = {
12
+ to: LonLatOutput;
13
+ from: LonLatOutput;
14
+ modes: Array<TransportMode>;
15
+ modeSettings: ModeSetting[];
16
+ };
17
+
18
+ /**
19
+ * Generates every possible mathematical subset of the input TransportModes.
20
+ * Uses code from:
21
+ * https://stackoverflow.com/questions/5752002/find-all-possible-subset-combos-in-an-array
22
+ * @param array Array of input transport modes
23
+ * @returns 2D array representing every possible subset of transport modes from input
24
+ */
25
+ function combinations(array: TransportMode[]): TransportMode[][] {
26
+ if (!array) return [];
27
+ return (
28
+ // eslint-disable-next-line no-bitwise
29
+ new Array(1 << array.length)
30
+ .fill(null)
31
+ // eslint-disable-next-line no-bitwise
32
+ .map((e1, i) => array.filter((e2, j) => i & (1 << j)))
33
+ );
34
+ }
35
+ export const SIMPLIFICATIONS = {
36
+ AIRPLANE: "TRANSIT",
37
+ BICYCLE: "PERSONAL",
38
+ BUS: "TRANSIT",
39
+ CABLE_CAR: "TRANSIT",
40
+ CAR: "CAR",
41
+ FERRY: "TRANSIT",
42
+ FLEX: "SHARED", // TODO: this allows FLEX+WALK. Is this reasonable?
43
+ FUNICULAR: "TRANSIT",
44
+ GONDOLA: "TRANSIT",
45
+ RAIL: "TRANSIT",
46
+ SCOOTER: "PERSONAL",
47
+ SUBWAY: "TRANSIT",
48
+ TRAM: "TRANSIT",
49
+ TRANSIT: "TRANSIT",
50
+ WALK: "WALK"
51
+ };
52
+
53
+ export const TRANSIT_SUBMODES = Object.keys(SIMPLIFICATIONS).filter(
54
+ mode => SIMPLIFICATIONS[mode] === "TRANSIT" && mode !== "TRANSIT"
55
+ );
56
+ export const TRANSIT_SUBMODES_AND_TRANSIT = Object.keys(SIMPLIFICATIONS).filter(
57
+ mode => SIMPLIFICATIONS[mode] === "TRANSIT"
58
+ );
59
+
60
+ export function generateCombinations(params: OTPQueryParams): OTPQueryParams[] {
61
+ const VALID_COMBOS = [
62
+ ["WALK"],
63
+ ["PERSONAL"],
64
+ ["TRANSIT", "SHARED"],
65
+ ["WALK", "SHARED"],
66
+ ["TRANSIT"],
67
+ ["TRANSIT", "PERSONAL"],
68
+ ["TRANSIT", "CAR"]
69
+ ];
70
+
71
+ const BANNED_TOGETHER = ["SCOOTER", "BICYCLE"];
72
+
73
+ // List of the transit submodes that are included in the input params
74
+ const queryTransitSubmodes = params.modes
75
+ .filter(mode => TRANSIT_SUBMODES.includes(mode.mode))
76
+ .map(mode => mode.mode);
77
+
78
+ return (
79
+ combinations(params.modes)
80
+ .filter(combo => {
81
+ if (combo.length === 0) return false;
82
+
83
+ // All current qualifiers currently simplify to "SHARED"
84
+ const simplifiedModes = Array.from(
85
+ new Set(
86
+ combo.map(c => (c.qualifier ? "SHARED" : SIMPLIFICATIONS[c.mode]))
87
+ )
88
+ );
89
+
90
+ // Ensure that if we have one transit mode, then we include ALL transit modes
91
+ if (simplifiedModes.includes("TRANSIT")) {
92
+ const flatModes = combo.map(m => m.mode);
93
+ if (
94
+ combo.reduce((prev, cur) => {
95
+ if (queryTransitSubmodes.includes(cur.mode)) {
96
+ return prev - 1;
97
+ }
98
+ return prev;
99
+ }, queryTransitSubmodes.length) !== 0
100
+ ) {
101
+ return false;
102
+ }
103
+ }
104
+
105
+ // OTP doesn't support multiple non-walk modes
106
+ if (BANNED_TOGETHER.every(m => combo.find(c => c.mode === m)))
107
+ return false;
108
+
109
+ return !!VALID_COMBOS.find(
110
+ vc =>
111
+ simplifiedModes.every(m => vc.includes(m)) &&
112
+ vc.every(m => simplifiedModes.includes(m))
113
+ );
114
+ })
115
+ // create new filter that removes subTransit modes from appearing on their own
116
+ // ONLY IF there's multiple of them!
117
+ .map(combo => ({ ...params, modes: combo }))
118
+ );
119
+ }
120
+
121
+ // eslint-disable-next-line import/prefer-default-export
122
+ export function generateOtp2Query(params: OTPQueryParams): any {
123
+ const { to, from, modeSettings } = params;
124
+
125
+ // This extracts the values from the mode settings to key value pairs
126
+ const modeSettingValues = modeSettings.reduce((prev, cur) => {
127
+ prev[cur.key] = cur.value;
128
+ return prev;
129
+ }, {}) as ModeSettingValues;
130
+
131
+ const {
132
+ walkReluctance,
133
+ wheelchair,
134
+ bikeReluctance,
135
+ carReluctance,
136
+ allowBikeRental
137
+ } = modeSettingValues;
138
+
139
+ return {
140
+ query: print(PlanQuery),
141
+ variables: {
142
+ fromPlace: [from.lat, from.lon].join(","),
143
+ toPlace: [to.lat, to.lon].join(","),
144
+ modes: params.modes,
145
+ allowBikeRental,
146
+ walkReluctance,
147
+ wheelchair,
148
+ bikeReluctance,
149
+ carReluctance
150
+ }
151
+ };
152
+ }
package/src/state.ts ADDED
File without changes
package/tsconfig.json CHANGED
@@ -2,6 +2,7 @@
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
  "skipLibCheck": true