@opentripplanner/core-utils 15.0.0 → 16.0.1
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 +104 -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 +20 -11
- package/lib/itinerary.d.ts.map +1 -1
- package/lib/itinerary.js +96 -85
- 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 +64 -6
- package/src/index.ts +3 -0
- package/src/itinerary.ts +145 -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
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
|
})
|
|
@@ -708,39 +727,59 @@ export function getLegCost(
|
|
|
708
727
|
* @param category Rider category (youth, regular, senior)
|
|
709
728
|
* @param container Fare container (cash, electronic)
|
|
710
729
|
* @param seenFareIds List of fare product IDs that have already been seen on prev legs.
|
|
730
|
+
* @param nulledTotalFareOnAnyMissingFare If this is set to true, the total fare
|
|
731
|
+
* will be null if *any* fare is missing. If false, the total will be the total
|
|
732
|
+
* fare with the missing fares ignored.
|
|
711
733
|
* @returns Money object for the total itinerary cost.
|
|
712
734
|
*/
|
|
713
735
|
export function getItineraryCost(
|
|
714
736
|
legs: Leg[],
|
|
715
|
-
mediumId?: string | string[] | null,
|
|
716
|
-
riderCategoryId?: string | string[] | null
|
|
737
|
+
mediumId?: string | (string | null)[] | null,
|
|
738
|
+
riderCategoryId?: string | (string | null)[] | null,
|
|
739
|
+
nulledTotalFareOnAnyMissingFare = false
|
|
717
740
|
): Money | undefined {
|
|
718
|
-
// TODO: Better input type handling
|
|
719
741
|
if (Array.isArray(mediumId) || Array.isArray(riderCategoryId)) {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
742
|
+
// TODO: Better input type handling
|
|
743
|
+
if (Array.isArray(mediumId) && Array.isArray(riderCategoryId)) {
|
|
744
|
+
if (mediumId.length !== riderCategoryId.length) {
|
|
745
|
+
console.warn(
|
|
746
|
+
"Invalid input types, only using first item. medium id list and rider category list must have same number of items"
|
|
747
|
+
);
|
|
748
|
+
return getItineraryCost(legs, mediumId[0], riderCategoryId[0]);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const total = mediumId.reduce<Money>(
|
|
752
|
+
(acc, currentMediumId, index) => {
|
|
753
|
+
const newCost = getItineraryCost(
|
|
754
|
+
legs,
|
|
755
|
+
currentMediumId,
|
|
756
|
+
riderCategoryId[index]
|
|
757
|
+
);
|
|
758
|
+
if (!newCost) return acc;
|
|
759
|
+
|
|
760
|
+
return {
|
|
761
|
+
amount: acc.amount + (newCost.amount || 0),
|
|
762
|
+
currency:
|
|
763
|
+
acc.currency.code !== ""
|
|
764
|
+
? acc.currency
|
|
765
|
+
: newCost.currency ?? acc.currency
|
|
766
|
+
};
|
|
767
|
+
},
|
|
768
|
+
{ amount: 0, currency: { code: "", digits: 0 } }
|
|
723
769
|
);
|
|
724
|
-
return getItineraryCost(legs, mediumId[0], riderCategoryId[0]);
|
|
725
|
-
}
|
|
726
770
|
|
|
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
|
-
}
|
|
771
|
+
if (!total.currency?.code) return undefined;
|
|
772
|
+
return total;
|
|
736
773
|
}
|
|
737
|
-
|
|
738
|
-
|
|
774
|
+
console.warn(
|
|
775
|
+
"Invalid input types, only using first item. medium id list and rider category list must have same number of items"
|
|
776
|
+
);
|
|
777
|
+
return undefined;
|
|
739
778
|
}
|
|
740
779
|
|
|
741
780
|
const legCosts = legs
|
|
742
781
|
// Only legs with fares (no walking legs)
|
|
743
|
-
.filter(leg => leg.fareProducts?.length > 0)
|
|
782
|
+
.filter(leg => leg.fareProducts?.length && leg.fareProducts.length > 0)
|
|
744
783
|
// Get the leg cost object of each leg
|
|
745
784
|
.reduce<{ seenIds: string[]; legCosts: AppliedFareProduct[] }>(
|
|
746
785
|
(acc, leg) => {
|
|
@@ -755,6 +794,7 @@ export function getItineraryCost(
|
|
|
755
794
|
acc.seenIds
|
|
756
795
|
);
|
|
757
796
|
if (!appliedFareProduct) return acc;
|
|
797
|
+
if (!productUseId) return acc;
|
|
758
798
|
return {
|
|
759
799
|
legCosts: [...acc.legCosts, appliedFareProduct],
|
|
760
800
|
seenIds: [...acc.seenIds, productUseId]
|
|
@@ -764,14 +804,22 @@ export function getItineraryCost(
|
|
|
764
804
|
)
|
|
765
805
|
.legCosts.map(lc => lc.legPrice);
|
|
766
806
|
|
|
807
|
+
if (
|
|
808
|
+
nulledTotalFareOnAnyMissingFare &&
|
|
809
|
+
legs.filter(l => l.transitLeg).length !== legCosts.length
|
|
810
|
+
) {
|
|
811
|
+
return undefined;
|
|
812
|
+
}
|
|
813
|
+
|
|
767
814
|
if (legCosts.length === 0) return undefined;
|
|
768
815
|
// Calculate the total
|
|
769
816
|
return legCosts.reduce<Money>(
|
|
770
817
|
(prev, cur) => ({
|
|
771
818
|
amount: prev.amount + cur?.amount || 0,
|
|
772
|
-
currency: prev.currency
|
|
819
|
+
currency: prev.currency.code !== "" ? prev.currency : cur.currency
|
|
773
820
|
}),
|
|
774
|
-
|
|
821
|
+
// eslint-disable-next-line prettier/prettier -- old eslint doesn't know satisfies
|
|
822
|
+
{ amount: 0, currency: { code: "", digits: 0 } satisfies Currency }
|
|
775
823
|
);
|
|
776
824
|
}
|
|
777
825
|
|
|
@@ -790,7 +838,7 @@ const pickupDropoffTypeToOtp1 = (otp2Type: string): string | null => {
|
|
|
790
838
|
}
|
|
791
839
|
};
|
|
792
840
|
|
|
793
|
-
export const convertGraphQLResponseToLegacy = (leg: any):
|
|
841
|
+
export const convertGraphQLResponseToLegacy = (leg: any): Leg => ({
|
|
794
842
|
...leg,
|
|
795
843
|
agencyBrandingUrl: leg.agency?.url,
|
|
796
844
|
agencyId: leg.agency?.id,
|
|
@@ -839,7 +887,7 @@ export const getLegRouteShortName = (
|
|
|
839
887
|
/** Extract the route long name for a leg returned from OTP1 or OTP2. */
|
|
840
888
|
export const getLegRouteLongName = (
|
|
841
889
|
leg: Pick<Leg, "route" | "routeLongName">
|
|
842
|
-
): string |
|
|
890
|
+
): string | undefined => {
|
|
843
891
|
const { route, routeLongName } = leg;
|
|
844
892
|
// typeof route === "object" denotes newer OTP2 responses. routeLongName is OTP1.
|
|
845
893
|
return typeof route === "object" ? route?.longName : routeLongName;
|
|
@@ -851,6 +899,6 @@ export const getLegRouteLongName = (
|
|
|
851
899
|
*/
|
|
852
900
|
export const getLegRouteName = (
|
|
853
901
|
leg: Pick<Leg, "route" | "routeLongName" | "routeShortName">
|
|
854
|
-
): string => {
|
|
902
|
+
): string | undefined => {
|
|
855
903
|
return getLegRouteShortName(leg) || getLegRouteLongName(leg);
|
|
856
904
|
};
|
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,
|