gtfs-to-html 2.4.4 → 2.5.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/CHANGELOG.md +14 -0
- package/app/index.js +5 -3
- package/lib/geojson-utils.js +19 -26
- package/lib/gtfs-to-html.js +8 -15
- package/lib/log-utils.js +2 -2
- package/lib/utils.js +296 -330
- package/package.json +11 -10
- package/www/docs/configuration.md +1 -1
- package/www/package.json +2 -2
- package/www/yarn.lock +693 -667
package/lib/utils.js
CHANGED
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
getTimetableNotesReferences,
|
|
28
28
|
getTimetableNotes,
|
|
29
29
|
getRoutes,
|
|
30
|
-
getDb,
|
|
31
30
|
getCalendars,
|
|
32
31
|
getTimetableStopOrders,
|
|
33
32
|
getStops,
|
|
@@ -37,6 +36,7 @@ import {
|
|
|
37
36
|
getTimetables,
|
|
38
37
|
getTimetablePages,
|
|
39
38
|
getAgencies,
|
|
39
|
+
openDb,
|
|
40
40
|
} from 'gtfs';
|
|
41
41
|
import { stringify } from 'csv-stringify';
|
|
42
42
|
import moment from 'moment';
|
|
@@ -275,8 +275,8 @@ const sortTripsByStoptimeAtStop = (trips, stopId) =>
|
|
|
275
275
|
/*
|
|
276
276
|
* Get all calendar dates for a specific timetable.
|
|
277
277
|
*/
|
|
278
|
-
const getCalendarDatesForTimetable =
|
|
279
|
-
const calendarDates =
|
|
278
|
+
const getCalendarDatesForTimetable = (timetable, config) => {
|
|
279
|
+
const calendarDates = getCalendarDates(
|
|
280
280
|
{
|
|
281
281
|
service_id: timetable.service_ids,
|
|
282
282
|
},
|
|
@@ -334,8 +334,8 @@ const getDaysFromCalendars = (calendars) => {
|
|
|
334
334
|
/*
|
|
335
335
|
* Get the `trip_headsign` for a specific timetable.
|
|
336
336
|
*/
|
|
337
|
-
const getDirectionHeadsignFromTimetable =
|
|
338
|
-
const trips =
|
|
337
|
+
const getDirectionHeadsignFromTimetable = (timetable) => {
|
|
338
|
+
const trips = getTrips(
|
|
339
339
|
{
|
|
340
340
|
direction_id: timetable.direction_id,
|
|
341
341
|
route_id: timetable.route_ids,
|
|
@@ -353,31 +353,31 @@ const getDirectionHeadsignFromTimetable = async (timetable) => {
|
|
|
353
353
|
/*
|
|
354
354
|
* Get the notes for a specific timetable.
|
|
355
355
|
*/
|
|
356
|
-
const getTimetableNotesForTimetable =
|
|
356
|
+
const getTimetableNotesForTimetable = (timetable, config) => {
|
|
357
357
|
const noteReferences = [
|
|
358
358
|
// Get all notes for this timetable.
|
|
359
|
-
...
|
|
359
|
+
...getTimetableNotesReferences({
|
|
360
360
|
timetable_id: timetable.timetable_id,
|
|
361
|
-
})
|
|
361
|
+
}),
|
|
362
362
|
|
|
363
363
|
// Get all notes for this route.
|
|
364
|
-
...
|
|
364
|
+
...getTimetableNotesReferences({
|
|
365
365
|
route_id: timetable.routes.map((route) => route.route_id),
|
|
366
366
|
timetable_id: null,
|
|
367
|
-
})
|
|
367
|
+
}),
|
|
368
368
|
|
|
369
369
|
// Get all notes for all trips in this timetable.
|
|
370
|
-
...
|
|
370
|
+
...getTimetableNotesReferences({
|
|
371
371
|
trip_id: timetable.orderedTrips.map((trip) => trip.trip_id),
|
|
372
|
-
})
|
|
372
|
+
}),
|
|
373
373
|
|
|
374
374
|
// Get all notes for all stops in this timetable.
|
|
375
|
-
...
|
|
375
|
+
...getTimetableNotesReferences({
|
|
376
376
|
stop_id: timetable.stops.map((stop) => stop.stop_id),
|
|
377
377
|
trip_id: null,
|
|
378
378
|
route_id: null,
|
|
379
379
|
timetable_id: null,
|
|
380
|
-
})
|
|
380
|
+
}),
|
|
381
381
|
];
|
|
382
382
|
|
|
383
383
|
const usedNoteReferences = [];
|
|
@@ -416,7 +416,7 @@ const getTimetableNotesForTimetable = async (timetable, config) => {
|
|
|
416
416
|
}
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
-
const notes =
|
|
419
|
+
const notes = getTimetableNotes({
|
|
420
420
|
note_id: usedNoteReferences.map((noteReference) => noteReference.note_id),
|
|
421
421
|
});
|
|
422
422
|
|
|
@@ -445,14 +445,14 @@ const getTimetableNotesForTimetable = async (timetable, config) => {
|
|
|
445
445
|
* Create a timetable page from a single timetable. Used if no
|
|
446
446
|
* `timetable_pages.txt` is present.
|
|
447
447
|
*/
|
|
448
|
-
const convertTimetableToTimetablePage =
|
|
448
|
+
const convertTimetableToTimetablePage = (timetable, config) => {
|
|
449
449
|
if (!timetable.routes) {
|
|
450
|
-
timetable.routes =
|
|
450
|
+
timetable.routes = getRoutes({
|
|
451
451
|
route_id: timetable.route_ids,
|
|
452
452
|
});
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
-
const filename =
|
|
455
|
+
const filename = generateFileName(timetable, config);
|
|
456
456
|
|
|
457
457
|
return {
|
|
458
458
|
timetable_page_id: timetable.timetable_id,
|
|
@@ -517,61 +517,47 @@ const convertRouteToTimetablePage = (
|
|
|
517
517
|
* Create timetable pages for all routes in an agency. Used if no
|
|
518
518
|
* `timetables.txt` is present.
|
|
519
519
|
*/
|
|
520
|
-
const convertRoutesToTimetablePages =
|
|
521
|
-
const db =
|
|
522
|
-
const routes =
|
|
523
|
-
const calendars =
|
|
520
|
+
const convertRoutesToTimetablePages = (config) => {
|
|
521
|
+
const db = openDb(config);
|
|
522
|
+
const routes = getRoutes();
|
|
523
|
+
const calendars = getCalendars();
|
|
524
524
|
|
|
525
525
|
// Find all calendar dates with service_ids not present in `calendar.txt`.
|
|
526
526
|
const serviceIds = calendars.map((calendar) => calendar.service_id);
|
|
527
|
-
const calendarDates =
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
527
|
+
const calendarDates = db
|
|
528
|
+
.prepare(
|
|
529
|
+
`SELECT * FROM calendar_dates WHERE exception_type = 1 AND service_id NOT IN (${serviceIds
|
|
530
|
+
.map((serviceId) => `'${serviceId}'`)
|
|
531
|
+
.join(', ')})`
|
|
532
|
+
)
|
|
533
|
+
.all();
|
|
534
|
+
|
|
535
|
+
const timetablePages = routes.map((route) => {
|
|
536
|
+
const trips = getTrips(
|
|
537
|
+
{
|
|
538
|
+
route_id: route.route_id,
|
|
539
|
+
},
|
|
540
|
+
['trip_headsign', 'direction_id']
|
|
541
|
+
);
|
|
542
|
+
const directions = uniqBy(trips, (trip) => trip.direction_id);
|
|
543
|
+
const dayGroups = groupBy(calendars, calendarToCalendarCode);
|
|
544
|
+
const calendarDateGroups = groupBy(calendarDates, 'service_id');
|
|
532
545
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
return Promise.all(
|
|
546
|
-
directions.map((direction) =>
|
|
547
|
-
Promise.all([
|
|
548
|
-
Promise.all(
|
|
549
|
-
Object.values(dayGroups).map((calendars) =>
|
|
550
|
-
convertRouteToTimetablePage(
|
|
551
|
-
route,
|
|
552
|
-
direction,
|
|
553
|
-
calendars,
|
|
554
|
-
null,
|
|
555
|
-
config
|
|
556
|
-
)
|
|
557
|
-
)
|
|
558
|
-
),
|
|
559
|
-
Promise.all(
|
|
560
|
-
Object.values(calendarDateGroups).map((calendarDates) =>
|
|
561
|
-
convertRouteToTimetablePage(
|
|
562
|
-
route,
|
|
563
|
-
direction,
|
|
564
|
-
null,
|
|
565
|
-
calendarDates,
|
|
566
|
-
config
|
|
567
|
-
)
|
|
568
|
-
)
|
|
569
|
-
),
|
|
570
|
-
])
|
|
546
|
+
return directions.map((direction) => [
|
|
547
|
+
Object.values(dayGroups).map((calendars) =>
|
|
548
|
+
convertRouteToTimetablePage(route, direction, calendars, null, config)
|
|
549
|
+
),
|
|
550
|
+
Object.values(calendarDateGroups).map((calendarDates) =>
|
|
551
|
+
convertRouteToTimetablePage(
|
|
552
|
+
route,
|
|
553
|
+
direction,
|
|
554
|
+
null,
|
|
555
|
+
calendarDates,
|
|
556
|
+
config
|
|
571
557
|
)
|
|
572
|
-
)
|
|
573
|
-
|
|
574
|
-
);
|
|
558
|
+
),
|
|
559
|
+
]);
|
|
560
|
+
});
|
|
575
561
|
|
|
576
562
|
return compact(flattenDeep(timetablePages));
|
|
577
563
|
};
|
|
@@ -616,6 +602,10 @@ const duplicateStopsForDifferentArrivalDeparture = (
|
|
|
616
602
|
timetable,
|
|
617
603
|
config
|
|
618
604
|
) => {
|
|
605
|
+
if (config.showArrivalOnDifference === null) {
|
|
606
|
+
return stopIds;
|
|
607
|
+
}
|
|
608
|
+
|
|
619
609
|
for (const trip of timetable.orderedTrips) {
|
|
620
610
|
for (const stoptime of trip.stoptimes) {
|
|
621
611
|
const timepointDifference = fromGTFSTime(stoptime.departure_time).diff(
|
|
@@ -623,10 +613,7 @@ const duplicateStopsForDifferentArrivalDeparture = (
|
|
|
623
613
|
'minutes'
|
|
624
614
|
);
|
|
625
615
|
|
|
626
|
-
if (
|
|
627
|
-
config.showArrivalOnDifference === null ||
|
|
628
|
-
timepointDifference < config.showArrivalOnDifference
|
|
629
|
-
) {
|
|
616
|
+
if (timepointDifference < config.showArrivalOnDifference) {
|
|
630
617
|
continue;
|
|
631
618
|
}
|
|
632
619
|
|
|
@@ -652,9 +639,9 @@ const duplicateStopsForDifferentArrivalDeparture = (
|
|
|
652
639
|
/*
|
|
653
640
|
* Get a sorted array of stop_ids for a specific timetable.
|
|
654
641
|
*/
|
|
655
|
-
const getStopOrder =
|
|
642
|
+
const getStopOrder = (timetable, config) => {
|
|
656
643
|
// First, check if `timetable_stop_order.txt` for route exists
|
|
657
|
-
const timetableStopOrders =
|
|
644
|
+
const timetableStopOrders = getTimetableStopOrders(
|
|
658
645
|
{
|
|
659
646
|
timetable_id: timetable.timetable_id,
|
|
660
647
|
},
|
|
@@ -714,45 +701,43 @@ const getStopOrder = async (timetable, config) => {
|
|
|
714
701
|
/*
|
|
715
702
|
* Get an array of stops for a specific timetable.
|
|
716
703
|
*/
|
|
717
|
-
const getStopsForTimetable =
|
|
704
|
+
const getStopsForTimetable = (timetable, config) => {
|
|
718
705
|
if (timetable.orderedTrips.length === 0) {
|
|
719
706
|
return [];
|
|
720
707
|
}
|
|
721
708
|
|
|
722
|
-
const orderedStopIds =
|
|
723
|
-
const orderedStops =
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
});
|
|
709
|
+
const orderedStopIds = getStopOrder(timetable, config);
|
|
710
|
+
const orderedStops = orderedStopIds.map((stopId, index) => {
|
|
711
|
+
const stops = getStops({
|
|
712
|
+
stop_id: stopId,
|
|
713
|
+
});
|
|
728
714
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
715
|
+
if (stops.length === 0) {
|
|
716
|
+
throw new Error(
|
|
717
|
+
`No stop found found for stop_id=${stopId} in timetable_id=${timetable.timetable_id}`
|
|
718
|
+
);
|
|
719
|
+
}
|
|
734
720
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
721
|
+
const stop = {
|
|
722
|
+
...stops[0],
|
|
723
|
+
trips: [],
|
|
724
|
+
};
|
|
739
725
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
726
|
+
if (
|
|
727
|
+
index < orderedStopIds.length - 1 &&
|
|
728
|
+
stopId === orderedStopIds[index + 1]
|
|
729
|
+
) {
|
|
730
|
+
stop.type = 'arrival';
|
|
731
|
+
} else if (index > 0 && stopId === orderedStopIds[index - 1]) {
|
|
732
|
+
stop.type = 'departure';
|
|
733
|
+
}
|
|
748
734
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
);
|
|
735
|
+
return stop;
|
|
736
|
+
});
|
|
752
737
|
|
|
753
738
|
// If `showStopCity` is true, look up stop attributes.
|
|
754
739
|
if (timetable.showStopCity) {
|
|
755
|
-
const stopAttributes =
|
|
740
|
+
const stopAttributes = getStopAttributes({
|
|
756
741
|
stop_id: orderedStopIds,
|
|
757
742
|
});
|
|
758
743
|
|
|
@@ -773,8 +758,8 @@ const getStopsForTimetable = async (timetable, config) => {
|
|
|
773
758
|
/*
|
|
774
759
|
* Get all calendars from a specific timetable.
|
|
775
760
|
*/
|
|
776
|
-
const getCalendarsFromTimetable =
|
|
777
|
-
const db =
|
|
761
|
+
const getCalendarsFromTimetable = (timetable) => {
|
|
762
|
+
const db = openDb();
|
|
778
763
|
let whereClause = '';
|
|
779
764
|
const whereClauses = [];
|
|
780
765
|
|
|
@@ -808,14 +793,14 @@ const getCalendarsFromTimetable = async (timetable) => {
|
|
|
808
793
|
whereClause = `WHERE ${whereClauses.join(' AND ')}`;
|
|
809
794
|
}
|
|
810
795
|
|
|
811
|
-
return db.
|
|
796
|
+
return db.prepare(`SELECT * FROM calendar ${whereClause}`).all();
|
|
812
797
|
};
|
|
813
798
|
|
|
814
799
|
/*
|
|
815
800
|
* Get all calendar date service ids for an agency between two dates.
|
|
816
801
|
*/
|
|
817
|
-
const getCalendarDatesServiceIds =
|
|
818
|
-
const db =
|
|
802
|
+
const getCalendarDatesServiceIds = (startDate, endDate) => {
|
|
803
|
+
const db = openDb();
|
|
819
804
|
const whereClauses = ['exception_type = 1'];
|
|
820
805
|
|
|
821
806
|
if (endDate) {
|
|
@@ -826,11 +811,13 @@ const getCalendarDatesServiceIds = async (startDate, endDate) => {
|
|
|
826
811
|
whereClauses.push(`date >= ${sqlString.escape(startDate)}`);
|
|
827
812
|
}
|
|
828
813
|
|
|
829
|
-
const calendarDates =
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
814
|
+
const calendarDates = db
|
|
815
|
+
.prepare(
|
|
816
|
+
`SELECT DISTINCT service_id FROM calendar_dates WHERE ${whereClauses.join(
|
|
817
|
+
' AND '
|
|
818
|
+
)}`
|
|
819
|
+
)
|
|
820
|
+
.all();
|
|
834
821
|
return calendarDates.map((calendarDate) => calendarDate.service_id);
|
|
835
822
|
};
|
|
836
823
|
|
|
@@ -839,8 +826,8 @@ const getCalendarDatesServiceIds = async (startDate, endDate) => {
|
|
|
839
826
|
* and the stop_id of parent station itself. If no parent station, it returns the
|
|
840
827
|
* stop_id.
|
|
841
828
|
*/
|
|
842
|
-
const getAllStationStopIds =
|
|
843
|
-
const stops =
|
|
829
|
+
const getAllStationStopIds = (stopId) => {
|
|
830
|
+
const stops = getStops({
|
|
844
831
|
stop_id: stopId,
|
|
845
832
|
});
|
|
846
833
|
|
|
@@ -850,7 +837,7 @@ const getAllStationStopIds = async (stopId) => {
|
|
|
850
837
|
return [stopId];
|
|
851
838
|
}
|
|
852
839
|
|
|
853
|
-
const stopsInParentStation =
|
|
840
|
+
const stopsInParentStation = getStops(
|
|
854
841
|
{
|
|
855
842
|
parent_station: stop.parent_station,
|
|
856
843
|
},
|
|
@@ -866,8 +853,8 @@ const getAllStationStopIds = async (stopId) => {
|
|
|
866
853
|
/*
|
|
867
854
|
* Get trips with the same `block_id`.
|
|
868
855
|
*/
|
|
869
|
-
const getTripsWithSameBlock =
|
|
870
|
-
const trips =
|
|
856
|
+
const getTripsWithSameBlock = (trip, timetable) => {
|
|
857
|
+
const trips = getTrips(
|
|
871
858
|
{
|
|
872
859
|
block_id: trip.block_id,
|
|
873
860
|
service_id: timetable.service_ids,
|
|
@@ -875,26 +862,24 @@ const getTripsWithSameBlock = async (trip, timetable) => {
|
|
|
875
862
|
['trip_id', 'route_id']
|
|
876
863
|
);
|
|
877
864
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
);
|
|
865
|
+
for (const blockTrip of trips) {
|
|
866
|
+
const stopTimes = getStoptimes(
|
|
867
|
+
{
|
|
868
|
+
trip_id: blockTrip.trip_id,
|
|
869
|
+
},
|
|
870
|
+
[],
|
|
871
|
+
[['stop_sequence', 'ASC']]
|
|
872
|
+
);
|
|
887
873
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
874
|
+
if (stopTimes.length === 0) {
|
|
875
|
+
throw new Error(
|
|
876
|
+
`No stoptimes found found for trip_id=${blockTrip.trip_id}`
|
|
877
|
+
);
|
|
878
|
+
}
|
|
893
879
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
);
|
|
880
|
+
blockTrip.firstStoptime = first(stopTimes);
|
|
881
|
+
blockTrip.lastStoptime = last(stopTimes);
|
|
882
|
+
}
|
|
898
883
|
|
|
899
884
|
return sortBy(trips, (trip) => trip.firstStoptime.departure_timestamp);
|
|
900
885
|
};
|
|
@@ -903,7 +888,7 @@ const getTripsWithSameBlock = async (trip, timetable) => {
|
|
|
903
888
|
* Get next trip and previous trip with the same `block_id` if it arrives or
|
|
904
889
|
* departs from the same stop and is a different route.
|
|
905
890
|
*/
|
|
906
|
-
const addTripContinuation =
|
|
891
|
+
const addTripContinuation = (trip, timetable) => {
|
|
907
892
|
if (!trip.block_id) {
|
|
908
893
|
return;
|
|
909
894
|
}
|
|
@@ -911,10 +896,10 @@ const addTripContinuation = async (trip, timetable) => {
|
|
|
911
896
|
const maxContinuesAsWaitingTimeSeconds = 60 * 60;
|
|
912
897
|
|
|
913
898
|
const firstStoptime = first(trip.stoptimes);
|
|
914
|
-
const firstStopIds =
|
|
899
|
+
const firstStopIds = getAllStationStopIds(firstStoptime.stop_id);
|
|
915
900
|
const lastStoptime = last(trip.stoptimes);
|
|
916
|
-
const lastStopIds =
|
|
917
|
-
const blockTrips =
|
|
901
|
+
const lastStopIds = getAllStationStopIds(lastStoptime.stop_id);
|
|
902
|
+
const blockTrips = getTripsWithSameBlock(trip, timetable);
|
|
918
903
|
|
|
919
904
|
// "Continues From" trips must be the previous trip chronologically.
|
|
920
905
|
const previousTrip = findLast(
|
|
@@ -937,7 +922,7 @@ const addTripContinuation = async (trip, timetable) => {
|
|
|
937
922
|
firstStoptime.departure_timestamp - maxContinuesAsWaitingTimeSeconds &&
|
|
938
923
|
firstStopIds.includes(previousTrip.lastStoptime.stop_id)
|
|
939
924
|
) {
|
|
940
|
-
const routes =
|
|
925
|
+
const routes = getRoutes({
|
|
941
926
|
route_id: previousTrip.route_id,
|
|
942
927
|
});
|
|
943
928
|
|
|
@@ -968,7 +953,7 @@ const addTripContinuation = async (trip, timetable) => {
|
|
|
968
953
|
lastStoptime.arrival_timestamp + maxContinuesAsWaitingTimeSeconds &&
|
|
969
954
|
lastStopIds.includes(nextTrip.firstStoptime.stop_id)
|
|
970
955
|
) {
|
|
971
|
-
const routes =
|
|
956
|
+
const routes = getRoutes({
|
|
972
957
|
route_id: nextTrip.route_id,
|
|
973
958
|
});
|
|
974
959
|
|
|
@@ -1021,7 +1006,9 @@ const filterTrips = (timetable) => {
|
|
|
1021
1006
|
/*
|
|
1022
1007
|
* Get all trips from a timetable.
|
|
1023
1008
|
*/
|
|
1024
|
-
|
|
1009
|
+
|
|
1010
|
+
/* eslint-disable complexity */
|
|
1011
|
+
const getTripsForTimetable = (timetable, calendars, config) => {
|
|
1025
1012
|
const tripQuery = {
|
|
1026
1013
|
route_id: timetable.route_ids,
|
|
1027
1014
|
service_id: timetable.service_ids,
|
|
@@ -1031,7 +1018,7 @@ const getTripsForTimetable = async (timetable, calendars, config) => {
|
|
|
1031
1018
|
tripQuery.direction_id = timetable.direction_id;
|
|
1032
1019
|
}
|
|
1033
1020
|
|
|
1034
|
-
const trips =
|
|
1021
|
+
const trips = getTrips(tripQuery);
|
|
1035
1022
|
|
|
1036
1023
|
if (trips.length === 0) {
|
|
1037
1024
|
timetable.warnings.push(
|
|
@@ -1043,7 +1030,7 @@ const getTripsForTimetable = async (timetable, calendars, config) => {
|
|
|
1043
1030
|
);
|
|
1044
1031
|
}
|
|
1045
1032
|
|
|
1046
|
-
const frequencies =
|
|
1033
|
+
const frequencies = getFrequencies({
|
|
1047
1034
|
trip_id: trips.map((trip) => trip.trip_id),
|
|
1048
1035
|
});
|
|
1049
1036
|
|
|
@@ -1051,81 +1038,78 @@ const getTripsForTimetable = async (timetable, calendars, config) => {
|
|
|
1051
1038
|
timetable.service_ids = uniq(trips.map((trip) => trip.service_id));
|
|
1052
1039
|
|
|
1053
1040
|
const formattedTrips = [];
|
|
1054
|
-
await Promise.all(
|
|
1055
|
-
trips.map(async (trip) => {
|
|
1056
|
-
const formattedTrip = formatTrip(trip, timetable, calendars, config);
|
|
1057
|
-
formattedTrip.stoptimes = await getStoptimes(
|
|
1058
|
-
{
|
|
1059
|
-
trip_id: formattedTrip.trip_id,
|
|
1060
|
-
},
|
|
1061
|
-
[],
|
|
1062
|
-
[['stop_sequence', 'ASC']]
|
|
1063
|
-
);
|
|
1064
1041
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1042
|
+
for (const trip of trips) {
|
|
1043
|
+
const formattedTrip = formatTrip(trip, timetable, calendars, config);
|
|
1044
|
+
formattedTrip.stoptimes = getStoptimes(
|
|
1045
|
+
{
|
|
1046
|
+
trip_id: formattedTrip.trip_id,
|
|
1047
|
+
},
|
|
1048
|
+
[],
|
|
1049
|
+
[['stop_sequence', 'ASC']]
|
|
1050
|
+
);
|
|
1074
1051
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
timetable.
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
}
|
|
1052
|
+
if (formattedTrip.stoptimes.length === 0) {
|
|
1053
|
+
timetable.warnings.push(
|
|
1054
|
+
`No stoptimes found for trip_id=${
|
|
1055
|
+
formattedTrip.trip_id
|
|
1056
|
+
}, route_id=${timetable.route_ids.join('_')}, timetable_id=${
|
|
1057
|
+
timetable.timetable_id
|
|
1058
|
+
}`
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1085
1061
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
}
|
|
1062
|
+
// Exclude trips before timetable `start_timestamp`
|
|
1063
|
+
if (
|
|
1064
|
+
timetable.start_timestamp !== '' &&
|
|
1065
|
+
timetable.start_timestamp !== null &&
|
|
1066
|
+
timetable.start_timestamp !== undefined &&
|
|
1067
|
+
trip.stoptimes[0].arrival_timestamp < timetable.start_timestamp
|
|
1068
|
+
) {
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1096
1071
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1072
|
+
// Exclude trips after timetable `end_timestamp`
|
|
1073
|
+
if (
|
|
1074
|
+
timetable.end_timestamp !== '' &&
|
|
1075
|
+
timetable.end_timestamp !== null &&
|
|
1076
|
+
timetable.end_timestamp !== undefined &&
|
|
1077
|
+
trip.stoptimes[0].arrival_timestamp >= timetable.end_timestamp
|
|
1078
|
+
) {
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1099
1081
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
}
|
|
1082
|
+
if (timetable.show_trip_continuation) {
|
|
1083
|
+
addTripContinuation(formattedTrip, timetable);
|
|
1103
1084
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
}
|
|
1085
|
+
if (formattedTrip.continues_as_route) {
|
|
1086
|
+
timetable.has_continues_as_route = true;
|
|
1107
1087
|
}
|
|
1108
1088
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
);
|
|
1112
|
-
|
|
1113
|
-
if (tripFrequencies.length === 0) {
|
|
1114
|
-
formattedTrips.push(formattedTrip);
|
|
1115
|
-
} else {
|
|
1116
|
-
const frequencyTrips = generateTripsByFrequencies(
|
|
1117
|
-
formattedTrip,
|
|
1118
|
-
frequencies,
|
|
1119
|
-
config
|
|
1120
|
-
);
|
|
1121
|
-
formattedTrips.push(...frequencyTrips);
|
|
1122
|
-
timetable.frequencies = frequencies;
|
|
1123
|
-
timetable.frequencyExactTimes = some(frequencies, {
|
|
1124
|
-
exact_times: 1,
|
|
1125
|
-
});
|
|
1089
|
+
if (formattedTrip.continues_from_route) {
|
|
1090
|
+
timetable.has_continues_from_route = true;
|
|
1126
1091
|
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const tripFrequencies = frequencies.filter(
|
|
1095
|
+
(frequency) => frequency.trip_id === trip.trip_id
|
|
1096
|
+
);
|
|
1097
|
+
|
|
1098
|
+
if (tripFrequencies.length === 0) {
|
|
1099
|
+
formattedTrips.push(formattedTrip);
|
|
1100
|
+
} else {
|
|
1101
|
+
const frequencyTrips = generateTripsByFrequencies(
|
|
1102
|
+
formattedTrip,
|
|
1103
|
+
frequencies,
|
|
1104
|
+
config
|
|
1105
|
+
);
|
|
1106
|
+
formattedTrips.push(...frequencyTrips);
|
|
1107
|
+
timetable.frequencies = frequencies;
|
|
1108
|
+
timetable.frequencyExactTimes = some(frequencies, {
|
|
1109
|
+
exact_times: 1,
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1129
1113
|
|
|
1130
1114
|
if (config.useParentStation) {
|
|
1131
1115
|
const stopIds = [];
|
|
@@ -1136,7 +1120,7 @@ const getTripsForTimetable = async (timetable, calendars, config) => {
|
|
|
1136
1120
|
}
|
|
1137
1121
|
}
|
|
1138
1122
|
|
|
1139
|
-
const stops =
|
|
1123
|
+
const stops = getStops(
|
|
1140
1124
|
{
|
|
1141
1125
|
stop_id: uniq(stopIds),
|
|
1142
1126
|
},
|
|
@@ -1156,73 +1140,65 @@ const getTripsForTimetable = async (timetable, calendars, config) => {
|
|
|
1156
1140
|
|
|
1157
1141
|
return sortTrips(formattedTrips, config);
|
|
1158
1142
|
};
|
|
1143
|
+
/* eslint-enable complexity */
|
|
1159
1144
|
|
|
1160
1145
|
/*
|
|
1161
1146
|
* Format timetables for display.
|
|
1162
1147
|
*/
|
|
1163
|
-
const formatTimetables =
|
|
1164
|
-
const formattedTimetables =
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
}
|
|
1148
|
+
const formatTimetables = (timetables, config) => {
|
|
1149
|
+
const formattedTimetables = timetables.map((timetable) => {
|
|
1150
|
+
timetable.warnings = [];
|
|
1151
|
+
const dayList = formatDays(timetable, config);
|
|
1152
|
+
const calendars = getCalendarsFromTimetable(timetable);
|
|
1153
|
+
let serviceIds = calendars.map((calendar) => calendar.service_id);
|
|
1154
|
+
|
|
1155
|
+
if (timetable.include_exceptions === 1) {
|
|
1156
|
+
const calendarDatesServiceIds = getCalendarDatesServiceIds(
|
|
1157
|
+
timetable.start_date,
|
|
1158
|
+
timetable.end_date
|
|
1159
|
+
);
|
|
1160
|
+
serviceIds = uniq([...serviceIds, ...calendarDatesServiceIds]);
|
|
1161
|
+
}
|
|
1178
1162
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1163
|
+
Object.assign(timetable, {
|
|
1164
|
+
noServiceSymbolUsed: false,
|
|
1165
|
+
requestDropoffSymbolUsed: false,
|
|
1166
|
+
noDropoffSymbolUsed: false,
|
|
1167
|
+
requestPickupSymbolUsed: false,
|
|
1168
|
+
noPickupSymbolUsed: false,
|
|
1169
|
+
interpolatedStopSymbolUsed: false,
|
|
1170
|
+
showStopCity: config.showStopCity,
|
|
1171
|
+
showStopDescription: config.showStopDescription,
|
|
1172
|
+
noServiceSymbol: config.noServiceSymbol,
|
|
1173
|
+
requestDropoffSymbol: config.requestDropoffSymbol,
|
|
1174
|
+
noDropoffSymbol: config.noDropoffSymbol,
|
|
1175
|
+
requestPickupSymbol: config.requestPickupSymbol,
|
|
1176
|
+
noPickupSymbol: config.noPickupSymbol,
|
|
1177
|
+
interpolatedStopSymbol: config.interpolatedStopSymbol,
|
|
1178
|
+
orientation: timetable.orientation || config.defaultOrientation,
|
|
1179
|
+
service_ids: serviceIds,
|
|
1180
|
+
dayList,
|
|
1181
|
+
dayListLong: formatDaysLong(dayList, config),
|
|
1182
|
+
});
|
|
1199
1183
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
timetable.stops = await getStopsForTimetable(timetable, config);
|
|
1206
|
-
timetable.calendarDates = await getCalendarDatesForTimetable(
|
|
1207
|
-
timetable,
|
|
1208
|
-
config
|
|
1209
|
-
);
|
|
1210
|
-
timetable.timetable_label = formatTimetableLabel(timetable);
|
|
1211
|
-
timetable.notes = await getTimetableNotesForTimetable(timetable, config);
|
|
1184
|
+
timetable.orderedTrips = getTripsForTimetable(timetable, calendars, config);
|
|
1185
|
+
timetable.stops = getStopsForTimetable(timetable, config);
|
|
1186
|
+
timetable.calendarDates = getCalendarDatesForTimetable(timetable, config);
|
|
1187
|
+
timetable.timetable_label = formatTimetableLabel(timetable);
|
|
1188
|
+
timetable.notes = getTimetableNotesForTimetable(timetable, config);
|
|
1212
1189
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1190
|
+
if (config.showMap) {
|
|
1191
|
+
timetable.geojson = getTimetableGeoJSON(timetable, config);
|
|
1192
|
+
}
|
|
1216
1193
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1194
|
+
// Filter trips after all timetable properties are assigned
|
|
1195
|
+
timetable.orderedTrips = filterTrips(timetable);
|
|
1219
1196
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1197
|
+
// Format stops after all timetable properties are assigned
|
|
1198
|
+
timetable.stops = formatStops(timetable, config);
|
|
1222
1199
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
);
|
|
1200
|
+
return timetable;
|
|
1201
|
+
});
|
|
1226
1202
|
|
|
1227
1203
|
if (config.allowEmptyTimetables) {
|
|
1228
1204
|
return formattedTimetables;
|
|
@@ -1236,15 +1212,15 @@ const formatTimetables = async (timetables, config) => {
|
|
|
1236
1212
|
/*
|
|
1237
1213
|
* Get all timetable pages for an agency.
|
|
1238
1214
|
*/
|
|
1239
|
-
export
|
|
1240
|
-
const timetables = mergeTimetablesWithSameId(
|
|
1215
|
+
export function getTimetablePagesForAgency(config) {
|
|
1216
|
+
const timetables = mergeTimetablesWithSameId(getTimetables());
|
|
1241
1217
|
|
|
1242
1218
|
// If no timetables, build each route and direction into a timetable.
|
|
1243
1219
|
if (timetables.length === 0) {
|
|
1244
1220
|
return convertRoutesToTimetablePages(config);
|
|
1245
1221
|
}
|
|
1246
1222
|
|
|
1247
|
-
const timetablePages =
|
|
1223
|
+
const timetablePages = getTimetablePages(
|
|
1248
1224
|
{},
|
|
1249
1225
|
[],
|
|
1250
1226
|
[['timetable_page_id', 'ASC']]
|
|
@@ -1253,48 +1229,44 @@ export async function getTimetablePagesForAgency(config) {
|
|
|
1253
1229
|
// Check if there are any timetable pages defined in `timetable_pages.txt`.
|
|
1254
1230
|
if (timetablePages.length === 0) {
|
|
1255
1231
|
// If no timetablepages, use timetables
|
|
1256
|
-
return
|
|
1257
|
-
|
|
1258
|
-
convertTimetableToTimetablePage(timetable, config)
|
|
1259
|
-
)
|
|
1232
|
+
return timetables.map((timetable) =>
|
|
1233
|
+
convertTimetableToTimetablePage(timetable, config)
|
|
1260
1234
|
);
|
|
1261
1235
|
}
|
|
1262
1236
|
|
|
1263
|
-
const routes =
|
|
1237
|
+
const routes = getRoutes();
|
|
1264
1238
|
|
|
1265
1239
|
// Otherwise, use timetable pages defined in `timetable_pages.txt`.
|
|
1266
|
-
return
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
);
|
|
1240
|
+
return timetablePages.map((timetablePage) => {
|
|
1241
|
+
timetablePage.timetables = sortBy(
|
|
1242
|
+
timetables.filter(
|
|
1243
|
+
(timetable) =>
|
|
1244
|
+
timetable.timetable_page_id === timetablePage.timetable_page_id
|
|
1245
|
+
),
|
|
1246
|
+
'timetable_sequence'
|
|
1247
|
+
);
|
|
1275
1248
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1249
|
+
// Add routes for each timetable.
|
|
1250
|
+
for (const timetable of timetablePage.timetables) {
|
|
1251
|
+
timetable.routes = routes.filter((route) =>
|
|
1252
|
+
timetable.route_ids.includes(route.route_id)
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1282
1255
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
);
|
|
1256
|
+
return timetablePage;
|
|
1257
|
+
});
|
|
1286
1258
|
}
|
|
1287
1259
|
|
|
1288
1260
|
/*
|
|
1289
1261
|
* Get a timetable_page by id.
|
|
1290
1262
|
*/
|
|
1291
|
-
const getTimetablePageById =
|
|
1263
|
+
const getTimetablePageById = (timetablePageId, config) => {
|
|
1292
1264
|
// Check if there are any timetable pages defined in `timetable_pages.txt`.
|
|
1293
|
-
const timetablePages =
|
|
1265
|
+
const timetablePages = getTimetablePages({
|
|
1294
1266
|
timetable_page_id: timetablePageId,
|
|
1295
1267
|
});
|
|
1296
1268
|
|
|
1297
|
-
const timetables = mergeTimetablesWithSameId(
|
|
1269
|
+
const timetables = mergeTimetablesWithSameId(getTimetables());
|
|
1298
1270
|
|
|
1299
1271
|
if (timetablePages.length > 1) {
|
|
1300
1272
|
throw new Error(
|
|
@@ -1313,13 +1285,11 @@ const getTimetablePageById = async (timetablePageId, config) => {
|
|
|
1313
1285
|
);
|
|
1314
1286
|
|
|
1315
1287
|
// Add routes for each timetable
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
timetable.
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
})
|
|
1322
|
-
);
|
|
1288
|
+
for (const timetable of timetablePage.timetables) {
|
|
1289
|
+
timetable.routes = getRoutes({
|
|
1290
|
+
route_id: timetable.route_ids,
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1323
1293
|
|
|
1324
1294
|
return timetablePage;
|
|
1325
1295
|
}
|
|
@@ -1356,11 +1326,11 @@ const getTimetablePageById = async (timetablePageId, config) => {
|
|
|
1356
1326
|
|
|
1357
1327
|
const routeId = parts.join('|');
|
|
1358
1328
|
|
|
1359
|
-
const routes =
|
|
1329
|
+
const routes = getRoutes({
|
|
1360
1330
|
route_id: routeId,
|
|
1361
1331
|
});
|
|
1362
1332
|
|
|
1363
|
-
const trips =
|
|
1333
|
+
const trips = getTrips(
|
|
1364
1334
|
{
|
|
1365
1335
|
route_id: routeId,
|
|
1366
1336
|
direction_id: directionId,
|
|
@@ -1376,12 +1346,12 @@ const getTimetablePageById = async (timetablePageId, config) => {
|
|
|
1376
1346
|
}
|
|
1377
1347
|
|
|
1378
1348
|
if (/^[01]*$/.test(calendarCode)) {
|
|
1379
|
-
calendars =
|
|
1349
|
+
calendars = getCalendars({
|
|
1380
1350
|
...calendarCodeToCalendar(calendarCode),
|
|
1381
1351
|
});
|
|
1382
1352
|
} else {
|
|
1383
1353
|
serviceId = calendarCode;
|
|
1384
|
-
calendarDates =
|
|
1354
|
+
calendarDates = getCalendarDates({
|
|
1385
1355
|
exception_type: 1,
|
|
1386
1356
|
service_id: serviceId,
|
|
1387
1357
|
});
|
|
@@ -1462,10 +1432,10 @@ export function setDefaultConfig(initialConfig) {
|
|
|
1462
1432
|
/*
|
|
1463
1433
|
* Get a timetable page by id.
|
|
1464
1434
|
*/
|
|
1465
|
-
export
|
|
1466
|
-
const timetablePage =
|
|
1435
|
+
export function getFormattedTimetablePage(timetablePageId, config) {
|
|
1436
|
+
const timetablePage = getTimetablePageById(timetablePageId, config);
|
|
1467
1437
|
|
|
1468
|
-
timetablePage.consolidatedTimetables =
|
|
1438
|
+
timetablePage.consolidatedTimetables = formatTimetables(
|
|
1469
1439
|
timetablePage.timetables,
|
|
1470
1440
|
config
|
|
1471
1441
|
);
|
|
@@ -1481,7 +1451,7 @@ export async function getFormattedTimetablePage(timetablePageId, config) {
|
|
|
1481
1451
|
flatMap(timetablePage.consolidatedTimetables, 'route_ids')
|
|
1482
1452
|
);
|
|
1483
1453
|
|
|
1484
|
-
const timetableRoutes =
|
|
1454
|
+
const timetableRoutes = getRoutes(
|
|
1485
1455
|
{
|
|
1486
1456
|
route_id: timetablePage.route_ids,
|
|
1487
1457
|
},
|
|
@@ -1502,21 +1472,17 @@ export async function getFormattedTimetablePage(timetablePageId, config) {
|
|
|
1502
1472
|
}
|
|
1503
1473
|
|
|
1504
1474
|
// Get `direction_name` for each timetable.
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
timetable
|
|
1510
|
-
);
|
|
1511
|
-
}
|
|
1475
|
+
for (const timetable of timetablePage.consolidatedTimetables) {
|
|
1476
|
+
if (isNullOrEmpty(timetable.direction_name)) {
|
|
1477
|
+
timetable.direction_name = getDirectionHeadsignFromTimetable(timetable);
|
|
1478
|
+
}
|
|
1512
1479
|
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
);
|
|
1480
|
+
if (!timetable.routes) {
|
|
1481
|
+
timetable.routes = getRoutes({
|
|
1482
|
+
route_id: timetable.route_ids,
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1520
1486
|
|
|
1521
1487
|
return timetablePage;
|
|
1522
1488
|
}
|
|
@@ -1564,7 +1530,7 @@ export function generateTimetableHTML(timetablePage, config) {
|
|
|
1564
1530
|
/*
|
|
1565
1531
|
* Generate the CSV timetable for a timetable page.
|
|
1566
1532
|
*/
|
|
1567
|
-
export
|
|
1533
|
+
export function generateTimetableCSV(timetable) {
|
|
1568
1534
|
// Generate horizontal orientation, then transpose if vertical is needed.
|
|
1569
1535
|
const lines = [];
|
|
1570
1536
|
|
|
@@ -1606,15 +1572,15 @@ export async function generateTimetableCSV(timetable) {
|
|
|
1606
1572
|
/*
|
|
1607
1573
|
* Generate the HTML for the agency overview page.
|
|
1608
1574
|
*/
|
|
1609
|
-
export
|
|
1610
|
-
const agencies =
|
|
1575
|
+
export function generateOverviewHTML(timetablePages, config) {
|
|
1576
|
+
const agencies = getAgencies();
|
|
1611
1577
|
if (agencies.length === 0) {
|
|
1612
1578
|
throw new Error('No agencies found');
|
|
1613
1579
|
}
|
|
1614
1580
|
|
|
1615
1581
|
let geojson;
|
|
1616
1582
|
if (config.showMap) {
|
|
1617
|
-
geojson =
|
|
1583
|
+
geojson = getAgencyGeoJSON(config);
|
|
1618
1584
|
}
|
|
1619
1585
|
|
|
1620
1586
|
// Sort timetables for display, first numerically then alphabetically.
|