@opentripplanner/core-utils 9.0.0-alpha.3 → 9.0.0-alpha.30
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/itinerary.js +81 -29
- package/esm/itinerary.js.map +1 -1
- package/esm/query-gen.js +134 -61
- package/esm/query-gen.js.map +1 -1
- package/lib/core-utils.story.d.ts +22 -0
- package/lib/core-utils.story.d.ts.map +1 -0
- package/lib/core-utils.story.js +69 -0
- package/lib/core-utils.story.js.map +1 -0
- package/lib/itinerary.d.ts +28 -16
- package/lib/itinerary.d.ts.map +1 -1
- package/lib/itinerary.js +81 -32
- package/lib/itinerary.js.map +1 -1
- package/lib/query-gen.d.ts +49 -5
- package/lib/query-gen.d.ts.map +1 -1
- package/lib/query-gen.js +111 -45
- package/lib/query-gen.js.map +1 -1
- package/package.json +5 -6
- package/src/__tests__/__mocks__/tnc-itinerary.json +34 -18
- package/src/__tests__/itinerary.js +3 -28
- package/src/__tests__/{query-params.js → query-params.ts} +95 -10
- package/src/core-utils.story.tsx +1 -1
- package/src/itinerary.ts +99 -40
- package/src/otpSchema.json +12940 -0
- package/src/planQuery.graphql +222 -106
- package/src/query-gen.ts +169 -71
- package/tsconfig.json +3 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/esm/state.js +0 -2
- package/esm/state.js.map +0 -1
- package/lib/state.d.ts +0 -1
- package/lib/state.d.ts.map +0 -1
- package/lib/state.js +0 -2
- package/lib/state.js.map +0 -1
- package/src/state.ts +0 -0
- /package/src/__tests__/__snapshots__/{query-params.js.snap → query-params.ts.snap} +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { ModeSetting, TransportMode } from "@opentripplanner/types";
|
|
2
|
+
|
|
1
3
|
import { reduceOtpFlexModes } from "../query";
|
|
2
4
|
import queryParams, { getCustomQueryParams } from "../query-params";
|
|
3
|
-
import { generateCombinations } from "../query-gen";
|
|
5
|
+
import { extractAdditionalModes, generateCombinations } from "../query-gen";
|
|
4
6
|
|
|
5
7
|
const customWalkDistanceOptions = [
|
|
6
8
|
{
|
|
@@ -13,7 +15,7 @@ const customWalkDistanceOptions = [
|
|
|
13
15
|
}
|
|
14
16
|
];
|
|
15
17
|
|
|
16
|
-
function modeStrToTransportMode(m) {
|
|
18
|
+
function modeStrToTransportMode(m): TransportMode {
|
|
17
19
|
const splitVals = m.split("_");
|
|
18
20
|
return {
|
|
19
21
|
mode: splitVals[0],
|
|
@@ -26,18 +28,18 @@ const mockLatLon = {
|
|
|
26
28
|
lon: 2
|
|
27
29
|
};
|
|
28
30
|
|
|
29
|
-
function expectModes(modes, expectedModes) {
|
|
31
|
+
function expectModes(modes: string[], expectedModes: string[][]) {
|
|
30
32
|
const generatedModesList = generateCombinations({
|
|
31
|
-
modes: modes.map(modeStrToTransportMode),
|
|
32
|
-
to: mockLatLon,
|
|
33
33
|
from: mockLatLon,
|
|
34
|
-
|
|
34
|
+
modes: modes.map(modeStrToTransportMode),
|
|
35
|
+
modeSettings: [],
|
|
36
|
+
to: mockLatLon
|
|
35
37
|
});
|
|
36
38
|
const expandedExpectedModesList = expectedModes.map(em => ({
|
|
37
|
-
modes: em.map(modeStrToTransportMode),
|
|
38
|
-
to: mockLatLon,
|
|
39
39
|
from: mockLatLon,
|
|
40
|
-
|
|
40
|
+
modes: em.map(modeStrToTransportMode),
|
|
41
|
+
modeSettings: [],
|
|
42
|
+
to: mockLatLon
|
|
41
43
|
}));
|
|
42
44
|
return it(
|
|
43
45
|
modes.join(" "),
|
|
@@ -49,6 +51,59 @@ function expectModes(modes, expectedModes) {
|
|
|
49
51
|
);
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
describe("extract-modes", () => {
|
|
55
|
+
const mode = {
|
|
56
|
+
mode: "UNICYCLE"
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const testTransportMode: TransportMode = {
|
|
60
|
+
mode: "testMode"
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const checkboxModeSetting: ModeSetting = {
|
|
64
|
+
addTransportMode: mode,
|
|
65
|
+
applicableMode: testTransportMode.mode,
|
|
66
|
+
icon: null,
|
|
67
|
+
key: "test",
|
|
68
|
+
label: "test",
|
|
69
|
+
type: "CHECKBOX",
|
|
70
|
+
value: true
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const dropdownModeSetting: ModeSetting = {
|
|
74
|
+
applicableMode: testTransportMode.mode,
|
|
75
|
+
key: "test",
|
|
76
|
+
label: "test",
|
|
77
|
+
options: [{ text: "testOption", value: "1", addTransportMode: mode }],
|
|
78
|
+
type: "DROPDOWN",
|
|
79
|
+
value: "1"
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
it("determines whether a checkbox setting is extracted correctly", () => {
|
|
83
|
+
expect(
|
|
84
|
+
extractAdditionalModes([checkboxModeSetting], [testTransportMode])
|
|
85
|
+
).toEqual([mode]);
|
|
86
|
+
});
|
|
87
|
+
it("determines whether a dropdown setting is extracted correctly", () => {
|
|
88
|
+
expect(
|
|
89
|
+
extractAdditionalModes([dropdownModeSetting], [testTransportMode])
|
|
90
|
+
).toEqual([mode]);
|
|
91
|
+
});
|
|
92
|
+
it("determines whether a checkbox setting set to false is ignored", () => {
|
|
93
|
+
expect(
|
|
94
|
+
extractAdditionalModes(
|
|
95
|
+
[{ ...checkboxModeSetting, value: false }],
|
|
96
|
+
[testTransportMode]
|
|
97
|
+
)
|
|
98
|
+
).toEqual([]);
|
|
99
|
+
});
|
|
100
|
+
it("determines whether a checkbox setting with no modes is ignored", () => {
|
|
101
|
+
expect(extractAdditionalModes([{ ...checkboxModeSetting }], [])).toEqual(
|
|
102
|
+
[]
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
52
107
|
describe("query-gen", () => {
|
|
53
108
|
describe("generateCombinations", () => {
|
|
54
109
|
expectModes(["WALK"], [["WALK"]]);
|
|
@@ -93,6 +148,10 @@ describe("query-gen", () => {
|
|
|
93
148
|
["WALK"]
|
|
94
149
|
]
|
|
95
150
|
);
|
|
151
|
+
expectModes(
|
|
152
|
+
["BICYCLE_RENT", "BICYCLE", "WALK"],
|
|
153
|
+
[["BICYCLE_RENT", "WALK"], ["BICYCLE"], ["WALK"]]
|
|
154
|
+
);
|
|
96
155
|
expectModes(
|
|
97
156
|
["SCOOTER_RENT", "BICYCLE_RENT", "TRANSIT", "WALK"],
|
|
98
157
|
[
|
|
@@ -104,6 +163,26 @@ describe("query-gen", () => {
|
|
|
104
163
|
["WALK"]
|
|
105
164
|
]
|
|
106
165
|
);
|
|
166
|
+
|
|
167
|
+
expectModes(
|
|
168
|
+
["CAR_HAIL", "TRANSIT"],
|
|
169
|
+
[["TRANSIT"], ["CAR_HAIL", "TRANSIT"]]
|
|
170
|
+
);
|
|
171
|
+
expectModes(
|
|
172
|
+
["CAR_HAIL", "BICYCLE_RENT", "TRANSIT"],
|
|
173
|
+
[["TRANSIT"], ["CAR_HAIL", "TRANSIT"], ["BICYCLE_RENT", "TRANSIT"]]
|
|
174
|
+
);
|
|
175
|
+
expectModes(
|
|
176
|
+
["CAR_HAIL", "BICYCLE_RENT", "TRANSIT", "WALK"],
|
|
177
|
+
[
|
|
178
|
+
["TRANSIT"],
|
|
179
|
+
["CAR_HAIL", "TRANSIT"],
|
|
180
|
+
["CAR_HAIL", "WALK"],
|
|
181
|
+
["BICYCLE_RENT", "TRANSIT"],
|
|
182
|
+
["BICYCLE_RENT", "WALK"],
|
|
183
|
+
["WALK"]
|
|
184
|
+
]
|
|
185
|
+
);
|
|
107
186
|
expectModes(
|
|
108
187
|
["FLEX", "TRANSIT", "WALK"],
|
|
109
188
|
[["TRANSIT"], ["FLEX", "TRANSIT"], ["FLEX", "WALK"], ["WALK"]]
|
|
@@ -131,9 +210,15 @@ describe("query-gen", () => {
|
|
|
131
210
|
]
|
|
132
211
|
);
|
|
133
212
|
expectModes(
|
|
134
|
-
|
|
213
|
+
// Transit is required to enable other transit submodes
|
|
214
|
+
["BUS", "RAIL", "GONDOLA", "TRAM", "TRANSIT"],
|
|
135
215
|
[["BUS", "RAIL", "GONDOLA", "TRAM"]]
|
|
136
216
|
);
|
|
217
|
+
expectModes(
|
|
218
|
+
// Transit is required to enable other transit submodes
|
|
219
|
+
["TRANSIT"],
|
|
220
|
+
[["TRANSIT"]]
|
|
221
|
+
);
|
|
137
222
|
});
|
|
138
223
|
});
|
|
139
224
|
|
package/src/core-utils.story.tsx
CHANGED
|
@@ -50,7 +50,7 @@ export const RouteColorTester = (): JSX.Element => {
|
|
|
50
50
|
</>
|
|
51
51
|
);
|
|
52
52
|
};
|
|
53
|
-
// Disable color contrast checking for the uncorrected color pairs
|
|
53
|
+
// Disable color contrast checking for the uncorrected color pairs.
|
|
54
54
|
RouteColorTester.parameters = {
|
|
55
55
|
a11y: { config: { rules: [{ id: "color-contrast", reviewOnFail: true }] } },
|
|
56
56
|
storyshots: { disable: true }
|
package/src/itinerary.ts
CHANGED
|
@@ -4,9 +4,10 @@ import {
|
|
|
4
4
|
Config,
|
|
5
5
|
ElevationProfile,
|
|
6
6
|
FlexBookingInfo,
|
|
7
|
-
|
|
7
|
+
ItineraryOnlyLegsRequired,
|
|
8
8
|
LatLngArray,
|
|
9
9
|
Leg,
|
|
10
|
+
MassUnitOption,
|
|
10
11
|
Money,
|
|
11
12
|
Place,
|
|
12
13
|
Step,
|
|
@@ -77,6 +78,11 @@ export function legDropoffRequiresAdvanceBooking(leg: Leg): boolean {
|
|
|
77
78
|
return isAdvanceBookingRequired(leg.dropOffBookingInfo);
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
// alpha-only comment
|
|
82
|
+
export function isRideshareLeg(leg: Leg): boolean {
|
|
83
|
+
return !!leg.rideHailingEstimate?.provider?.id;
|
|
84
|
+
}
|
|
85
|
+
|
|
80
86
|
export function isWalk(mode: string): boolean {
|
|
81
87
|
if (!mode) return false;
|
|
82
88
|
|
|
@@ -192,16 +198,26 @@ export function toSentenceCase(str: string): string {
|
|
|
192
198
|
*/
|
|
193
199
|
export function getCompanyFromLeg(leg: Leg): string {
|
|
194
200
|
if (!leg) return null;
|
|
195
|
-
const {
|
|
201
|
+
const {
|
|
202
|
+
from,
|
|
203
|
+
mode,
|
|
204
|
+
rentedBike,
|
|
205
|
+
rentedCar,
|
|
206
|
+
rentedVehicle,
|
|
207
|
+
rideHailingEstimate
|
|
208
|
+
} = leg;
|
|
196
209
|
if (mode === "CAR" && rentedCar) {
|
|
197
210
|
return from.networks[0];
|
|
198
211
|
}
|
|
199
|
-
if (mode === "CAR" &&
|
|
200
|
-
return
|
|
212
|
+
if (mode === "CAR" && rideHailingEstimate) {
|
|
213
|
+
return rideHailingEstimate.provider.id;
|
|
201
214
|
}
|
|
202
215
|
if (mode === "BICYCLE" && rentedBike && from.networks) {
|
|
203
216
|
return from.networks[0];
|
|
204
217
|
}
|
|
218
|
+
if (from.rentalVehicle) {
|
|
219
|
+
return from.rentalVehicle.network;
|
|
220
|
+
}
|
|
205
221
|
if (
|
|
206
222
|
(mode === "MICROMOBILITY" || mode === "SCOOTER") &&
|
|
207
223
|
rentedVehicle &&
|
|
@@ -212,7 +228,9 @@ export function getCompanyFromLeg(leg: Leg): string {
|
|
|
212
228
|
return null;
|
|
213
229
|
}
|
|
214
230
|
|
|
215
|
-
export function getItineraryBounds(
|
|
231
|
+
export function getItineraryBounds(
|
|
232
|
+
itinerary: ItineraryOnlyLegsRequired
|
|
233
|
+
): LatLngArray[] {
|
|
216
234
|
let coords = [];
|
|
217
235
|
itinerary.legs.forEach(leg => {
|
|
218
236
|
const legCoords = polyline
|
|
@@ -391,10 +409,13 @@ export function getCompanyForNetwork(
|
|
|
391
409
|
* @return {string} A label for use in presentation on a website.
|
|
392
410
|
*/
|
|
393
411
|
export function getCompaniesLabelFromNetworks(
|
|
394
|
-
networks: string[],
|
|
412
|
+
networks: string | string[],
|
|
395
413
|
companies: Company[] = []
|
|
396
414
|
): string {
|
|
397
|
-
|
|
415
|
+
let networksArray = networks;
|
|
416
|
+
if (typeof networks === "string") networksArray = [networks];
|
|
417
|
+
|
|
418
|
+
return (networksArray as string[])
|
|
398
419
|
.map(network => getCompanyForNetwork(network, companies))
|
|
399
420
|
.filter(co => !!co)
|
|
400
421
|
.map(co => co.label)
|
|
@@ -407,7 +428,7 @@ export function getTNCLocation(leg: Leg, type: string): string {
|
|
|
407
428
|
}
|
|
408
429
|
|
|
409
430
|
export function calculatePhysicalActivity(
|
|
410
|
-
itinerary:
|
|
431
|
+
itinerary: ItineraryOnlyLegsRequired
|
|
411
432
|
): {
|
|
412
433
|
bikeDuration: number;
|
|
413
434
|
caloriesBurned: number;
|
|
@@ -433,17 +454,19 @@ export function calculatePhysicalActivity(
|
|
|
433
454
|
* these values and currency info.
|
|
434
455
|
* It is assumed that the same currency is used for all TNC legs.
|
|
435
456
|
*/
|
|
436
|
-
export function calculateTncFares(
|
|
457
|
+
export function calculateTncFares(
|
|
458
|
+
itinerary: ItineraryOnlyLegsRequired
|
|
459
|
+
): TncFare {
|
|
437
460
|
return itinerary.legs
|
|
438
|
-
.filter(leg => leg.mode === "CAR" && leg.
|
|
461
|
+
.filter(leg => leg.mode === "CAR" && leg.rideHailingEstimate)
|
|
439
462
|
.reduce(
|
|
440
|
-
({ maxTNCFare, minTNCFare }, {
|
|
441
|
-
const {
|
|
463
|
+
({ maxTNCFare, minTNCFare }, { rideHailingEstimate }) => {
|
|
464
|
+
const { minPrice, maxPrice } = rideHailingEstimate;
|
|
442
465
|
return {
|
|
443
466
|
// Assumes a single currency for entire itinerary.
|
|
444
|
-
currencyCode: currency,
|
|
445
|
-
maxTNCFare: maxTNCFare +
|
|
446
|
-
minTNCFare: minTNCFare +
|
|
467
|
+
currencyCode: minPrice.currency.code,
|
|
468
|
+
maxTNCFare: maxTNCFare + maxPrice.amount,
|
|
469
|
+
minTNCFare: minTNCFare + minPrice.amount
|
|
447
470
|
};
|
|
448
471
|
},
|
|
449
472
|
{
|
|
@@ -454,27 +477,6 @@ export function calculateTncFares(itinerary: Itinerary): TncFare {
|
|
|
454
477
|
);
|
|
455
478
|
}
|
|
456
479
|
|
|
457
|
-
/**
|
|
458
|
-
* For a given fare component (either total fare or component parts), returns
|
|
459
|
-
* an object with the fare value (in cents).
|
|
460
|
-
*/
|
|
461
|
-
export function getTransitFare(
|
|
462
|
-
fareComponent: Money
|
|
463
|
-
): {
|
|
464
|
-
currencyCode: string;
|
|
465
|
-
transitFare: number;
|
|
466
|
-
} {
|
|
467
|
-
return fareComponent
|
|
468
|
-
? {
|
|
469
|
-
currencyCode: fareComponent.currency.currencyCode,
|
|
470
|
-
transitFare: fareComponent.cents
|
|
471
|
-
}
|
|
472
|
-
: {
|
|
473
|
-
currencyCode: "USD",
|
|
474
|
-
transitFare: 0
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
|
|
478
480
|
/**
|
|
479
481
|
* Sources:
|
|
480
482
|
* - https://www.itf-oecd.org/sites/default/files/docs/environmental-performance-new-mobility.pdf
|
|
@@ -501,15 +503,16 @@ const CARBON_INTENSITY_DEFAULTS = {
|
|
|
501
503
|
};
|
|
502
504
|
|
|
503
505
|
/**
|
|
504
|
-
* @param
|
|
505
|
-
* @param
|
|
506
|
+
* @param {itinerary} itinerary OTP trip itinierary, only legs is required.
|
|
507
|
+
* @param {carbonIntensity} carbonIntensity carbon intensity by mode in grams/meter
|
|
506
508
|
* @param {units} units units to be used in return value
|
|
507
509
|
* @return Amount of carbon in chosen unit
|
|
508
510
|
*/
|
|
509
511
|
export function calculateEmissions(
|
|
510
|
-
|
|
512
|
+
// This type makes all the properties from Itinerary optional except legs.
|
|
513
|
+
itinerary: ItineraryOnlyLegsRequired,
|
|
511
514
|
carbonIntensity: Record<string, number> = {},
|
|
512
|
-
units?:
|
|
515
|
+
units?: MassUnitOption
|
|
513
516
|
): number {
|
|
514
517
|
// Apply defaults for any values that we don't have.
|
|
515
518
|
const carbonIntensityWithDefaults = {
|
|
@@ -555,3 +558,59 @@ export function getDisplayedStopId(placeOrStop: Place | Stop): string {
|
|
|
555
558
|
}
|
|
556
559
|
return stopCode || stopId?.split(":")[1] || stopId;
|
|
557
560
|
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Extracts useful data from the fare products on a leg, such as the leg cost and transfer info.
|
|
564
|
+
* @param leg Leg with fare products (must have used getLegsWithFares)
|
|
565
|
+
* @param category Rider category
|
|
566
|
+
* @param container Fare container (cash, electronic)
|
|
567
|
+
* @returns Object containing price as well as the transfer discount amount, if a transfer was used.
|
|
568
|
+
*/
|
|
569
|
+
export function getLegCost(
|
|
570
|
+
leg: Leg,
|
|
571
|
+
mediumId: string,
|
|
572
|
+
riderCategoryId: string
|
|
573
|
+
): { price: Money; transferAmount?: Money | undefined } {
|
|
574
|
+
if (!leg.fareProducts) return { price: undefined };
|
|
575
|
+
const relevantFareProducts = leg.fareProducts.filter(({ product }) => {
|
|
576
|
+
return (
|
|
577
|
+
product.riderCategory.id === riderCategoryId &&
|
|
578
|
+
product.medium.id === mediumId
|
|
579
|
+
);
|
|
580
|
+
});
|
|
581
|
+
const totalCost = relevantFareProducts.find(
|
|
582
|
+
fp => fp.product.name === "rideCost"
|
|
583
|
+
)?.product?.price;
|
|
584
|
+
const transferFareProduct = relevantFareProducts.find(
|
|
585
|
+
fp => fp.product.name === "transfer"
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
return {
|
|
589
|
+
price: totalCost,
|
|
590
|
+
transferAmount: transferFareProduct?.product.price
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Returns the total itinerary cost for a given set of legs.
|
|
596
|
+
* @param legs Itinerary legs with fare products (must have used getLegsWithFares)
|
|
597
|
+
* @param category Rider category (youth, regular, senior)
|
|
598
|
+
* @param container Fare container (cash, electronic)
|
|
599
|
+
* @returns Money object for the total itinerary cost.
|
|
600
|
+
*/
|
|
601
|
+
export function getItineraryCost(
|
|
602
|
+
legs: Leg[],
|
|
603
|
+
category: string,
|
|
604
|
+
container: string
|
|
605
|
+
): Money {
|
|
606
|
+
return legs
|
|
607
|
+
.filter(leg => !!leg.fareProducts)
|
|
608
|
+
.map(leg => getLegCost(leg, category, container).price)
|
|
609
|
+
.reduce<Money>(
|
|
610
|
+
(prev, cur) => ({
|
|
611
|
+
amount: prev.amount + cur?.amount || 0,
|
|
612
|
+
currency: prev.currency ?? cur?.currency
|
|
613
|
+
}),
|
|
614
|
+
{ amount: 0, currency: null }
|
|
615
|
+
);
|
|
616
|
+
}
|