@opentripplanner/core-utils 14.3.0 → 14.3.2
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.
- package/README.md +1 -0
- package/esm/__tests__/__mocks__/three-transfer-itinerary.json +2195 -0
- package/esm/itinerary.js +20 -25
- package/esm/itinerary.js.map +1 -1
- package/esm/three-transfer-itinerary.json +2195 -0
- package/lib/__tests__/__mocks__/three-transfer-itinerary.json +2195 -0
- package/lib/index.js +34 -50
- package/lib/index.js.map +1 -1
- package/lib/itinerary.d.ts.map +1 -1
- package/lib/itinerary.js +560 -532
- package/lib/itinerary.js.map +1 -1
- package/lib/map.js +39 -40
- package/lib/map.js.map +1 -1
- package/lib/query-gen.js +138 -134
- package/lib/query-gen.js.map +1 -1
- package/lib/route.d.ts.map +1 -1
- package/lib/route.js +248 -230
- package/lib/route.js.map +1 -1
- package/lib/storage.js +26 -24
- package/lib/storage.js.map +1 -1
- package/lib/suspense.d.ts.map +1 -1
- package/lib/suspense.js +15 -28
- package/lib/suspense.js.map +1 -1
- package/lib/time.d.ts.map +1 -1
- package/lib/time.js +48 -36
- package/lib/time.js.map +1 -1
- package/lib/ui.js +36 -33
- package/lib/ui.js.map +1 -1
- package/package.json +3 -3
- package/src/__snapshots__/core-utils.story.tsx.snap +1 -1
- package/src/__tests__/__mocks__/three-transfer-itinerary.json +2195 -0
- package/src/__tests__/itinerary.ts +9 -0
- package/src/itinerary.ts +21 -21
- package/tsconfig.tsbuildinfo +1 -1
package/lib/itinerary.js
CHANGED
|
@@ -1,385 +1,387 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
exports.
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.calculateEmissions = calculateEmissions;
|
|
8
|
+
exports.calculatePhysicalActivity = calculatePhysicalActivity;
|
|
9
|
+
exports.calculateTncFares = calculateTncFares;
|
|
10
|
+
exports.containsGeometry = containsGeometry;
|
|
11
|
+
exports.descope = exports.convertGraphQLResponseToLegacy = void 0;
|
|
12
|
+
exports.endsWithGeometry = endsWithGeometry;
|
|
13
|
+
exports.getCompaniesLabelFromNetworks = getCompaniesLabelFromNetworks;
|
|
14
|
+
exports.getCompanyForNetwork = getCompanyForNetwork;
|
|
15
|
+
exports.getCompanyFromLeg = getCompanyFromLeg;
|
|
16
|
+
exports.getDisplayedStopId = getDisplayedStopId;
|
|
17
|
+
exports.getElevationProfile = getElevationProfile;
|
|
18
|
+
exports.getItineraryBounds = getItineraryBounds;
|
|
19
|
+
exports.getItineraryCost = getItineraryCost;
|
|
20
|
+
exports.getLegBounds = getLegBounds;
|
|
21
|
+
exports.getLegCost = getLegCost;
|
|
22
|
+
exports.getLegRouteShortName = exports.getLegRouteName = exports.getLegRouteLongName = void 0;
|
|
23
|
+
exports.getMapColor = getMapColor;
|
|
24
|
+
exports.getTNCLocation = getTNCLocation;
|
|
25
|
+
exports.getTextWidth = getTextWidth;
|
|
26
|
+
exports.getTransitModes = getTransitModes;
|
|
27
|
+
exports.hasBike = hasBike;
|
|
28
|
+
exports.hasCar = hasCar;
|
|
29
|
+
exports.hasHail = hasHail;
|
|
30
|
+
exports.hasMicromobility = hasMicromobility;
|
|
31
|
+
exports.hasRental = hasRental;
|
|
32
|
+
exports.hasTransit = hasTransit;
|
|
33
|
+
exports.isAccessMode = isAccessMode;
|
|
34
|
+
exports.isAdvanceBookingRequired = isAdvanceBookingRequired;
|
|
35
|
+
exports.isBicycle = isBicycle;
|
|
36
|
+
exports.isBicycleRent = isBicycleRent;
|
|
37
|
+
exports.isCar = isCar;
|
|
38
|
+
exports.isCoordinationRequired = isCoordinationRequired;
|
|
39
|
+
exports.isFlex = isFlex;
|
|
40
|
+
exports.isMicromobility = isMicromobility;
|
|
41
|
+
exports.isReservationRequired = isReservationRequired;
|
|
42
|
+
exports.isRideshareLeg = isRideshareLeg;
|
|
43
|
+
exports.isTransit = isTransit;
|
|
44
|
+
exports.isTransitLeg = isTransitLeg;
|
|
45
|
+
exports.isWalk = isWalk;
|
|
46
|
+
exports.legContainsGeometry = legContainsGeometry;
|
|
47
|
+
exports.legDropoffRequiresAdvanceBooking = legDropoffRequiresAdvanceBooking;
|
|
48
|
+
exports.legElevationAtDistance = legElevationAtDistance;
|
|
49
|
+
exports.legLocationAtDistance = legLocationAtDistance;
|
|
50
|
+
exports.mapOldElevationComponentToNew = mapOldElevationComponentToNew;
|
|
51
|
+
exports.startsWithGeometry = startsWithGeometry;
|
|
52
|
+
exports.toSentenceCase = toSentenceCase;
|
|
53
|
+
exports.zeroDollars = exports.transitModes = void 0;
|
|
54
|
+
var _polyline = _interopRequireDefault(require("@mapbox/polyline"));
|
|
55
|
+
var _along = _interopRequireDefault(require("@turf/along"));
|
|
10
56
|
// All OTP transit modes
|
|
11
|
-
exports.transitModes = [
|
|
12
|
-
|
|
13
|
-
"TROLLEYBUS",
|
|
14
|
-
"BUS",
|
|
15
|
-
"SUBWAY",
|
|
16
|
-
"FERRY",
|
|
17
|
-
"RAIL",
|
|
18
|
-
"GONDOLA"
|
|
19
|
-
];
|
|
57
|
+
const transitModes = exports.transitModes = ["TRAM", "TROLLEYBUS", "BUS", "SUBWAY", "FERRY", "RAIL", "GONDOLA"];
|
|
58
|
+
|
|
20
59
|
/**
|
|
21
60
|
* @param {config} config OTP-RR configuration object
|
|
22
61
|
* @return {Array} List of all transit modes defined in config; otherwise default mode list
|
|
23
62
|
*/
|
|
63
|
+
|
|
24
64
|
function getTransitModes(config) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return config.modes.transitModes.map(tm => typeof tm !== "string" ? tm.mode : tm);
|
|
65
|
+
if (!config || !config.modes || !config.modes.transitModes) return transitModes;
|
|
66
|
+
return config.modes.transitModes.map(tm => typeof tm !== "string" ? tm.mode : tm);
|
|
28
67
|
}
|
|
29
|
-
exports.getTransitModes = getTransitModes;
|
|
30
68
|
function isTransitLeg(leg) {
|
|
31
|
-
|
|
69
|
+
return leg.transitLeg;
|
|
32
70
|
}
|
|
33
|
-
exports.isTransitLeg = isTransitLeg;
|
|
34
71
|
function isTransit(mode) {
|
|
35
|
-
|
|
72
|
+
return transitModes.includes(mode) || mode === "TRANSIT";
|
|
36
73
|
}
|
|
37
|
-
|
|
74
|
+
|
|
38
75
|
/**
|
|
39
76
|
* Returns true if the leg pickup rules enabled which require
|
|
40
77
|
* calling ahead for the service to run. "mustPhone" is the only
|
|
41
78
|
* property which encodes this info.
|
|
42
79
|
*/
|
|
43
80
|
function isReservationRequired(leg) {
|
|
44
|
-
|
|
81
|
+
return (leg === null || leg === void 0 ? void 0 : leg.boardRule) === "mustPhone" || (leg === null || leg === void 0 ? void 0 : leg.alightRule) === "mustPhone";
|
|
45
82
|
}
|
|
46
|
-
exports.isReservationRequired = isReservationRequired;
|
|
47
83
|
/**
|
|
48
84
|
* Returns true if a user must ask the driver to let the user off
|
|
49
85
|
* or if the user must flag the driver down for pickup.
|
|
50
86
|
* "coordinateWithDriver" in board/alight rule encodes this info.
|
|
51
87
|
*/
|
|
52
88
|
function isCoordinationRequired(leg) {
|
|
53
|
-
|
|
54
|
-
(leg === null || leg === void 0 ? void 0 : leg.alightRule) === "coordinateWithDriver");
|
|
89
|
+
return (leg === null || leg === void 0 ? void 0 : leg.boardRule) === "coordinateWithDriver" || (leg === null || leg === void 0 ? void 0 : leg.alightRule) === "coordinateWithDriver";
|
|
55
90
|
}
|
|
56
|
-
exports.isCoordinationRequired = isCoordinationRequired;
|
|
57
91
|
function containsGeometry(place) {
|
|
58
|
-
|
|
59
|
-
|
|
92
|
+
var _place$stop, _place$stop2;
|
|
93
|
+
return (place === null || place === void 0 || (_place$stop = place.stop) === null || _place$stop === void 0 ? void 0 : _place$stop.geometries) !== null && (place === null || place === void 0 || (_place$stop2 = place.stop) === null || _place$stop2 === void 0 ? void 0 : _place$stop2.geometries) !== undefined;
|
|
60
94
|
}
|
|
61
|
-
exports.containsGeometry = containsGeometry;
|
|
62
95
|
function endsWithGeometry(leg) {
|
|
63
|
-
|
|
96
|
+
return containsGeometry(leg === null || leg === void 0 ? void 0 : leg.to);
|
|
64
97
|
}
|
|
65
|
-
exports.endsWithGeometry = endsWithGeometry;
|
|
66
98
|
function startsWithGeometry(leg) {
|
|
67
|
-
|
|
99
|
+
return containsGeometry(leg === null || leg === void 0 ? void 0 : leg.from);
|
|
68
100
|
}
|
|
69
|
-
exports.startsWithGeometry = startsWithGeometry;
|
|
70
101
|
function legContainsGeometry(leg) {
|
|
71
|
-
|
|
102
|
+
return endsWithGeometry(leg) || startsWithGeometry(leg);
|
|
72
103
|
}
|
|
73
|
-
exports.legContainsGeometry = legContainsGeometry;
|
|
74
104
|
function isAdvanceBookingRequired(info) {
|
|
75
|
-
|
|
76
|
-
|
|
105
|
+
var _info$latestBookingTi;
|
|
106
|
+
return (info === null || info === void 0 || (_info$latestBookingTi = info.latestBookingTime) === null || _info$latestBookingTi === void 0 ? void 0 : _info$latestBookingTi.daysPrior) > 0;
|
|
77
107
|
}
|
|
78
|
-
exports.isAdvanceBookingRequired = isAdvanceBookingRequired;
|
|
79
108
|
function legDropoffRequiresAdvanceBooking(leg) {
|
|
80
|
-
|
|
109
|
+
return isAdvanceBookingRequired(leg === null || leg === void 0 ? void 0 : leg.dropOffBookingInfo);
|
|
81
110
|
}
|
|
82
|
-
|
|
111
|
+
|
|
83
112
|
/**
|
|
84
113
|
* The two rules checked by the above two functions are the only values
|
|
85
114
|
* returned by OTP when a leg is a flex leg.
|
|
86
115
|
*/
|
|
87
116
|
function isFlex(leg) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
117
|
+
var _leg$stopCalls;
|
|
118
|
+
return (leg === null || leg === void 0 || (_leg$stopCalls = leg.stopCalls) === null || _leg$stopCalls === void 0 ? void 0 : _leg$stopCalls.some(call => {
|
|
119
|
+
var _call$stopLocation;
|
|
120
|
+
return (// Flex calls are "Location" or "LocationGroup"
|
|
121
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
122
|
+
call === null || call === void 0 || (_call$stopLocation = call.stopLocation) === null || _call$stopLocation === void 0 ? void 0 : _call$stopLocation.__typename.startsWith("Location")
|
|
123
|
+
);
|
|
124
|
+
})) || false;
|
|
93
125
|
}
|
|
94
|
-
exports.isFlex = isFlex;
|
|
95
126
|
function isRideshareLeg(leg) {
|
|
96
|
-
|
|
97
|
-
|
|
127
|
+
var _leg$rideHailingEstim;
|
|
128
|
+
return !!((_leg$rideHailingEstim = leg.rideHailingEstimate) !== null && _leg$rideHailingEstim !== void 0 && (_leg$rideHailingEstim = _leg$rideHailingEstim.provider) !== null && _leg$rideHailingEstim !== void 0 && _leg$rideHailingEstim.id);
|
|
98
129
|
}
|
|
99
|
-
exports.isRideshareLeg = isRideshareLeg;
|
|
100
130
|
function isWalk(mode) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return mode === "WALK";
|
|
131
|
+
if (!mode) return false;
|
|
132
|
+
return mode === "WALK";
|
|
104
133
|
}
|
|
105
|
-
exports.isWalk = isWalk;
|
|
106
134
|
function isBicycle(mode) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return mode === "BICYCLE";
|
|
135
|
+
if (!mode) return false;
|
|
136
|
+
return mode === "BICYCLE";
|
|
110
137
|
}
|
|
111
|
-
exports.isBicycle = isBicycle;
|
|
112
138
|
function isBicycleRent(mode) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return mode === "BICYCLE_RENT";
|
|
139
|
+
if (!mode) return false;
|
|
140
|
+
return mode === "BICYCLE_RENT";
|
|
116
141
|
}
|
|
117
|
-
exports.isBicycleRent = isBicycleRent;
|
|
118
142
|
function isCar(mode) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return mode.startsWith("CAR");
|
|
143
|
+
if (!mode) return false;
|
|
144
|
+
return mode.startsWith("CAR");
|
|
122
145
|
}
|
|
123
|
-
exports.isCar = isCar;
|
|
124
146
|
function isMicromobility(mode) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return mode.startsWith("MICROMOBILITY") || mode.startsWith("SCOOTER");
|
|
147
|
+
if (!mode) return false;
|
|
148
|
+
return mode.startsWith("MICROMOBILITY") || mode.startsWith("SCOOTER");
|
|
128
149
|
}
|
|
129
|
-
exports.isMicromobility = isMicromobility;
|
|
130
150
|
function isAccessMode(mode) {
|
|
131
|
-
|
|
132
|
-
isBicycle(mode) ||
|
|
133
|
-
isBicycleRent(mode) ||
|
|
134
|
-
isCar(mode) ||
|
|
135
|
-
isMicromobility(mode));
|
|
151
|
+
return isWalk(mode) || isBicycle(mode) || isBicycleRent(mode) || isCar(mode) || isMicromobility(mode);
|
|
136
152
|
}
|
|
137
|
-
|
|
153
|
+
|
|
138
154
|
/**
|
|
139
155
|
* @param {string} modesStr a comma-separated list of OTP modes
|
|
140
156
|
* @return {boolean} whether any of the modes are transit modes
|
|
141
157
|
*/
|
|
142
158
|
function hasTransit(modesStr) {
|
|
143
|
-
|
|
159
|
+
return modesStr.split(",").some(mode => isTransit(mode));
|
|
144
160
|
}
|
|
145
|
-
|
|
161
|
+
|
|
146
162
|
/**
|
|
147
163
|
* @param {string} modesStr a comma-separated list of OTP modes
|
|
148
164
|
* @return {boolean} whether any of the modes are car-based modes
|
|
149
165
|
*/
|
|
150
166
|
function hasCar(modesStr) {
|
|
151
|
-
|
|
167
|
+
return modesStr.split(",").some(mode => isCar(mode));
|
|
152
168
|
}
|
|
153
|
-
|
|
169
|
+
|
|
154
170
|
/**
|
|
155
171
|
* @param {string} modesStr a comma-separated list of OTP modes
|
|
156
172
|
* @return {boolean} whether any of the modes are bicycle-based modes
|
|
157
173
|
*/
|
|
158
174
|
function hasBike(modesStr) {
|
|
159
|
-
|
|
160
|
-
.split(",")
|
|
161
|
-
.some(mode => isBicycle(mode) || isBicycleRent(mode));
|
|
175
|
+
return modesStr.split(",").some(mode => isBicycle(mode) || isBicycleRent(mode));
|
|
162
176
|
}
|
|
163
|
-
|
|
177
|
+
|
|
164
178
|
/**
|
|
165
179
|
* @param {string} modesStr a comma-separated list of OTP modes
|
|
166
180
|
* @return {boolean} whether any of the modes are micromobility-based modes
|
|
167
181
|
*/
|
|
168
182
|
function hasMicromobility(modesStr) {
|
|
169
|
-
|
|
183
|
+
return modesStr.split(",").some(mode => isMicromobility(mode));
|
|
170
184
|
}
|
|
171
|
-
|
|
185
|
+
|
|
172
186
|
/**
|
|
173
187
|
* @param {string} modesStr a comma-separated list of OTP modes
|
|
174
188
|
* @return {boolean} whether any of the modes is a hailing mode
|
|
175
189
|
*/
|
|
176
190
|
function hasHail(modesStr) {
|
|
177
|
-
|
|
191
|
+
return modesStr.split(",").some(mode => mode.indexOf("_HAIL") > -1);
|
|
178
192
|
}
|
|
179
|
-
|
|
193
|
+
|
|
180
194
|
/**
|
|
181
195
|
* @param {string} modesStr a comma-separated list of OTP modes
|
|
182
196
|
* @return {boolean} whether any of the modes is a rental mode
|
|
183
197
|
*/
|
|
184
198
|
function hasRental(modesStr) {
|
|
185
|
-
|
|
199
|
+
return modesStr.split(",").some(mode => mode.indexOf("_RENT") > -1);
|
|
186
200
|
}
|
|
187
|
-
exports.hasRental = hasRental;
|
|
188
201
|
function getMapColor(mode) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return "#080";
|
|
202
|
-
if (mode === "TRAM")
|
|
203
|
-
return "#800";
|
|
204
|
-
if (mode === "FERRY")
|
|
205
|
-
return "#008";
|
|
206
|
-
if (mode === "CAR")
|
|
207
|
-
return "#444";
|
|
208
|
-
if (mode === "MICROMOBILITY" || mode === "SCOOTER")
|
|
209
|
-
return "#f5a729";
|
|
210
|
-
return "#aaa";
|
|
202
|
+
mode = mode || this.get("mode");
|
|
203
|
+
if (mode === "WALK") return "#444";
|
|
204
|
+
if (mode === "BICYCLE") return "#0073e5";
|
|
205
|
+
if (mode === "SUBWAY") return "#e60000";
|
|
206
|
+
if (mode === "RAIL") return "#b00";
|
|
207
|
+
if (mode === "BUS") return "#080";
|
|
208
|
+
if (mode === "TROLLEYBUS") return "#080";
|
|
209
|
+
if (mode === "TRAM") return "#800";
|
|
210
|
+
if (mode === "FERRY") return "#008";
|
|
211
|
+
if (mode === "CAR") return "#444";
|
|
212
|
+
if (mode === "MICROMOBILITY" || mode === "SCOOTER") return "#f5a729";
|
|
213
|
+
return "#aaa";
|
|
211
214
|
}
|
|
212
|
-
exports.getMapColor = getMapColor;
|
|
213
215
|
function toSentenceCase(str) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
if (str == null) {
|
|
217
|
+
return "";
|
|
218
|
+
}
|
|
219
|
+
str = String(str);
|
|
220
|
+
return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
|
|
219
221
|
}
|
|
220
|
-
|
|
222
|
+
|
|
221
223
|
/**
|
|
222
224
|
* Derive the company string based on mode and network associated with leg.
|
|
223
225
|
*/
|
|
224
226
|
function getCompanyFromLeg(leg) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
227
|
+
var _from$vehicleRentalSt;
|
|
228
|
+
if (!leg) return null;
|
|
229
|
+
const {
|
|
230
|
+
from,
|
|
231
|
+
mode,
|
|
232
|
+
rentedBike,
|
|
233
|
+
rentedCar,
|
|
234
|
+
rentedVehicle,
|
|
235
|
+
rideHailingEstimate
|
|
236
|
+
} = leg;
|
|
237
|
+
if (mode === "CAR" && rentedCar) {
|
|
238
|
+
return from.networks[0];
|
|
239
|
+
}
|
|
240
|
+
if (mode === "CAR" && rideHailingEstimate) {
|
|
241
|
+
return rideHailingEstimate.provider.id;
|
|
242
|
+
}
|
|
243
|
+
if (mode === "BICYCLE" && rentedBike && from.networks) {
|
|
244
|
+
return from.networks[0];
|
|
245
|
+
}
|
|
246
|
+
if (from.rentalVehicle) {
|
|
247
|
+
return from.rentalVehicle.network;
|
|
248
|
+
}
|
|
249
|
+
if ((_from$vehicleRentalSt = from.vehicleRentalStation) !== null && _from$vehicleRentalSt !== void 0 && _from$vehicleRentalSt.rentalNetwork) {
|
|
250
|
+
return from.vehicleRentalStation.rentalNetwork.networkId;
|
|
251
|
+
}
|
|
252
|
+
if ((mode === "MICROMOBILITY" || mode === "SCOOTER") && rentedVehicle && from.networks) {
|
|
253
|
+
return from.networks[0];
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
250
256
|
}
|
|
251
|
-
exports.getCompanyFromLeg = getCompanyFromLeg;
|
|
252
257
|
function getItineraryBounds(itinerary) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
exports.getItineraryBounds = getItineraryBounds;
|
|
258
|
+
let coords = [];
|
|
259
|
+
itinerary.legs.forEach(leg => {
|
|
260
|
+
const legCoords = _polyline.default.toGeoJSON(leg.legGeometry.points).coordinates.map(c => [c[1], c[0]]);
|
|
261
|
+
coords = [...coords, ...legCoords];
|
|
262
|
+
});
|
|
263
|
+
return coords;
|
|
264
|
+
}
|
|
265
|
+
|
|
263
266
|
/**
|
|
264
267
|
* Return a coords object that encloses the given leg's geometry.
|
|
265
268
|
*/
|
|
266
269
|
function getLegBounds(leg) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
exports.getLegBounds = getLegBounds;
|
|
270
|
+
const coords = _polyline.default.toGeoJSON(leg.legGeometry.points).coordinates.map(c => [c[1], c[0]]);
|
|
271
|
+
|
|
272
|
+
// in certain cases, there might be zero-length coordinates in the leg
|
|
273
|
+
// geometry. In these cases, build us an array of coordinates using the from
|
|
274
|
+
// and to data of the leg.
|
|
275
|
+
if (coords.length === 0) {
|
|
276
|
+
coords.push([leg.from.lat, leg.from.lon], [leg.to.lat, leg.to.lon]);
|
|
277
|
+
}
|
|
278
|
+
return coords;
|
|
279
|
+
}
|
|
280
|
+
|
|
279
281
|
/* Returns an interpolated lat-lon at a specified distance along a leg */
|
|
282
|
+
|
|
280
283
|
function legLocationAtDistance(leg, distance) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
catch (e) {
|
|
291
|
-
// FIXME handle error!
|
|
284
|
+
if (!leg.legGeometry) return null;
|
|
285
|
+
try {
|
|
286
|
+
const line = _polyline.default.toGeoJSON(leg.legGeometry.points);
|
|
287
|
+
const pt = (0, _along.default)(line, distance, {
|
|
288
|
+
units: "meters"
|
|
289
|
+
});
|
|
290
|
+
if (pt && pt.geometry && pt.geometry.coordinates) {
|
|
291
|
+
return [pt.geometry.coordinates[1], pt.geometry.coordinates[0]];
|
|
292
292
|
}
|
|
293
|
-
|
|
293
|
+
} catch (e) {
|
|
294
|
+
// FIXME handle error!
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
294
297
|
}
|
|
295
|
-
|
|
298
|
+
|
|
296
299
|
/* Returns an interpolated elevation at a specified distance along a leg */
|
|
300
|
+
|
|
297
301
|
function legElevationAtDistance(points, distance) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
traversed += elevDistanceSpan;
|
|
302
|
+
// Iterate through the combined elevation profile
|
|
303
|
+
let traversed = 0;
|
|
304
|
+
// If first point distance is not zero, insert starting point at zero with
|
|
305
|
+
// null elevation. Encountering this value should trigger the warning below.
|
|
306
|
+
if (points[0][0] > 0) {
|
|
307
|
+
points.unshift([0, null]);
|
|
308
|
+
}
|
|
309
|
+
for (let i = 1; i < points.length; i++) {
|
|
310
|
+
const start = points[i - 1];
|
|
311
|
+
const elevDistanceSpan = points[i][0] - start[0];
|
|
312
|
+
if (distance >= traversed && distance <= traversed + elevDistanceSpan) {
|
|
313
|
+
// Distance falls within this point and the previous one;
|
|
314
|
+
// compute & return interpolated elevation value
|
|
315
|
+
if (start[1] === null) {
|
|
316
|
+
console.warn("Elevation value does not exist for distance.", distance, traversed);
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
const pct = (distance - traversed) / elevDistanceSpan;
|
|
320
|
+
const elevSpan = points[i][1] - start[1];
|
|
321
|
+
return start[1] + elevSpan * pct;
|
|
320
322
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
+
traversed += elevDistanceSpan;
|
|
324
|
+
}
|
|
325
|
+
console.warn("Elevation value does not exist for distance.", distance, traversed);
|
|
326
|
+
return null;
|
|
323
327
|
}
|
|
324
|
-
exports.legElevationAtDistance = legElevationAtDistance;
|
|
325
328
|
function mapOldElevationComponentToNew(oldElev) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
329
|
+
return {
|
|
330
|
+
distance: oldElev.first,
|
|
331
|
+
elevation: oldElev.second
|
|
332
|
+
};
|
|
330
333
|
}
|
|
331
|
-
|
|
334
|
+
|
|
332
335
|
// Iterate through the steps, building the array of elevation points and
|
|
333
336
|
// keeping track of the minimum and maximum elevations reached
|
|
334
337
|
function getElevationProfile(steps, unitConversion = 1) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
exports.getElevationProfile = getElevationProfile;
|
|
338
|
+
let minElev = 100000;
|
|
339
|
+
let maxElev = -100000;
|
|
340
|
+
let traversed = 0;
|
|
341
|
+
let gain = 0;
|
|
342
|
+
let loss = 0;
|
|
343
|
+
let previous = null;
|
|
344
|
+
const points = [];
|
|
345
|
+
steps.forEach(step => {
|
|
346
|
+
var _step$elevation;
|
|
347
|
+
// Support for old REST response data (in step.elevation)
|
|
348
|
+
const stepElevationProfile = step.elevationProfile || Array.isArray(step.elevation) && ((_step$elevation = step.elevation) === null || _step$elevation === void 0 ? void 0 : _step$elevation.map(mapOldElevationComponentToNew));
|
|
349
|
+
if (!stepElevationProfile || stepElevationProfile.length === 0) {
|
|
350
|
+
traversed += step.distance;
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
for (let i = 0; i < stepElevationProfile.length; i++) {
|
|
354
|
+
const elev = stepElevationProfile[i];
|
|
355
|
+
if (previous) {
|
|
356
|
+
const diff = (elev.elevation - previous.elevation) * unitConversion;
|
|
357
|
+
if (diff > 0) gain += diff;else loss += diff;
|
|
358
|
+
}
|
|
359
|
+
if (i === 0 && elev.distance !== 0) {
|
|
360
|
+
// console.warn(`No elevation data available for step ${stepIndex}-${i} at beginning of segment`, elev)
|
|
361
|
+
}
|
|
362
|
+
const convertedElevation = elev.elevation * unitConversion;
|
|
363
|
+
if (convertedElevation < minElev) minElev = convertedElevation;
|
|
364
|
+
if (convertedElevation > maxElev) maxElev = convertedElevation;
|
|
365
|
+
points.push([traversed + elev.distance, elev.elevation]);
|
|
366
|
+
// Insert "filler" point if the last point in elevation profile does not
|
|
367
|
+
// reach the full distance of the step.
|
|
368
|
+
if (i === stepElevationProfile.length - 1 && elev.distance !== step.distance) {
|
|
369
|
+
// points.push([traversed + step.distance, elev.second])
|
|
370
|
+
}
|
|
371
|
+
previous = elev;
|
|
372
|
+
}
|
|
373
|
+
traversed += step.distance;
|
|
374
|
+
});
|
|
375
|
+
return {
|
|
376
|
+
maxElev,
|
|
377
|
+
minElev,
|
|
378
|
+
points,
|
|
379
|
+
traversed,
|
|
380
|
+
gain,
|
|
381
|
+
loss
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
383
385
|
/**
|
|
384
386
|
* Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
|
|
385
387
|
*
|
|
@@ -389,27 +391,28 @@ exports.getElevationProfile = getElevationProfile;
|
|
|
389
391
|
* @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
|
|
390
392
|
*/
|
|
391
393
|
function getTextWidth(text, font = "22px Arial") {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
394
|
+
// Create custom type for function including reused canvas object
|
|
395
|
+
|
|
396
|
+
// reuse canvas object for better performance
|
|
397
|
+
const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
|
|
398
|
+
const context = canvas.getContext("2d");
|
|
399
|
+
context.font = font;
|
|
400
|
+
const metrics = context.measureText(text);
|
|
401
|
+
return metrics.width;
|
|
402
|
+
}
|
|
403
|
+
|
|
401
404
|
/**
|
|
402
405
|
* Get the configured company object for the given network string if the company
|
|
403
406
|
* has been defined in the provided companies array config.
|
|
404
407
|
*/
|
|
405
408
|
function getCompanyForNetwork(networkString, companies = []) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
409
|
+
const company = companies.find(co => co.id === networkString);
|
|
410
|
+
if (!company) {
|
|
411
|
+
console.warn(`No company found in config.yml that matches rented vehicle network: ${networkString}`, companies);
|
|
412
|
+
}
|
|
413
|
+
return company;
|
|
411
414
|
}
|
|
412
|
-
|
|
415
|
+
|
|
413
416
|
/**
|
|
414
417
|
* Get a string label to display from a list of vehicle rental networks. Returns
|
|
415
418
|
* empty string if no networks provided.
|
|
@@ -419,60 +422,57 @@ exports.getCompanyForNetwork = getCompanyForNetwork;
|
|
|
419
422
|
* @return {string} A label for use in presentation on a website.
|
|
420
423
|
*/
|
|
421
424
|
function getCompaniesLabelFromNetworks(networks, companies = []) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
return (Array.isArray(networks) ? networks : [networks])
|
|
425
|
-
.map(network => getCompanyForNetwork(network, companies))
|
|
426
|
-
.filter(co => !!co)
|
|
427
|
-
.map(co => co.label)
|
|
428
|
-
.join("/");
|
|
425
|
+
if (!networks) return "";
|
|
426
|
+
return (Array.isArray(networks) ? networks : [networks]).map(network => getCompanyForNetwork(network, companies)).filter(co => !!co).map(co => co.label).join("/");
|
|
429
427
|
}
|
|
430
|
-
exports.getCompaniesLabelFromNetworks = getCompaniesLabelFromNetworks;
|
|
431
428
|
function getTNCLocation(leg, type) {
|
|
432
|
-
|
|
433
|
-
|
|
429
|
+
const location = leg[type];
|
|
430
|
+
return `${location.lat.toFixed(5)},${location.lon.toFixed(5)}`;
|
|
434
431
|
}
|
|
435
|
-
exports.getTNCLocation = getTNCLocation;
|
|
436
432
|
function calculatePhysicalActivity(itinerary) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
}
|
|
452
|
-
exports.calculatePhysicalActivity = calculatePhysicalActivity;
|
|
433
|
+
let walkDuration = 0;
|
|
434
|
+
let bikeDuration = 0;
|
|
435
|
+
itinerary.legs.forEach(leg => {
|
|
436
|
+
if (leg.mode.startsWith("WALK")) walkDuration += leg.duration;
|
|
437
|
+
if (leg.mode.startsWith("BICYCLE")) bikeDuration += leg.duration;
|
|
438
|
+
});
|
|
439
|
+
const caloriesBurned = walkDuration / 3600 * 280 + bikeDuration / 3600 * 290;
|
|
440
|
+
return {
|
|
441
|
+
bikeDuration,
|
|
442
|
+
caloriesBurned,
|
|
443
|
+
walkDuration
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
453
447
|
/**
|
|
454
448
|
* For an itinerary, calculates the TNC fares and returns an object with
|
|
455
449
|
* these values and currency info.
|
|
456
450
|
* It is assumed that the same currency is used for all TNC legs.
|
|
457
451
|
*/
|
|
458
452
|
function calculateTncFares(itinerary) {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
453
|
+
return itinerary.legs.filter(leg => leg.mode === "CAR" && leg.rideHailingEstimate).reduce(({
|
|
454
|
+
maxTNCFare,
|
|
455
|
+
minTNCFare
|
|
456
|
+
}, {
|
|
457
|
+
rideHailingEstimate
|
|
458
|
+
}) => {
|
|
459
|
+
const {
|
|
460
|
+
minPrice,
|
|
461
|
+
maxPrice
|
|
462
|
+
} = rideHailingEstimate;
|
|
463
|
+
return {
|
|
464
|
+
// Assumes a single currency for entire itinerary.
|
|
465
|
+
currencyCode: minPrice.currency.code,
|
|
466
|
+
maxTNCFare: maxTNCFare + maxPrice.amount,
|
|
467
|
+
minTNCFare: minTNCFare + minPrice.amount
|
|
468
|
+
};
|
|
469
|
+
}, {
|
|
470
|
+
currencyCode: null,
|
|
471
|
+
maxTNCFare: 0,
|
|
472
|
+
minTNCFare: 0
|
|
473
|
+
});
|
|
474
474
|
}
|
|
475
|
-
|
|
475
|
+
|
|
476
476
|
/**
|
|
477
477
|
* Sources:
|
|
478
478
|
* - https://www.itf-oecd.org/sites/default/files/docs/environmental-performance-new-mobility.pdf
|
|
@@ -481,23 +481,24 @@ exports.calculateTncFares = calculateTncFares;
|
|
|
481
481
|
* Other values extrapolated.
|
|
482
482
|
*/
|
|
483
483
|
const CARBON_INTENSITY_DEFAULTS = {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
484
|
+
walk: 0.026,
|
|
485
|
+
bicycle: 0.017,
|
|
486
|
+
car: 0.162,
|
|
487
|
+
tram: 0.066,
|
|
488
|
+
trolleybus: 0.066,
|
|
489
|
+
subway: 0.066,
|
|
490
|
+
rail: 0.066,
|
|
491
|
+
bus: 0.09,
|
|
492
|
+
ferry: 0.082,
|
|
493
|
+
cable_car: 0.021,
|
|
494
|
+
gondola: 0.021,
|
|
495
|
+
funicular: 0.066,
|
|
496
|
+
transit: 0.066,
|
|
497
|
+
leg_switch: 0,
|
|
498
|
+
airplane: 0.382,
|
|
499
|
+
micromobility: 0.095
|
|
500
500
|
};
|
|
501
|
+
|
|
501
502
|
/**
|
|
502
503
|
* @param {itinerary} itinerary OTP trip itinierary, only legs is required.
|
|
503
504
|
* @param {carbonIntensity} carbonIntensity carbon intensity by mode in grams/meter
|
|
@@ -507,30 +508,30 @@ const CARBON_INTENSITY_DEFAULTS = {
|
|
|
507
508
|
function calculateEmissions(
|
|
508
509
|
// This type makes all the properties from Itinerary optional except legs.
|
|
509
510
|
itinerary, carbonIntensity = {}, units) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
533
|
-
|
|
511
|
+
var _itinerary$legs;
|
|
512
|
+
// Apply defaults for any values that we don't have.
|
|
513
|
+
const carbonIntensityWithDefaults = {
|
|
514
|
+
...CARBON_INTENSITY_DEFAULTS,
|
|
515
|
+
...carbonIntensity
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// Distance is in meters, totalCarbon is in grams
|
|
519
|
+
const totalCarbon = (itinerary === null || itinerary === void 0 || (_itinerary$legs = itinerary.legs) === null || _itinerary$legs === void 0 ? void 0 : _itinerary$legs.reduce((total, leg) => {
|
|
520
|
+
return (leg.distance * carbonIntensityWithDefaults[leg.mode.toLowerCase()] || 0) + total;
|
|
521
|
+
}, 0)) || 0;
|
|
522
|
+
switch (units) {
|
|
523
|
+
case "ounce":
|
|
524
|
+
return totalCarbon / 28.35;
|
|
525
|
+
case "kilogram":
|
|
526
|
+
return totalCarbon / 1000;
|
|
527
|
+
case "pound":
|
|
528
|
+
return totalCarbon / 454;
|
|
529
|
+
case "gram":
|
|
530
|
+
default:
|
|
531
|
+
return totalCarbon;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
534
535
|
/**
|
|
535
536
|
* Returns the user-facing stop id to display for a stop or place, using the following priority:
|
|
536
537
|
* 1. stop code,
|
|
@@ -538,29 +539,38 @@ exports.calculateEmissions = calculateEmissions;
|
|
|
538
539
|
* 3. stop id, whether null or not (this is the fallback case).
|
|
539
540
|
*/
|
|
540
541
|
function getDisplayedStopId(placeOrStop) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
542
|
+
var _stopId;
|
|
543
|
+
let stopId;
|
|
544
|
+
let stopCode;
|
|
545
|
+
if ("stopId" in placeOrStop) {
|
|
546
|
+
({
|
|
547
|
+
stopCode,
|
|
548
|
+
stopId
|
|
549
|
+
} = placeOrStop);
|
|
550
|
+
} else if ("id" in placeOrStop) {
|
|
551
|
+
({
|
|
552
|
+
code: stopCode,
|
|
553
|
+
id: stopId
|
|
554
|
+
} = placeOrStop);
|
|
555
|
+
}
|
|
556
|
+
return stopCode || ((_stopId = stopId) === null || _stopId === void 0 ? void 0 : _stopId.split(":")[1]) || stopId;
|
|
557
|
+
}
|
|
558
|
+
|
|
552
559
|
/**
|
|
553
560
|
* Removes the first part of the OTP standard scope (":"), if it is present
|
|
554
561
|
* @param item String that is potentially scoped with `:` character
|
|
555
562
|
* @returns descoped string
|
|
556
563
|
*/
|
|
557
|
-
const descope =
|
|
564
|
+
const descope = item => {
|
|
565
|
+
var _item$split;
|
|
566
|
+
return item === null || item === void 0 || (_item$split = item.split(":")) === null || _item$split === void 0 ? void 0 : _item$split[1];
|
|
567
|
+
};
|
|
558
568
|
exports.descope = descope;
|
|
559
|
-
const zeroDollars =
|
|
560
|
-
|
|
561
|
-
|
|
569
|
+
const zeroDollars = currency => ({
|
|
570
|
+
amount: 0,
|
|
571
|
+
currency
|
|
562
572
|
});
|
|
563
|
-
|
|
573
|
+
|
|
564
574
|
/**
|
|
565
575
|
* Extracts useful data from the fare products on a leg, such as the leg cost and transfer info.
|
|
566
576
|
* @param leg Leg with Fares v2 information
|
|
@@ -572,53 +582,56 @@ exports.zeroDollars = zeroDollars;
|
|
|
572
582
|
* all the information needed, but the other fields are kept to
|
|
573
583
|
* make the transition to Fares V2 less jarring.
|
|
574
584
|
*/
|
|
585
|
+
exports.zeroDollars = zeroDollars;
|
|
575
586
|
function getLegCost(leg, mediumId, riderCategoryId, seenFareIds) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const { currency } = fare.product.price;
|
|
598
|
-
return {
|
|
599
|
-
id: fare.id,
|
|
600
|
-
product: {
|
|
601
|
-
...fare.product,
|
|
602
|
-
legPrice: alreadySeen ? (0, exports.zeroDollars)(currency) : fare.product.price
|
|
603
|
-
}
|
|
604
|
-
};
|
|
605
|
-
})
|
|
606
|
-
.sort((a, b) => { var _a, _b, _c, _d; return ((_b = (_a = a.product) === null || _a === void 0 ? void 0 : _a.legPrice) === null || _b === void 0 ? void 0 : _b.amount) - ((_d = (_c = b.product) === null || _c === void 0 ? void 0 : _c.legPrice) === null || _d === void 0 ? void 0 : _d.amount); });
|
|
607
|
-
// Return the cheapest, but include other matches as well
|
|
608
|
-
const cheapestRelevantFareProduct = relevantFareProducts[0];
|
|
609
|
-
// TODO: return one object here instead of dumbing it down?
|
|
587
|
+
if (!leg.fareProducts) return {
|
|
588
|
+
price: undefined
|
|
589
|
+
};
|
|
590
|
+
const relevantFareProducts = leg.fareProducts.filter(({
|
|
591
|
+
product
|
|
592
|
+
}) => {
|
|
593
|
+
var _product$riderCategor, _product$riderCategor2, _product$medium, _product$medium2;
|
|
594
|
+
// riderCategory and medium can be specifically defined as null to handle
|
|
595
|
+
// generic GTFS based fares from OTP when there is no fare model
|
|
596
|
+
|
|
597
|
+
// Remove (optional) agency scoping
|
|
598
|
+
const productRiderCategoryId = descope(product === null || product === void 0 || (_product$riderCategor = product.riderCategory) === null || _product$riderCategor === void 0 ? void 0 : _product$riderCategor.id) || (product === null || product === void 0 || (_product$riderCategor2 = product.riderCategory) === null || _product$riderCategor2 === void 0 ? void 0 : _product$riderCategor2.id) || null;
|
|
599
|
+
const productMediaId = descope(product === null || product === void 0 || (_product$medium = product.medium) === null || _product$medium === void 0 ? void 0 : _product$medium.id) || (product === null || product === void 0 || (_product$medium2 = product.medium) === null || _product$medium2 === void 0 ? void 0 : _product$medium2.id) || null;
|
|
600
|
+
return productRiderCategoryId === riderCategoryId && productMediaId === mediumId && (// Make sure there's a price
|
|
601
|
+
// Some fare products don't have a price at all.
|
|
602
|
+
product === null || product === void 0 ? void 0 : product.price);
|
|
603
|
+
}).map(fare => {
|
|
604
|
+
const alreadySeen = (seenFareIds === null || seenFareIds === void 0 ? void 0 : seenFareIds.indexOf(fare.id)) > -1;
|
|
605
|
+
const {
|
|
606
|
+
currency
|
|
607
|
+
} = fare.product.price;
|
|
610
608
|
return {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
"DependentFareProduct",
|
|
617
|
-
price: cheapestRelevantFareProduct === null || cheapestRelevantFareProduct === void 0 ? void 0 : cheapestRelevantFareProduct.product.legPrice,
|
|
618
|
-
productUseId: cheapestRelevantFareProduct === null || cheapestRelevantFareProduct === void 0 ? void 0 : cheapestRelevantFareProduct.id
|
|
609
|
+
id: fare.id,
|
|
610
|
+
product: {
|
|
611
|
+
...fare.product,
|
|
612
|
+
legPrice: alreadySeen ? zeroDollars(currency) : fare.product.price
|
|
613
|
+
}
|
|
619
614
|
};
|
|
615
|
+
}).sort((a, b) => {
|
|
616
|
+
var _a$product, _b$product;
|
|
617
|
+
return ((_a$product = a.product) === null || _a$product === void 0 || (_a$product = _a$product.legPrice) === null || _a$product === void 0 ? void 0 : _a$product.amount) - ((_b$product = b.product) === null || _b$product === void 0 || (_b$product = _b$product.legPrice) === null || _b$product === void 0 ? void 0 : _b$product.amount);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
// Return the cheapest, but include other matches as well
|
|
621
|
+
const cheapestRelevantFareProduct = relevantFareProducts[0];
|
|
622
|
+
|
|
623
|
+
// TODO: return one object here instead of dumbing it down?
|
|
624
|
+
return {
|
|
625
|
+
alternateFareProducts: relevantFareProducts.splice(1).map(fp => fp.product),
|
|
626
|
+
appliedFareProduct: cheapestRelevantFareProduct === null || cheapestRelevantFareProduct === void 0 ? void 0 : cheapestRelevantFareProduct.product,
|
|
627
|
+
isDependent:
|
|
628
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
629
|
+
(cheapestRelevantFareProduct === null || cheapestRelevantFareProduct === void 0 ? void 0 : cheapestRelevantFareProduct.product.__typename) === "DependentFareProduct",
|
|
630
|
+
price: cheapestRelevantFareProduct === null || cheapestRelevantFareProduct === void 0 ? void 0 : cheapestRelevantFareProduct.product.legPrice,
|
|
631
|
+
productUseId: cheapestRelevantFareProduct === null || cheapestRelevantFareProduct === void 0 ? void 0 : cheapestRelevantFareProduct.id
|
|
632
|
+
};
|
|
620
633
|
}
|
|
621
|
-
|
|
634
|
+
|
|
622
635
|
/**
|
|
623
636
|
* Returns the total itinerary cost for a given set of legs.
|
|
624
637
|
* @param legs Itinerary legs with fare products (must have used getLegsWithFares)
|
|
@@ -628,133 +641,148 @@ exports.getLegCost = getLegCost;
|
|
|
628
641
|
* @returns Money object for the total itinerary cost.
|
|
629
642
|
*/
|
|
630
643
|
function getItineraryCost(legs, mediumId, riderCategoryId) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
if (
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
return total;
|
|
644
|
+
// TODO: Better input type handling
|
|
645
|
+
if (Array.isArray(mediumId) || Array.isArray(riderCategoryId)) {
|
|
646
|
+
if ((mediumId === null || mediumId === void 0 ? void 0 : mediumId.length) !== riderCategoryId.length) {
|
|
647
|
+
console.warn("Invalid input types, only using first item. medium id list and rider category list must have same number of items");
|
|
648
|
+
return getItineraryCost(legs, mediumId[0], riderCategoryId[0]);
|
|
649
|
+
}
|
|
650
|
+
let total = {
|
|
651
|
+
amount: 0,
|
|
652
|
+
currency: null
|
|
653
|
+
};
|
|
654
|
+
for (let i = 0; i < mediumId.length; i++) {
|
|
655
|
+
const newCost = getItineraryCost(legs, mediumId[i], riderCategoryId[i]);
|
|
656
|
+
if (newCost) {
|
|
657
|
+
var _total, _total$currency;
|
|
658
|
+
total = {
|
|
659
|
+
amount: ((_total = total) === null || _total === void 0 ? void 0 : _total.amount) + ((newCost === null || newCost === void 0 ? void 0 : newCost.amount) || 0),
|
|
660
|
+
currency: (_total$currency = total.currency) !== null && _total$currency !== void 0 ? _total$currency : newCost === null || newCost === void 0 ? void 0 : newCost.currency
|
|
661
|
+
};
|
|
662
|
+
}
|
|
651
663
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
},
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
664
|
+
if (total.currency === null) return undefined;
|
|
665
|
+
return total;
|
|
666
|
+
}
|
|
667
|
+
const legCosts = legs
|
|
668
|
+
// Only legs with fares (no walking legs)
|
|
669
|
+
.filter(leg => {
|
|
670
|
+
var _leg$fareProducts;
|
|
671
|
+
return ((_leg$fareProducts = leg.fareProducts) === null || _leg$fareProducts === void 0 ? void 0 : _leg$fareProducts.length) > 0;
|
|
672
|
+
})
|
|
673
|
+
// Get the leg cost object of each leg
|
|
674
|
+
.reduce((acc, leg) => {
|
|
675
|
+
// getLegCost handles filtering out duplicate use IDs
|
|
676
|
+
// One fare product can be used on multiple legs,
|
|
677
|
+
// and we don't want to count it more than once.
|
|
678
|
+
// Use an object keyed by productUseId to deduplicate, then extract prices
|
|
679
|
+
const {
|
|
680
|
+
appliedFareProduct,
|
|
681
|
+
productUseId
|
|
682
|
+
} = getLegCost(leg, mediumId, riderCategoryId, acc.seenIds);
|
|
683
|
+
if (!appliedFareProduct) return acc;
|
|
684
|
+
return {
|
|
685
|
+
legCosts: [...acc.legCosts, appliedFareProduct],
|
|
686
|
+
seenIds: [...acc.seenIds, productUseId]
|
|
687
|
+
};
|
|
688
|
+
}, {
|
|
689
|
+
seenIds: [],
|
|
690
|
+
legCosts: []
|
|
691
|
+
}).legCosts.map(lc => lc.legPrice);
|
|
692
|
+
if (legCosts.length === 0) return undefined;
|
|
693
|
+
// Calculate the total
|
|
694
|
+
return legCosts.reduce((prev, cur) => {
|
|
695
|
+
var _prev$currency;
|
|
696
|
+
return {
|
|
697
|
+
amount: prev.amount + (cur === null || cur === void 0 ? void 0 : cur.amount) || 0,
|
|
698
|
+
currency: (_prev$currency = prev.currency) !== null && _prev$currency !== void 0 ? _prev$currency : cur === null || cur === void 0 ? void 0 : cur.currency
|
|
699
|
+
};
|
|
700
|
+
}, {
|
|
701
|
+
amount: 0,
|
|
702
|
+
currency: null
|
|
703
|
+
});
|
|
682
704
|
}
|
|
683
|
-
exports.getItineraryCost = getItineraryCost;
|
|
684
705
|
const pickupDropoffTypeToOtp1 = otp2Type => {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
706
|
+
switch (otp2Type) {
|
|
707
|
+
case "COORDINATE_WITH_DRIVER":
|
|
708
|
+
return "coordinateWithDriver";
|
|
709
|
+
case "CALL_AGENCY":
|
|
710
|
+
return "mustPhone";
|
|
711
|
+
case "SCHEDULED":
|
|
712
|
+
return "scheduled";
|
|
713
|
+
case "NONE":
|
|
714
|
+
return "none";
|
|
715
|
+
default:
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
697
718
|
};
|
|
698
|
-
const convertGraphQLResponseToLegacy =
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
719
|
+
const convertGraphQLResponseToLegacy = leg => {
|
|
720
|
+
var _leg$agency, _leg$agency2, _leg$agency3, _leg$agency4, _leg$from$stop, _leg$from$stop2, _leg$route, _leg$route2, _leg$route3, _leg$route4, _leg$route5, _leg$route6, _leg$to$stop, _leg$to$stop2, _leg$trip, _leg$trip2;
|
|
721
|
+
return {
|
|
722
|
+
...leg,
|
|
723
|
+
agencyBrandingUrl: (_leg$agency = leg.agency) === null || _leg$agency === void 0 ? void 0 : _leg$agency.url,
|
|
724
|
+
agencyId: (_leg$agency2 = leg.agency) === null || _leg$agency2 === void 0 ? void 0 : _leg$agency2.id,
|
|
725
|
+
agencyName: (_leg$agency3 = leg.agency) === null || _leg$agency3 === void 0 ? void 0 : _leg$agency3.name,
|
|
726
|
+
agencyUrl: (_leg$agency4 = leg.agency) === null || _leg$agency4 === void 0 ? void 0 : _leg$agency4.url,
|
|
727
|
+
alightRule: pickupDropoffTypeToOtp1(leg.dropoffType),
|
|
728
|
+
boardRule: pickupDropoffTypeToOtp1(leg.pickupType),
|
|
729
|
+
bookingRuleInfo: {
|
|
730
|
+
dropOff: (leg === null || leg === void 0 ? void 0 : leg.dropOffBookingInfo) || {},
|
|
731
|
+
pickUp: (leg === null || leg === void 0 ? void 0 : leg.pickUpBookingInfo) || {}
|
|
732
|
+
},
|
|
733
|
+
dropOffBookingInfo: {
|
|
734
|
+
latestBookingTime: leg.dropOffBookingInfo
|
|
735
|
+
},
|
|
736
|
+
from: {
|
|
737
|
+
...leg.from,
|
|
738
|
+
stopCode: (_leg$from$stop = leg.from.stop) === null || _leg$from$stop === void 0 ? void 0 : _leg$from$stop.code,
|
|
739
|
+
stopId: (_leg$from$stop2 = leg.from.stop) === null || _leg$from$stop2 === void 0 ? void 0 : _leg$from$stop2.gtfsId
|
|
740
|
+
},
|
|
741
|
+
route: (_leg$route = leg.route) === null || _leg$route === void 0 ? void 0 : _leg$route.shortName,
|
|
742
|
+
routeColor: (_leg$route2 = leg.route) === null || _leg$route2 === void 0 ? void 0 : _leg$route2.color,
|
|
743
|
+
routeId: (_leg$route3 = leg.route) === null || _leg$route3 === void 0 ? void 0 : _leg$route3.gtfsId,
|
|
744
|
+
routeLongName: (_leg$route4 = leg.route) === null || _leg$route4 === void 0 ? void 0 : _leg$route4.longName,
|
|
745
|
+
routeShortName: (_leg$route5 = leg.route) === null || _leg$route5 === void 0 ? void 0 : _leg$route5.shortName,
|
|
746
|
+
routeTextColor: (_leg$route6 = leg.route) === null || _leg$route6 === void 0 ? void 0 : _leg$route6.textColor,
|
|
747
|
+
to: {
|
|
748
|
+
...leg.to,
|
|
749
|
+
stopCode: (_leg$to$stop = leg.to.stop) === null || _leg$to$stop === void 0 ? void 0 : _leg$to$stop.code,
|
|
750
|
+
stopId: (_leg$to$stop2 = leg.to.stop) === null || _leg$to$stop2 === void 0 ? void 0 : _leg$to$stop2.gtfsId
|
|
751
|
+
},
|
|
752
|
+
tripHeadsign: (_leg$trip = leg.trip) === null || _leg$trip === void 0 ? void 0 : _leg$trip.tripHeadsign,
|
|
753
|
+
tripId: (_leg$trip2 = leg.trip) === null || _leg$trip2 === void 0 ? void 0 : _leg$trip2.gtfsId
|
|
754
|
+
};
|
|
734
755
|
};
|
|
735
|
-
|
|
756
|
+
|
|
736
757
|
/** Extracts the route number for a leg returned from OTP1 or OTP2. */
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
758
|
+
exports.convertGraphQLResponseToLegacy = convertGraphQLResponseToLegacy;
|
|
759
|
+
const getLegRouteShortName = leg => {
|
|
760
|
+
const {
|
|
761
|
+
route,
|
|
762
|
+
routeShortName
|
|
763
|
+
} = leg;
|
|
764
|
+
// typeof route === "object" denotes newer OTP2 responses. routeShortName and route as string is OTP1.
|
|
765
|
+
return typeof route === "object" ? route === null || route === void 0 ? void 0 : route.shortName : routeShortName || route;
|
|
743
766
|
};
|
|
744
|
-
|
|
767
|
+
|
|
745
768
|
/** Extract the route long name for a leg returned from OTP1 or OTP2. */
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
769
|
+
exports.getLegRouteShortName = getLegRouteShortName;
|
|
770
|
+
const getLegRouteLongName = leg => {
|
|
771
|
+
const {
|
|
772
|
+
route,
|
|
773
|
+
routeLongName
|
|
774
|
+
} = leg;
|
|
775
|
+
// typeof route === "object" denotes newer OTP2 responses. routeLongName is OTP1.
|
|
776
|
+
return typeof route === "object" ? route === null || route === void 0 ? void 0 : route.longName : routeLongName;
|
|
750
777
|
};
|
|
751
|
-
|
|
778
|
+
|
|
752
779
|
/**
|
|
753
780
|
* Returns the route short name, or the route long name if no short name is provided.
|
|
754
781
|
* This is happens with Seattle area streetcars and ferries.
|
|
755
782
|
*/
|
|
756
|
-
|
|
757
|
-
|
|
783
|
+
exports.getLegRouteLongName = getLegRouteLongName;
|
|
784
|
+
const getLegRouteName = leg => {
|
|
785
|
+
return getLegRouteShortName(leg) || getLegRouteLongName(leg);
|
|
758
786
|
};
|
|
759
787
|
exports.getLegRouteName = getLegRouteName;
|
|
760
788
|
//# sourceMappingURL=itinerary.js.map
|