@opentripplanner/core-utils 15.0.0 → 16.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/index.js +3 -0
- package/esm/index.js.map +1 -1
- package/esm/itinerary.js +95 -78
- package/esm/itinerary.js.map +1 -1
- package/esm/map.js +2 -2
- package/esm/map.js.map +1 -1
- package/esm/query-gen.js +9 -5
- package/esm/query-gen.js.map +1 -1
- package/esm/route.js +26 -20
- package/esm/route.js.map +1 -1
- package/esm/storage.js +4 -1
- package/esm/storage.js.map +1 -1
- package/esm/time.js +6 -5
- package/esm/time.js.map +1 -1
- package/esm/ui.js +4 -2
- package/esm/ui.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +6 -0
- package/lib/index.js.map +1 -1
- package/lib/itinerary.d.ts +17 -11
- package/lib/itinerary.d.ts.map +1 -1
- package/lib/itinerary.js +89 -84
- package/lib/itinerary.js.map +1 -1
- package/lib/map.d.ts +2 -2
- package/lib/map.d.ts.map +1 -1
- package/lib/map.js +1 -1
- package/lib/map.js.map +1 -1
- package/lib/query-gen.d.ts +1 -19
- package/lib/query-gen.d.ts.map +1 -1
- package/lib/query-gen.js +9 -5
- package/lib/query-gen.js.map +1 -1
- package/lib/route.d.ts +10 -8
- package/lib/route.d.ts.map +1 -1
- package/lib/route.js +22 -16
- package/lib/route.js.map +1 -1
- package/lib/storage.d.ts +1 -1
- package/lib/storage.d.ts.map +1 -1
- package/lib/storage.js +4 -1
- package/lib/storage.js.map +1 -1
- package/lib/time.d.ts +3 -1
- package/lib/time.d.ts.map +1 -1
- package/lib/time.js +5 -4
- package/lib/time.js.map +1 -1
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +4 -2
- package/lib/ui.js.map +1 -1
- package/package.json +9 -7
- package/src/__tests__/itinerary.ts +55 -5
- package/src/index.ts +3 -0
- package/src/itinerary.ts +134 -97
- package/src/map.ts +5 -3
- package/src/query-gen.ts +15 -9
- package/src/route.ts +65 -38
- package/src/storage.ts +8 -2
- package/src/time.ts +7 -6
- package/src/ui.ts +8 -6
- package/tsconfig.json +1 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getLegRouteShortName,
|
|
13
13
|
isFlex,
|
|
14
14
|
isTransit,
|
|
15
|
+
legElevationAtDistance,
|
|
15
16
|
mapOldElevationComponentToNew
|
|
16
17
|
} from "../itinerary";
|
|
17
18
|
|
|
@@ -261,21 +262,21 @@ describe("util > itinerary", () => {
|
|
|
261
262
|
"cash",
|
|
262
263
|
"regular"
|
|
263
264
|
);
|
|
264
|
-
expect(result
|
|
265
|
-
expect(result
|
|
265
|
+
expect(result?.amount).toEqual(5.75);
|
|
266
|
+
expect(result?.currency).toEqual({
|
|
266
267
|
code: "USD",
|
|
267
268
|
digits: 2
|
|
268
269
|
});
|
|
269
270
|
});
|
|
270
271
|
it("should calculate the total cost of an itinerary using fares v2", () => {
|
|
271
272
|
const result = getItineraryCost(faresv2Itinerary.legs, "3", "ADULT");
|
|
272
|
-
expect(result
|
|
273
|
+
expect(result?.amount).toEqual(2.8);
|
|
273
274
|
const complexResult = getItineraryCost(
|
|
274
275
|
faresv2Itinerary.legs,
|
|
275
276
|
["0", "0"],
|
|
276
277
|
["ADULT", "ADULT"]
|
|
277
278
|
);
|
|
278
|
-
expect(complexResult
|
|
279
|
+
expect(complexResult?.amount).toEqual(2.8 * 2);
|
|
279
280
|
});
|
|
280
281
|
it("should calculate the total cost of an itinerary with multiple v2 fares & transfers", () => {
|
|
281
282
|
const result = getItineraryCost(complexItinerary.legs, "0", "ADULT");
|
|
@@ -287,7 +288,7 @@ describe("util > itinerary", () => {
|
|
|
287
288
|
["0", "0"],
|
|
288
289
|
["ADULT", null]
|
|
289
290
|
);
|
|
290
|
-
expect(result
|
|
291
|
+
expect(result?.amount).toEqual(11.55);
|
|
291
292
|
});
|
|
292
293
|
it("should calculate the individual leg cost of a fares v2 legs", () => {
|
|
293
294
|
const firstLegResult = getLegCost(faresv2Itinerary.legs[1], "3", "ADULT");
|
|
@@ -404,4 +405,53 @@ describe("util > itinerary", () => {
|
|
|
404
405
|
).toBe("15");
|
|
405
406
|
});
|
|
406
407
|
});
|
|
408
|
+
|
|
409
|
+
describe("legElevationAtDistance", () => {
|
|
410
|
+
it("should interpolate elevation within a segment", () => {
|
|
411
|
+
const points: any = [
|
|
412
|
+
[0, 100],
|
|
413
|
+
[10, 110],
|
|
414
|
+
[20, 120]
|
|
415
|
+
];
|
|
416
|
+
// halfway between 100 and 110
|
|
417
|
+
expect(legElevationAtDistance(points, 5)).toBe(105);
|
|
418
|
+
expect(legElevationAtDistance(points, 15)).toBe(115);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("should return start elevation at distance 0", () => {
|
|
422
|
+
const points: any = [
|
|
423
|
+
[0, 42],
|
|
424
|
+
[10, 52]
|
|
425
|
+
];
|
|
426
|
+
expect(legElevationAtDistance(points, 0)).toBe(42);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it("should return end elevation at the segment boundary", () => {
|
|
430
|
+
const points: any = [
|
|
431
|
+
[0, 100],
|
|
432
|
+
[10, 110],
|
|
433
|
+
[20, 120]
|
|
434
|
+
];
|
|
435
|
+
expect(legElevationAtDistance(points, 10)).toBe(110);
|
|
436
|
+
expect(legElevationAtDistance(points, 20)).toBe(120);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("should return undefined when distance is before the profile", () => {
|
|
440
|
+
const points: any = [
|
|
441
|
+
[0, 100],
|
|
442
|
+
[10, 110],
|
|
443
|
+
[20, 120]
|
|
444
|
+
];
|
|
445
|
+
expect(legElevationAtDistance(points, -1)).toBeUndefined();
|
|
446
|
+
expect(legElevationAtDistance(points, 21)).toBeUndefined();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it("should return undefined when first point is not at zero", () => {
|
|
450
|
+
const points: any = [
|
|
451
|
+
[5, 100],
|
|
452
|
+
[15, 110]
|
|
453
|
+
];
|
|
454
|
+
expect(legElevationAtDistance(points, 4)).toBeUndefined();
|
|
455
|
+
});
|
|
456
|
+
});
|
|
407
457
|
});
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import * as itinerary from "./itinerary";
|
|
2
2
|
import * as map from "./map";
|
|
3
|
+
// @ts-expect-error not typed
|
|
3
4
|
import * as profile from "./profile";
|
|
5
|
+
// @ts-expect-error not typed
|
|
4
6
|
import * as query from "./query";
|
|
7
|
+
// @ts-expect-error not typed
|
|
5
8
|
import * as queryParams from "./query-params";
|
|
6
9
|
import * as route from "./route";
|
|
7
10
|
import * as storage from "./storage";
|
package/src/itinerary.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
Currency,
|
|
7
7
|
ElevationProfile,
|
|
8
8
|
ElevationProfileComponent,
|
|
9
|
+
FareProduct,
|
|
9
10
|
FlexBookingInfo,
|
|
10
11
|
ItineraryOnlyLegsRequired,
|
|
11
12
|
LatLngArray,
|
|
@@ -86,11 +87,12 @@ export function startsWithGeometry(leg: Leg): boolean {
|
|
|
86
87
|
export function legContainsGeometry(leg: Leg): boolean {
|
|
87
88
|
return endsWithGeometry(leg) || startsWithGeometry(leg);
|
|
88
89
|
}
|
|
89
|
-
export function isAdvanceBookingRequired(info
|
|
90
|
-
|
|
90
|
+
export function isAdvanceBookingRequired(info?: FlexBookingInfo): boolean {
|
|
91
|
+
const daysPrior = info?.latestBookingTime?.daysPrior;
|
|
92
|
+
return typeof daysPrior === "number" && daysPrior > 0;
|
|
91
93
|
}
|
|
92
94
|
export function legDropoffRequiresAdvanceBooking(leg: Leg): boolean {
|
|
93
|
-
return isAdvanceBookingRequired(leg
|
|
95
|
+
return isAdvanceBookingRequired(leg.dropOffBookingInfo);
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
/**
|
|
@@ -199,6 +201,7 @@ export function hasRental(modesStr: string): boolean {
|
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
export function getMapColor(mode: string): string {
|
|
204
|
+
// @ts-expect-error this is not typed
|
|
202
205
|
mode = mode || this.get("mode");
|
|
203
206
|
if (mode === "WALK") return "#444";
|
|
204
207
|
if (mode === "BICYCLE") return "#0073e5";
|
|
@@ -224,7 +227,7 @@ export function toSentenceCase(str: string): string {
|
|
|
224
227
|
/**
|
|
225
228
|
* Derive the company string based on mode and network associated with leg.
|
|
226
229
|
*/
|
|
227
|
-
export function getCompanyFromLeg(leg
|
|
230
|
+
export function getCompanyFromLeg(leg?: Leg): string | null {
|
|
228
231
|
if (!leg) return null;
|
|
229
232
|
const {
|
|
230
233
|
from,
|
|
@@ -234,14 +237,20 @@ export function getCompanyFromLeg(leg: Leg): string {
|
|
|
234
237
|
rentedVehicle,
|
|
235
238
|
rideHailingEstimate
|
|
236
239
|
} = leg;
|
|
240
|
+
|
|
241
|
+
const firstNetwork =
|
|
242
|
+
Array.isArray(from.networks) && from.networks.length > 0
|
|
243
|
+
? from.networks[0]
|
|
244
|
+
: null;
|
|
245
|
+
|
|
237
246
|
if (mode === "CAR" && rentedCar) {
|
|
238
|
-
return
|
|
247
|
+
return firstNetwork;
|
|
239
248
|
}
|
|
240
249
|
if (mode === "CAR" && rideHailingEstimate) {
|
|
241
250
|
return rideHailingEstimate.provider.id;
|
|
242
251
|
}
|
|
243
|
-
if (mode === "BICYCLE" && rentedBike
|
|
244
|
-
return
|
|
252
|
+
if (mode === "BICYCLE" && rentedBike) {
|
|
253
|
+
return firstNetwork;
|
|
245
254
|
}
|
|
246
255
|
if (from.rentalVehicle) {
|
|
247
256
|
return from.rentalVehicle.network;
|
|
@@ -249,12 +258,8 @@ export function getCompanyFromLeg(leg: Leg): string {
|
|
|
249
258
|
if (from.vehicleRentalStation?.rentalNetwork) {
|
|
250
259
|
return from.vehicleRentalStation.rentalNetwork.networkId;
|
|
251
260
|
}
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
rentedVehicle &&
|
|
255
|
-
from.networks
|
|
256
|
-
) {
|
|
257
|
-
return from.networks[0];
|
|
261
|
+
if ((mode === "MICROMOBILITY" || mode === "SCOOTER") && rentedVehicle) {
|
|
262
|
+
return firstNetwork;
|
|
258
263
|
}
|
|
259
264
|
return null;
|
|
260
265
|
}
|
|
@@ -262,12 +267,12 @@ export function getCompanyFromLeg(leg: Leg): string {
|
|
|
262
267
|
export function getItineraryBounds(
|
|
263
268
|
itinerary: ItineraryOnlyLegsRequired
|
|
264
269
|
): LatLngArray[] {
|
|
265
|
-
|
|
270
|
+
const coords: LatLngArray[] = [];
|
|
266
271
|
itinerary.legs.forEach(leg => {
|
|
267
272
|
const legCoords = polyline
|
|
268
273
|
.toGeoJSON(leg.legGeometry.points)
|
|
269
|
-
.coordinates.map((c:
|
|
270
|
-
coords
|
|
274
|
+
.coordinates.map((c): LatLngArray => [c[1], c[0]]);
|
|
275
|
+
coords.push(...legCoords);
|
|
271
276
|
});
|
|
272
277
|
return coords;
|
|
273
278
|
}
|
|
@@ -290,62 +295,56 @@ export function getLegBounds(leg: Leg): number[][] {
|
|
|
290
295
|
}
|
|
291
296
|
|
|
292
297
|
/* Returns an interpolated lat-lon at a specified distance along a leg */
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
298
|
+
export function legLocationAtDistance(
|
|
299
|
+
leg: Leg,
|
|
300
|
+
distance: number
|
|
301
|
+
): LatLngArray | undefined | null {
|
|
302
|
+
if (!leg.legGeometry) return undefined;
|
|
296
303
|
|
|
297
304
|
try {
|
|
298
305
|
const line = polyline.toGeoJSON(leg.legGeometry.points);
|
|
299
306
|
const pt = turfAlong(line, distance, { units: "meters" });
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
// FIXME handle error!
|
|
307
|
+
const coords = pt?.geometry?.coordinates;
|
|
308
|
+
return [coords[1], coords[0]];
|
|
309
|
+
} catch {
|
|
310
|
+
// This is designed to catch the toGeoJSON from throwing if the geometry is not in the correct format
|
|
305
311
|
}
|
|
306
312
|
|
|
307
313
|
return null;
|
|
308
314
|
}
|
|
309
315
|
|
|
310
|
-
|
|
316
|
+
/**
|
|
317
|
+
* Returns an interpolated elevation at a specified distance along a leg
|
|
318
|
+
* @param points - The points of the elevation profile. Each point is a tuple of [distance, elevation].
|
|
319
|
+
* @param distance - The distance along the leg to interpolate the elevation at
|
|
320
|
+
* @returns The interpolated elevation at the specified distance
|
|
321
|
+
*/
|
|
311
322
|
|
|
312
323
|
export function legElevationAtDistance(
|
|
313
|
-
points: number
|
|
324
|
+
points: [number, number][],
|
|
314
325
|
distance: number
|
|
315
|
-
): number {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (start[1] === null) {
|
|
330
|
-
console.warn(
|
|
331
|
-
"Elevation value does not exist for distance.",
|
|
332
|
-
distance,
|
|
333
|
-
traversed
|
|
334
|
-
);
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
const pct = (distance - traversed) / elevDistanceSpan;
|
|
338
|
-
const elevSpan = points[i][1] - start[1];
|
|
339
|
-
return start[1] + elevSpan * pct;
|
|
326
|
+
): number | undefined {
|
|
327
|
+
const elevation = points.reduce<number | undefined>((acc, point, index) => {
|
|
328
|
+
const prevPoint = points[index - 1];
|
|
329
|
+
// at the first index there is no previous point
|
|
330
|
+
if (!prevPoint) return acc;
|
|
331
|
+
const [pointDistance, pointElevation] = point;
|
|
332
|
+
const [prevPointDistance, prevPointElevation] = prevPoint;
|
|
333
|
+
if (distance >= prevPointDistance && distance <= pointDistance) {
|
|
334
|
+
return (
|
|
335
|
+
prevPointElevation +
|
|
336
|
+
((pointElevation - prevPointElevation) *
|
|
337
|
+
(distance - prevPointDistance)) /
|
|
338
|
+
(pointDistance - prevPointDistance)
|
|
339
|
+
);
|
|
340
340
|
}
|
|
341
|
-
|
|
341
|
+
return acc;
|
|
342
|
+
}, undefined);
|
|
343
|
+
if (elevation === undefined) {
|
|
344
|
+
console.warn("Elevation value does not exist for distance.", distance);
|
|
345
|
+
return undefined;
|
|
342
346
|
}
|
|
343
|
-
|
|
344
|
-
"Elevation value does not exist for distance.",
|
|
345
|
-
distance,
|
|
346
|
-
traversed
|
|
347
|
-
);
|
|
348
|
-
return null;
|
|
347
|
+
return elevation;
|
|
349
348
|
}
|
|
350
349
|
|
|
351
350
|
export function mapOldElevationComponentToNew(oldElev: {
|
|
@@ -370,7 +369,7 @@ export function getElevationProfile(
|
|
|
370
369
|
let gain = 0;
|
|
371
370
|
let loss = 0;
|
|
372
371
|
let previous: ElevationProfileComponent | null = null;
|
|
373
|
-
const points = [];
|
|
372
|
+
const points: [number, number][] = [];
|
|
374
373
|
steps.forEach(step => {
|
|
375
374
|
// Support for old REST response data (in step.elevation)
|
|
376
375
|
const stepElevationProfile =
|
|
@@ -430,6 +429,7 @@ export function getTextWidth(text: string, font = "22px Arial"): number {
|
|
|
430
429
|
(getTextWidth as GetTextWidth).canvas ||
|
|
431
430
|
((getTextWidth as GetTextWidth).canvas = document.createElement("canvas"));
|
|
432
431
|
const context = canvas.getContext("2d");
|
|
432
|
+
if (!context) return 0;
|
|
433
433
|
context.font = font;
|
|
434
434
|
const metrics = context.measureText(text);
|
|
435
435
|
return metrics.width;
|
|
@@ -442,7 +442,7 @@ export function getTextWidth(text: string, font = "22px Arial"): number {
|
|
|
442
442
|
export function getCompanyForNetwork(
|
|
443
443
|
networkString: string,
|
|
444
444
|
companies: Company[] = []
|
|
445
|
-
): Company {
|
|
445
|
+
): Company | undefined {
|
|
446
446
|
const company = companies.find(co => co.id === networkString);
|
|
447
447
|
if (!company) {
|
|
448
448
|
console.warn(
|
|
@@ -473,7 +473,10 @@ export function getCompaniesLabelFromNetworks(
|
|
|
473
473
|
.join("/");
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
-
export function getTNCLocation(
|
|
476
|
+
export function getTNCLocation(
|
|
477
|
+
leg: Pick<Leg, "from" | "to">,
|
|
478
|
+
type: "from" | "to"
|
|
479
|
+
): string {
|
|
477
480
|
const location = leg[type];
|
|
478
481
|
return `${location.lat.toFixed(5)},${location.lon.toFixed(5)}`;
|
|
479
482
|
}
|
|
@@ -509,15 +512,21 @@ export function calculateTncFares(
|
|
|
509
512
|
itinerary: ItineraryOnlyLegsRequired
|
|
510
513
|
): TncFare {
|
|
511
514
|
return itinerary.legs
|
|
512
|
-
.filter(
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
515
|
+
.filter(
|
|
516
|
+
(
|
|
517
|
+
leg
|
|
518
|
+
): leg is Leg & {
|
|
519
|
+
rideHailingEstimate: NonNullable<Leg["rideHailingEstimate"]>;
|
|
520
|
+
} => leg.mode === "CAR" && leg.rideHailingEstimate !== undefined
|
|
521
|
+
)
|
|
522
|
+
.reduce<TncFare>(
|
|
523
|
+
(acc, leg) => {
|
|
524
|
+
const { minPrice, maxPrice } = leg.rideHailingEstimate;
|
|
516
525
|
return {
|
|
517
526
|
// Assumes a single currency for entire itinerary.
|
|
518
527
|
currencyCode: minPrice.currency.code,
|
|
519
|
-
maxTNCFare: maxTNCFare + maxPrice.amount,
|
|
520
|
-
minTNCFare: minTNCFare + minPrice.amount
|
|
528
|
+
maxTNCFare: acc.maxTNCFare + maxPrice.amount,
|
|
529
|
+
minTNCFare: acc.minTNCFare + minPrice.amount
|
|
521
530
|
};
|
|
522
531
|
},
|
|
523
532
|
{
|
|
@@ -535,7 +544,7 @@ export function calculateTncFares(
|
|
|
535
544
|
* - https://www.itf-oecd.org/sites/default/files/life-cycle-assessment-calculations-2020.xlsx
|
|
536
545
|
* Other values extrapolated.
|
|
537
546
|
*/
|
|
538
|
-
const CARBON_INTENSITY_DEFAULTS = {
|
|
547
|
+
const CARBON_INTENSITY_DEFAULTS: Record<string, number> = {
|
|
539
548
|
walk: 0.026,
|
|
540
549
|
bicycle: 0.017,
|
|
541
550
|
car: 0.162,
|
|
@@ -627,6 +636,8 @@ export const zeroDollars = (currency: Currency): Money => ({
|
|
|
627
636
|
currency
|
|
628
637
|
});
|
|
629
638
|
|
|
639
|
+
type FareProductWithPrice = FareProduct & { price: Money };
|
|
640
|
+
|
|
630
641
|
/**
|
|
631
642
|
* Extracts useful data from the fare products on a leg, such as the leg cost and transfer info.
|
|
632
643
|
* @param leg Leg with Fares v2 information
|
|
@@ -642,7 +653,7 @@ export function getLegCost(
|
|
|
642
653
|
leg: Leg,
|
|
643
654
|
mediumId?: string | null,
|
|
644
655
|
riderCategoryId?: string | null,
|
|
645
|
-
seenFareIds?: string[]
|
|
656
|
+
seenFareIds?: string[] | null
|
|
646
657
|
): {
|
|
647
658
|
alternateFareProducts?: AppliedFareProduct[];
|
|
648
659
|
appliedFareProduct?: AppliedFareProduct;
|
|
@@ -673,14 +684,22 @@ export function getLegCost(
|
|
|
673
684
|
product?.price
|
|
674
685
|
);
|
|
675
686
|
})
|
|
687
|
+
// Make sure there's a price
|
|
688
|
+
// Some fare products don't have a price at all.
|
|
689
|
+
.filter(
|
|
690
|
+
(fare): fare is { id: string; product: FareProductWithPrice } =>
|
|
691
|
+
fare.product?.price !== undefined
|
|
692
|
+
)
|
|
676
693
|
.map(fare => {
|
|
677
|
-
const alreadySeen = seenFareIds?.indexOf(fare.id) > -1;
|
|
678
|
-
const { currency } = fare.product.price;
|
|
694
|
+
const alreadySeen = !!seenFareIds && seenFareIds?.indexOf(fare.id) > -1;
|
|
679
695
|
return {
|
|
680
696
|
id: fare.id,
|
|
681
697
|
product: {
|
|
682
698
|
...fare.product,
|
|
683
|
-
legPrice:
|
|
699
|
+
legPrice:
|
|
700
|
+
alreadySeen && fare.product.price
|
|
701
|
+
? zeroDollars(fare.product.price.currency)
|
|
702
|
+
: fare.product.price
|
|
684
703
|
} as AppliedFareProduct
|
|
685
704
|
};
|
|
686
705
|
})
|
|
@@ -712,35 +731,51 @@ export function getLegCost(
|
|
|
712
731
|
*/
|
|
713
732
|
export function getItineraryCost(
|
|
714
733
|
legs: Leg[],
|
|
715
|
-
mediumId?: string | string[] | null,
|
|
716
|
-
riderCategoryId?: string | string[] | null
|
|
734
|
+
mediumId?: string | (string | null)[] | null,
|
|
735
|
+
riderCategoryId?: string | (string | null)[] | null
|
|
717
736
|
): Money | undefined {
|
|
718
|
-
// TODO: Better input type handling
|
|
719
737
|
if (Array.isArray(mediumId) || Array.isArray(riderCategoryId)) {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
738
|
+
// TODO: Better input type handling
|
|
739
|
+
if (Array.isArray(mediumId) && Array.isArray(riderCategoryId)) {
|
|
740
|
+
if (mediumId.length !== riderCategoryId.length) {
|
|
741
|
+
console.warn(
|
|
742
|
+
"Invalid input types, only using first item. medium id list and rider category list must have same number of items"
|
|
743
|
+
);
|
|
744
|
+
return getItineraryCost(legs, mediumId[0], riderCategoryId[0]);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const total = mediumId.reduce<Money>(
|
|
748
|
+
(acc, currentMediumId, index) => {
|
|
749
|
+
const newCost = getItineraryCost(
|
|
750
|
+
legs,
|
|
751
|
+
currentMediumId,
|
|
752
|
+
riderCategoryId[index]
|
|
753
|
+
);
|
|
754
|
+
if (!newCost) return acc;
|
|
755
|
+
|
|
756
|
+
return {
|
|
757
|
+
amount: acc.amount + (newCost.amount || 0),
|
|
758
|
+
currency:
|
|
759
|
+
acc.currency.code !== ""
|
|
760
|
+
? acc.currency
|
|
761
|
+
: newCost.currency ?? acc.currency
|
|
762
|
+
};
|
|
763
|
+
},
|
|
764
|
+
{ amount: 0, currency: { code: "", digits: 0 } }
|
|
723
765
|
);
|
|
724
|
-
return getItineraryCost(legs, mediumId[0], riderCategoryId[0]);
|
|
725
|
-
}
|
|
726
766
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
const newCost = getItineraryCost(legs, mediumId[i], riderCategoryId[i]);
|
|
730
|
-
if (newCost) {
|
|
731
|
-
total = {
|
|
732
|
-
amount: total?.amount + (newCost?.amount || 0),
|
|
733
|
-
currency: total.currency ?? newCost?.currency
|
|
734
|
-
};
|
|
735
|
-
}
|
|
767
|
+
if (!total.currency?.code) return undefined;
|
|
768
|
+
return total;
|
|
736
769
|
}
|
|
737
|
-
|
|
738
|
-
|
|
770
|
+
console.warn(
|
|
771
|
+
"Invalid input types, only using first item. medium id list and rider category list must have same number of items"
|
|
772
|
+
);
|
|
773
|
+
return undefined;
|
|
739
774
|
}
|
|
740
775
|
|
|
741
776
|
const legCosts = legs
|
|
742
777
|
// Only legs with fares (no walking legs)
|
|
743
|
-
.filter(leg => leg.fareProducts?.length > 0)
|
|
778
|
+
.filter(leg => leg.fareProducts?.length && leg.fareProducts.length > 0)
|
|
744
779
|
// Get the leg cost object of each leg
|
|
745
780
|
.reduce<{ seenIds: string[]; legCosts: AppliedFareProduct[] }>(
|
|
746
781
|
(acc, leg) => {
|
|
@@ -755,6 +790,7 @@ export function getItineraryCost(
|
|
|
755
790
|
acc.seenIds
|
|
756
791
|
);
|
|
757
792
|
if (!appliedFareProduct) return acc;
|
|
793
|
+
if (!productUseId) return acc;
|
|
758
794
|
return {
|
|
759
795
|
legCosts: [...acc.legCosts, appliedFareProduct],
|
|
760
796
|
seenIds: [...acc.seenIds, productUseId]
|
|
@@ -769,9 +805,10 @@ export function getItineraryCost(
|
|
|
769
805
|
return legCosts.reduce<Money>(
|
|
770
806
|
(prev, cur) => ({
|
|
771
807
|
amount: prev.amount + cur?.amount || 0,
|
|
772
|
-
currency: prev.currency
|
|
808
|
+
currency: prev.currency.code !== "" ? prev.currency : cur.currency
|
|
773
809
|
}),
|
|
774
|
-
|
|
810
|
+
// eslint-disable-next-line prettier/prettier -- old eslint doesn't know satisfies
|
|
811
|
+
{ amount: 0, currency: { code: "", digits: 0 } satisfies Currency }
|
|
775
812
|
);
|
|
776
813
|
}
|
|
777
814
|
|
|
@@ -790,7 +827,7 @@ const pickupDropoffTypeToOtp1 = (otp2Type: string): string | null => {
|
|
|
790
827
|
}
|
|
791
828
|
};
|
|
792
829
|
|
|
793
|
-
export const convertGraphQLResponseToLegacy = (leg: any):
|
|
830
|
+
export const convertGraphQLResponseToLegacy = (leg: any): Leg => ({
|
|
794
831
|
...leg,
|
|
795
832
|
agencyBrandingUrl: leg.agency?.url,
|
|
796
833
|
agencyId: leg.agency?.id,
|
|
@@ -839,7 +876,7 @@ export const getLegRouteShortName = (
|
|
|
839
876
|
/** Extract the route long name for a leg returned from OTP1 or OTP2. */
|
|
840
877
|
export const getLegRouteLongName = (
|
|
841
878
|
leg: Pick<Leg, "route" | "routeLongName">
|
|
842
|
-
): string |
|
|
879
|
+
): string | undefined => {
|
|
843
880
|
const { route, routeLongName } = leg;
|
|
844
881
|
// typeof route === "object" denotes newer OTP2 responses. routeLongName is OTP1.
|
|
845
882
|
return typeof route === "object" ? route?.longName : routeLongName;
|
|
@@ -851,6 +888,6 @@ export const getLegRouteLongName = (
|
|
|
851
888
|
*/
|
|
852
889
|
export const getLegRouteName = (
|
|
853
890
|
leg: Pick<Leg, "route" | "routeLongName" | "routeShortName">
|
|
854
|
-
): string => {
|
|
891
|
+
): string | undefined => {
|
|
855
892
|
return getLegRouteShortName(leg) || getLegRouteLongName(leg);
|
|
856
893
|
};
|
package/src/map.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { LatLngArray, Location, UserPosition } from "@opentripplanner/types";
|
|
|
2
2
|
|
|
3
3
|
export function currentPositionToLocation(
|
|
4
4
|
currentPosition: UserPosition
|
|
5
|
-
): Location {
|
|
5
|
+
): Location | null {
|
|
6
6
|
if (currentPosition.error || !currentPosition.coords) {
|
|
7
7
|
console.warn(
|
|
8
8
|
"Cannot construct location from current position due to geolocation error or missing coordinates."
|
|
@@ -18,8 +18,10 @@ export function currentPositionToLocation(
|
|
|
18
18
|
|
|
19
19
|
// TRICKY: This method is used in query.js and in the context of
|
|
20
20
|
// otp-rr actions where the intl context is not available/does not apply.
|
|
21
|
-
export function coordsToString(coords: number[]): string {
|
|
22
|
-
return coords.length
|
|
21
|
+
export function coordsToString(coords: number[]): string | undefined {
|
|
22
|
+
return coords.length > 0
|
|
23
|
+
? coords.map(c => (+c).toFixed(5)).join(", ")
|
|
24
|
+
: undefined;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export function stringToCoords(str: string): number[] {
|
package/src/query-gen.ts
CHANGED
|
@@ -102,7 +102,7 @@ function combinations(array: TransportMode[]): TransportMode[][] {
|
|
|
102
102
|
* This constant maps all the transport mode to a broader mode type,
|
|
103
103
|
* which is used to determine the valid combinations of modes used in query generation.
|
|
104
104
|
*/
|
|
105
|
-
export const SIMPLIFICATIONS = {
|
|
105
|
+
export const SIMPLIFICATIONS: Record<string, string> = {
|
|
106
106
|
AIRPLANE: "TRANSIT",
|
|
107
107
|
BICYCLE: "PERSONAL",
|
|
108
108
|
BUS: "TRANSIT",
|
|
@@ -123,7 +123,7 @@ export const SIMPLIFICATIONS = {
|
|
|
123
123
|
};
|
|
124
124
|
|
|
125
125
|
// Inclusion of "TRANSIT" alone automatically implies "WALK" in OTP
|
|
126
|
-
const VALID_COMBOS = [
|
|
126
|
+
const VALID_COMBOS: string[][] = [
|
|
127
127
|
["WALK"],
|
|
128
128
|
["PERSONAL"],
|
|
129
129
|
["TRANSIT", "SHARED"],
|
|
@@ -220,19 +220,25 @@ export function generateOtp2Query(
|
|
|
220
220
|
const { from, modeSettings, to, ...otherOtpQueryParams } = otpQueryParams;
|
|
221
221
|
|
|
222
222
|
// This extracts the values from the mode settings to key value pairs
|
|
223
|
-
const modeSettingValues = modeSettings.reduce
|
|
224
|
-
|
|
223
|
+
const modeSettingValues = modeSettings.reduce<
|
|
224
|
+
Record<string, string | number | boolean>
|
|
225
|
+
>((prev, cur) => {
|
|
226
|
+
if (cur.type === "SLIDER" && cur.inverseKey && cur.value) {
|
|
225
227
|
prev[cur.inverseKey] = cur.high - cur.value + cur.low;
|
|
228
|
+
} else if (cur.value) {
|
|
229
|
+
prev[cur.key] = cur.value;
|
|
226
230
|
}
|
|
227
|
-
prev[cur.key] = cur.value;
|
|
228
231
|
|
|
229
232
|
// If we assign a value on true, return the value (or null) instead of a boolean.
|
|
230
|
-
if (cur.type === "CHECKBOX" && cur.truthValue) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
+
if (cur.type === "CHECKBOX" && cur.truthValue && cur.falseValue) {
|
|
234
|
+
const newVal = cur.value === true ? cur.truthValue : cur.falseValue;
|
|
235
|
+
if (newVal) {
|
|
236
|
+
prev[cur.key] = newVal;
|
|
237
|
+
}
|
|
233
238
|
}
|
|
234
239
|
return prev;
|
|
235
|
-
|
|
240
|
+
// eslint-disable-next-line prettier/prettier -- old eslint doesn't know satisfies
|
|
241
|
+
}, {}) satisfies ModeSettingValues;
|
|
236
242
|
|
|
237
243
|
const {
|
|
238
244
|
bikeReluctance,
|