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