gtfs-to-html 2.5.4 → 2.5.6
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 +13 -0
- package/lib/utils.js +89 -75
- package/package.json +8 -8
- package/www/docs/related-libraries.md +3 -3
- package/www/yarn.lock +221 -234
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.5.6] - 2023-08-23
|
|
9
|
+
|
|
10
|
+
### Updated
|
|
11
|
+
|
|
12
|
+
- Dependency updates
|
|
13
|
+
- Use most common headsign for timetable name
|
|
14
|
+
|
|
15
|
+
## [2.5.5] - 2023-07-18
|
|
16
|
+
|
|
17
|
+
### Updated
|
|
18
|
+
|
|
19
|
+
- Dependency updates
|
|
20
|
+
|
|
8
21
|
## [2.5.4] - 2023-07-07
|
|
9
22
|
|
|
10
23
|
### Changed
|
package/lib/utils.js
CHANGED
|
@@ -3,16 +3,21 @@ import { readFileSync } from 'node:fs';
|
|
|
3
3
|
import {
|
|
4
4
|
cloneDeep,
|
|
5
5
|
compact,
|
|
6
|
+
countBy,
|
|
7
|
+
entries,
|
|
6
8
|
every,
|
|
7
9
|
find,
|
|
8
10
|
findLast,
|
|
9
11
|
first,
|
|
10
12
|
flatMap,
|
|
11
13
|
flattenDeep,
|
|
14
|
+
flow,
|
|
12
15
|
isEqual,
|
|
13
16
|
groupBy,
|
|
17
|
+
head,
|
|
14
18
|
last,
|
|
15
19
|
maxBy,
|
|
20
|
+
partialRight,
|
|
16
21
|
reduce,
|
|
17
22
|
size,
|
|
18
23
|
some,
|
|
@@ -75,7 +80,7 @@ import {
|
|
|
75
80
|
import { formatTripNameForCSV } from './template-functions.js';
|
|
76
81
|
|
|
77
82
|
const { version } = JSON.parse(
|
|
78
|
-
readFileSync(new URL('../package.json', import.meta.url))
|
|
83
|
+
readFileSync(new URL('../package.json', import.meta.url)),
|
|
79
84
|
);
|
|
80
85
|
|
|
81
86
|
/*
|
|
@@ -100,7 +105,7 @@ const getLongestTripStoptimes = (trips, config) => {
|
|
|
100
105
|
const filteredTripStoptimes =
|
|
101
106
|
config.showOnlyTimepoint === true
|
|
102
107
|
? trips.map((trip) =>
|
|
103
|
-
trip.stoptimes.filter((stoptime) => isTimepoint(stoptime))
|
|
108
|
+
trip.stoptimes.filter((stoptime) => isTimepoint(stoptime)),
|
|
104
109
|
)
|
|
105
110
|
: trips.map((trip) => trip.stoptimes);
|
|
106
111
|
|
|
@@ -134,8 +139,8 @@ const findCommonStopId = (trips, config) => {
|
|
|
134
139
|
trip.stoptimes.find(
|
|
135
140
|
(tripStoptime) =>
|
|
136
141
|
tripStoptime.stop_id === stoptime.stop_id &&
|
|
137
|
-
tripStoptime.arrival_time !== null
|
|
138
|
-
)
|
|
142
|
+
tripStoptime.arrival_time !== null,
|
|
143
|
+
),
|
|
139
144
|
);
|
|
140
145
|
});
|
|
141
146
|
|
|
@@ -178,7 +183,7 @@ const deduplicateTrips = (trips, commonStopId) => {
|
|
|
178
183
|
// Only add trip if no existing trip with the same set of timepoints has already been added.
|
|
179
184
|
const tripIsUnique = every(similarTrips, (similarTrip) => {
|
|
180
185
|
const similarTripStoptimes = similarTrip.stoptimes.map(
|
|
181
|
-
(stoptime) => stoptime.departure_time
|
|
186
|
+
(stoptime) => stoptime.departure_time,
|
|
182
187
|
);
|
|
183
188
|
return !isEqual(stoptimes, similarTripStoptimes);
|
|
184
189
|
});
|
|
@@ -227,7 +232,7 @@ const sortTrips = (trips, config) => {
|
|
|
227
232
|
sortedTrips = sortBy(
|
|
228
233
|
trips,
|
|
229
234
|
['firstStoptime', 'lastStoptime'],
|
|
230
|
-
['asc', 'asc']
|
|
235
|
+
['asc', 'asc'],
|
|
231
236
|
);
|
|
232
237
|
} else if (config.sortingAlgorithm === 'end') {
|
|
233
238
|
// Sort trips chronologically using last stoptime of each trip, which can be at different stops.
|
|
@@ -244,7 +249,7 @@ const sortTrips = (trips, config) => {
|
|
|
244
249
|
sortedTrips = sortBy(
|
|
245
250
|
trips,
|
|
246
251
|
['lastStoptime', 'firstStoptime'],
|
|
247
|
-
['asc', 'asc']
|
|
252
|
+
['asc', 'asc'],
|
|
248
253
|
);
|
|
249
254
|
} else if (config.sortingAlgorithm === 'first') {
|
|
250
255
|
// Sort trips chronologically using the stoptime of a the first stop of the longest trip.
|
|
@@ -281,7 +286,7 @@ const getCalendarDatesForTimetable = (timetable, config) => {
|
|
|
281
286
|
service_id: timetable.service_ids,
|
|
282
287
|
},
|
|
283
288
|
[],
|
|
284
|
-
[['date', 'ASC']]
|
|
289
|
+
[['date', 'ASC']],
|
|
285
290
|
);
|
|
286
291
|
const start = fromGTFSDate(timetable.start_date);
|
|
287
292
|
const end = fromGTFSDate(timetable.end_date);
|
|
@@ -294,11 +299,11 @@ const getCalendarDatesForTimetable = (timetable, config) => {
|
|
|
294
299
|
if (moment(calendarDate.date, 'YYYYMMDD').isBetween(start, end)) {
|
|
295
300
|
if (calendarDate.exception_type === 1) {
|
|
296
301
|
filteredCalendarDates.includedDates.push(
|
|
297
|
-
formatDate(calendarDate, config.dateFormat)
|
|
302
|
+
formatDate(calendarDate, config.dateFormat),
|
|
298
303
|
);
|
|
299
304
|
} else if (calendarDate.exception_type === 2) {
|
|
300
305
|
filteredCalendarDates.excludedDates.push(
|
|
301
|
-
formatDate(calendarDate, config.dateFormat)
|
|
306
|
+
formatDate(calendarDate, config.dateFormat),
|
|
302
307
|
);
|
|
303
308
|
}
|
|
304
309
|
}
|
|
@@ -340,14 +345,21 @@ const getDirectionHeadsignFromTimetable = (timetable) => {
|
|
|
340
345
|
direction_id: timetable.direction_id,
|
|
341
346
|
route_id: timetable.route_ids,
|
|
342
347
|
},
|
|
343
|
-
['trip_headsign']
|
|
348
|
+
['trip_headsign'],
|
|
344
349
|
);
|
|
345
350
|
|
|
346
351
|
if (trips.length === 0) {
|
|
347
352
|
return '';
|
|
348
353
|
}
|
|
349
354
|
|
|
350
|
-
|
|
355
|
+
const mostCommonHeadsign = flow(
|
|
356
|
+
countBy,
|
|
357
|
+
entries,
|
|
358
|
+
partialRight(maxBy, last),
|
|
359
|
+
head,
|
|
360
|
+
)(compact(trips.map((trip) => trip.trip_headsign)));
|
|
361
|
+
|
|
362
|
+
return mostCommonHeadsign;
|
|
351
363
|
};
|
|
352
364
|
|
|
353
365
|
/*
|
|
@@ -394,13 +406,13 @@ const getTimetableNotesForTimetable = (timetable, config) => {
|
|
|
394
406
|
// Note references with stop_sequence must also have stop_id.
|
|
395
407
|
if (noteReference.stop_id === '' || noteReference.stop_id === null) {
|
|
396
408
|
config.logWarning(
|
|
397
|
-
`Timetable Note Reference for note_id=${noteReference.note_id} has a \`stop_sequence\` but no \`stop_id\` - ignoring
|
|
409
|
+
`Timetable Note Reference for note_id=${noteReference.note_id} has a \`stop_sequence\` but no \`stop_id\` - ignoring`,
|
|
398
410
|
);
|
|
399
411
|
continue;
|
|
400
412
|
}
|
|
401
413
|
|
|
402
414
|
const stop = timetable.stops.find(
|
|
403
|
-
(stop) => stop.stop_id === noteReference.stop_id
|
|
415
|
+
(stop) => stop.stop_id === noteReference.stop_id,
|
|
404
416
|
);
|
|
405
417
|
|
|
406
418
|
if (!stop) {
|
|
@@ -408,7 +420,7 @@ const getTimetableNotesForTimetable = (timetable, config) => {
|
|
|
408
420
|
}
|
|
409
421
|
|
|
410
422
|
const tripWithMatchingStopSequence = stop.trips.find(
|
|
411
|
-
(trip) => trip.stop_sequence === noteReference.stop_sequence
|
|
423
|
+
(trip) => trip.stop_sequence === noteReference.stop_sequence,
|
|
412
424
|
);
|
|
413
425
|
|
|
414
426
|
if (tripWithMatchingStopSequence) {
|
|
@@ -472,7 +484,7 @@ const convertRouteToTimetablePage = (
|
|
|
472
484
|
direction,
|
|
473
485
|
calendars,
|
|
474
486
|
calendarDates,
|
|
475
|
-
config
|
|
487
|
+
config,
|
|
476
488
|
) => {
|
|
477
489
|
const timetable = {
|
|
478
490
|
route_ids: [route.route_id],
|
|
@@ -501,10 +513,12 @@ const convertRouteToTimetablePage = (
|
|
|
501
513
|
Object.assign(timetable, getDaysFromCalendars(calendars));
|
|
502
514
|
|
|
503
515
|
timetable.start_date = toGTFSDate(
|
|
504
|
-
moment.min(
|
|
516
|
+
moment.min(
|
|
517
|
+
calendars.map((calendar) => fromGTFSDate(calendar.start_date)),
|
|
518
|
+
),
|
|
505
519
|
);
|
|
506
520
|
timetable.end_date = toGTFSDate(
|
|
507
|
-
moment.max(calendars.map((calendar) => fromGTFSDate(calendar.end_date)))
|
|
521
|
+
moment.max(calendars.map((calendar) => fromGTFSDate(calendar.end_date))),
|
|
508
522
|
);
|
|
509
523
|
}
|
|
510
524
|
|
|
@@ -528,7 +542,7 @@ const convertRoutesToTimetablePages = (config) => {
|
|
|
528
542
|
.prepare(
|
|
529
543
|
`SELECT * FROM calendar_dates WHERE exception_type = 1 AND service_id NOT IN (${serviceIds
|
|
530
544
|
.map((serviceId) => `'${serviceId}'`)
|
|
531
|
-
.join(', ')})
|
|
545
|
+
.join(', ')})`,
|
|
532
546
|
)
|
|
533
547
|
.all();
|
|
534
548
|
|
|
@@ -537,7 +551,7 @@ const convertRoutesToTimetablePages = (config) => {
|
|
|
537
551
|
{
|
|
538
552
|
route_id: route.route_id,
|
|
539
553
|
},
|
|
540
|
-
['trip_headsign', 'direction_id']
|
|
554
|
+
['trip_headsign', 'direction_id'],
|
|
541
555
|
);
|
|
542
556
|
const directions = uniqBy(trips, (trip) => trip.direction_id);
|
|
543
557
|
const dayGroups = groupBy(calendars, calendarToCalendarCode);
|
|
@@ -545,7 +559,7 @@ const convertRoutesToTimetablePages = (config) => {
|
|
|
545
559
|
|
|
546
560
|
return directions.map((direction) => [
|
|
547
561
|
Object.values(dayGroups).map((calendars) =>
|
|
548
|
-
convertRouteToTimetablePage(route, direction, calendars, null, config)
|
|
562
|
+
convertRouteToTimetablePage(route, direction, calendars, null, config),
|
|
549
563
|
),
|
|
550
564
|
Object.values(calendarDateGroups).map((calendarDates) =>
|
|
551
565
|
convertRouteToTimetablePage(
|
|
@@ -553,8 +567,8 @@ const convertRoutesToTimetablePages = (config) => {
|
|
|
553
567
|
direction,
|
|
554
568
|
null,
|
|
555
569
|
calendarDates,
|
|
556
|
-
config
|
|
557
|
-
)
|
|
570
|
+
config,
|
|
571
|
+
),
|
|
558
572
|
),
|
|
559
573
|
]);
|
|
560
574
|
});
|
|
@@ -567,7 +581,7 @@ const convertRoutesToTimetablePages = (config) => {
|
|
|
567
581
|
*/
|
|
568
582
|
const generateTripsByFrequencies = (trip, frequencies, config) => {
|
|
569
583
|
const formattedFrequencies = frequencies.map((frequency) =>
|
|
570
|
-
formatFrequency(frequency, config)
|
|
584
|
+
formatFrequency(frequency, config),
|
|
571
585
|
);
|
|
572
586
|
const resetTrip = resetStoptimesToMidnight(trip);
|
|
573
587
|
const trips = [];
|
|
@@ -600,7 +614,7 @@ const generateTripsByFrequencies = (trip, frequencies, config) => {
|
|
|
600
614
|
const duplicateStopsForDifferentArrivalDeparture = (
|
|
601
615
|
stopIds,
|
|
602
616
|
timetable,
|
|
603
|
-
config
|
|
617
|
+
config,
|
|
604
618
|
) => {
|
|
605
619
|
if (config.showArrivalOnDifference === null) {
|
|
606
620
|
return stopIds;
|
|
@@ -610,7 +624,7 @@ const duplicateStopsForDifferentArrivalDeparture = (
|
|
|
610
624
|
for (const stoptime of trip.stoptimes) {
|
|
611
625
|
const timepointDifference = fromGTFSTime(stoptime.departure_time).diff(
|
|
612
626
|
fromGTFSTime(stoptime.arrival_time),
|
|
613
|
-
'minutes'
|
|
627
|
+
'minutes',
|
|
614
628
|
);
|
|
615
629
|
|
|
616
630
|
if (timepointDifference < config.showArrivalOnDifference) {
|
|
@@ -646,12 +660,12 @@ const getStopOrder = (timetable, config) => {
|
|
|
646
660
|
timetable_id: timetable.timetable_id,
|
|
647
661
|
},
|
|
648
662
|
['stop_id'],
|
|
649
|
-
[['stop_sequence', 'ASC']]
|
|
663
|
+
[['stop_sequence', 'ASC']],
|
|
650
664
|
);
|
|
651
665
|
|
|
652
666
|
if (timetableStopOrders.length > 0) {
|
|
653
667
|
return timetableStopOrders.map(
|
|
654
|
-
(timetableStopOrder) => timetableStopOrder.stop_id
|
|
668
|
+
(timetableStopOrder) => timetableStopOrder.stop_id,
|
|
655
669
|
);
|
|
656
670
|
}
|
|
657
671
|
|
|
@@ -682,7 +696,7 @@ const getStopOrder = (timetable, config) => {
|
|
|
682
696
|
return duplicateStopsForDifferentArrivalDeparture(
|
|
683
697
|
stopIds,
|
|
684
698
|
timetable,
|
|
685
|
-
config
|
|
699
|
+
config,
|
|
686
700
|
);
|
|
687
701
|
} catch {
|
|
688
702
|
// Ignore errors and move to next strategy.
|
|
@@ -691,7 +705,7 @@ const getStopOrder = (timetable, config) => {
|
|
|
691
705
|
// Finally, fall back to using the stop order from the trip with the most stoptimes.
|
|
692
706
|
const longestTripStoptimes = getLongestTripStoptimes(
|
|
693
707
|
timetable.orderedTrips,
|
|
694
|
-
config
|
|
708
|
+
config,
|
|
695
709
|
);
|
|
696
710
|
const stopIds = longestTripStoptimes.map((stoptime) => stoptime.stop_id);
|
|
697
711
|
|
|
@@ -714,7 +728,7 @@ const getStopsForTimetable = (timetable, config) => {
|
|
|
714
728
|
|
|
715
729
|
if (stops.length === 0) {
|
|
716
730
|
throw new Error(
|
|
717
|
-
`No stop found found for stop_id=${stopId} in timetable_id=${timetable.timetable_id}
|
|
731
|
+
`No stop found found for stop_id=${stopId} in timetable_id=${timetable.timetable_id}`,
|
|
718
732
|
);
|
|
719
733
|
}
|
|
720
734
|
|
|
@@ -743,7 +757,7 @@ const getStopsForTimetable = (timetable, config) => {
|
|
|
743
757
|
|
|
744
758
|
for (const stopAttribute of stopAttributes) {
|
|
745
759
|
const stop = orderedStops.find(
|
|
746
|
-
(stop) => stop.stop_id === stopAttribute.stop_id
|
|
760
|
+
(stop) => stop.stop_id === stopAttribute.stop_id,
|
|
747
761
|
);
|
|
748
762
|
|
|
749
763
|
if (stop) {
|
|
@@ -782,7 +796,7 @@ const getCalendarsFromTimetable = (timetable) => {
|
|
|
782
796
|
|
|
783
797
|
return memo;
|
|
784
798
|
},
|
|
785
|
-
[]
|
|
799
|
+
[],
|
|
786
800
|
);
|
|
787
801
|
|
|
788
802
|
if (dayQueries.length > 0) {
|
|
@@ -814,8 +828,8 @@ const getCalendarDatesServiceIds = (startDate, endDate) => {
|
|
|
814
828
|
const calendarDates = db
|
|
815
829
|
.prepare(
|
|
816
830
|
`SELECT DISTINCT service_id FROM calendar_dates WHERE ${whereClauses.join(
|
|
817
|
-
' AND '
|
|
818
|
-
)}
|
|
831
|
+
' AND ',
|
|
832
|
+
)}`,
|
|
819
833
|
)
|
|
820
834
|
.all();
|
|
821
835
|
return calendarDates.map((calendarDate) => calendarDate.service_id);
|
|
@@ -841,7 +855,7 @@ const getAllStationStopIds = (stopId) => {
|
|
|
841
855
|
{
|
|
842
856
|
parent_station: stop.parent_station,
|
|
843
857
|
},
|
|
844
|
-
['stop_id']
|
|
858
|
+
['stop_id'],
|
|
845
859
|
);
|
|
846
860
|
|
|
847
861
|
return [
|
|
@@ -859,7 +873,7 @@ const getTripsWithSameBlock = (trip, timetable) => {
|
|
|
859
873
|
block_id: trip.block_id,
|
|
860
874
|
service_id: timetable.service_ids,
|
|
861
875
|
},
|
|
862
|
-
['trip_id', 'route_id']
|
|
876
|
+
['trip_id', 'route_id'],
|
|
863
877
|
);
|
|
864
878
|
|
|
865
879
|
for (const blockTrip of trips) {
|
|
@@ -868,12 +882,12 @@ const getTripsWithSameBlock = (trip, timetable) => {
|
|
|
868
882
|
trip_id: blockTrip.trip_id,
|
|
869
883
|
},
|
|
870
884
|
[],
|
|
871
|
-
[['stop_sequence', 'ASC']]
|
|
885
|
+
[['stop_sequence', 'ASC']],
|
|
872
886
|
);
|
|
873
887
|
|
|
874
888
|
if (stopTimes.length === 0) {
|
|
875
889
|
throw new Error(
|
|
876
|
-
`No stoptimes found found for trip_id=${blockTrip.trip_id}
|
|
890
|
+
`No stoptimes found found for trip_id=${blockTrip.trip_id}`,
|
|
877
891
|
);
|
|
878
892
|
}
|
|
879
893
|
|
|
@@ -906,7 +920,7 @@ const addTripContinuation = (trip, timetable) => {
|
|
|
906
920
|
blockTrips,
|
|
907
921
|
(blockTrip) =>
|
|
908
922
|
blockTrip.lastStoptime.arrival_timestamp <=
|
|
909
|
-
firstStoptime.departure_timestamp
|
|
923
|
+
firstStoptime.departure_timestamp,
|
|
910
924
|
);
|
|
911
925
|
|
|
912
926
|
/*
|
|
@@ -936,7 +950,7 @@ const addTripContinuation = (trip, timetable) => {
|
|
|
936
950
|
blockTrips,
|
|
937
951
|
(blockTrip) =>
|
|
938
952
|
blockTrip.firstStoptime.departure_timestamp >=
|
|
939
|
-
lastStoptime.arrival_timestamp
|
|
953
|
+
lastStoptime.arrival_timestamp,
|
|
940
954
|
);
|
|
941
955
|
|
|
942
956
|
// "Continues As" trips must be a different route_id.
|
|
@@ -993,7 +1007,7 @@ const filterTrips = (timetable) => {
|
|
|
993
1007
|
const timetableStopIds = new Set(timetable.stops.map((stop) => stop.stop_id));
|
|
994
1008
|
for (const trip of filteredTrips) {
|
|
995
1009
|
trip.stoptimes = trip.stoptimes.filter((stoptime) =>
|
|
996
|
-
timetableStopIds.has(stoptime.stop_id)
|
|
1010
|
+
timetableStopIds.has(stoptime.stop_id),
|
|
997
1011
|
);
|
|
998
1012
|
}
|
|
999
1013
|
|
|
@@ -1023,10 +1037,10 @@ const getTripsForTimetable = (timetable, calendars, config) => {
|
|
|
1023
1037
|
if (trips.length === 0) {
|
|
1024
1038
|
timetable.warnings.push(
|
|
1025
1039
|
`No trips found for route_id=${timetable.route_ids.join(
|
|
1026
|
-
'_'
|
|
1040
|
+
'_',
|
|
1027
1041
|
)}, direction_id=${timetable.direction_id}, service_ids=${JSON.stringify(
|
|
1028
|
-
timetable.service_ids
|
|
1029
|
-
)}, timetable_id=${timetable.timetable_id}
|
|
1042
|
+
timetable.service_ids,
|
|
1043
|
+
)}, timetable_id=${timetable.timetable_id}`,
|
|
1030
1044
|
);
|
|
1031
1045
|
}
|
|
1032
1046
|
|
|
@@ -1046,7 +1060,7 @@ const getTripsForTimetable = (timetable, calendars, config) => {
|
|
|
1046
1060
|
trip_id: formattedTrip.trip_id,
|
|
1047
1061
|
},
|
|
1048
1062
|
[],
|
|
1049
|
-
[['stop_sequence', 'ASC']]
|
|
1063
|
+
[['stop_sequence', 'ASC']],
|
|
1050
1064
|
);
|
|
1051
1065
|
|
|
1052
1066
|
if (formattedTrip.stoptimes.length === 0) {
|
|
@@ -1055,7 +1069,7 @@ const getTripsForTimetable = (timetable, calendars, config) => {
|
|
|
1055
1069
|
formattedTrip.trip_id
|
|
1056
1070
|
}, route_id=${timetable.route_ids.join('_')}, timetable_id=${
|
|
1057
1071
|
timetable.timetable_id
|
|
1058
|
-
}
|
|
1072
|
+
}`,
|
|
1059
1073
|
);
|
|
1060
1074
|
}
|
|
1061
1075
|
|
|
@@ -1092,7 +1106,7 @@ const getTripsForTimetable = (timetable, calendars, config) => {
|
|
|
1092
1106
|
}
|
|
1093
1107
|
|
|
1094
1108
|
const tripFrequencies = frequencies.filter(
|
|
1095
|
-
(frequency) => frequency.trip_id === trip.trip_id
|
|
1109
|
+
(frequency) => frequency.trip_id === trip.trip_id,
|
|
1096
1110
|
);
|
|
1097
1111
|
|
|
1098
1112
|
if (tripFrequencies.length === 0) {
|
|
@@ -1101,7 +1115,7 @@ const getTripsForTimetable = (timetable, calendars, config) => {
|
|
|
1101
1115
|
const frequencyTrips = generateTripsByFrequencies(
|
|
1102
1116
|
formattedTrip,
|
|
1103
1117
|
frequencies,
|
|
1104
|
-
config
|
|
1118
|
+
config,
|
|
1105
1119
|
);
|
|
1106
1120
|
formattedTrips.push(...frequencyTrips);
|
|
1107
1121
|
timetable.frequencies = frequencies;
|
|
@@ -1124,13 +1138,13 @@ const getTripsForTimetable = (timetable, calendars, config) => {
|
|
|
1124
1138
|
{
|
|
1125
1139
|
stop_id: uniq(stopIds),
|
|
1126
1140
|
},
|
|
1127
|
-
['parent_station', 'stop_id']
|
|
1141
|
+
['parent_station', 'stop_id'],
|
|
1128
1142
|
);
|
|
1129
1143
|
|
|
1130
1144
|
for (const trip of formattedTrips) {
|
|
1131
1145
|
for (const stoptime of trip.stoptimes) {
|
|
1132
1146
|
const parentStationStop = stops.find(
|
|
1133
|
-
(stop) => stop.stop_id === stoptime.stop_id
|
|
1147
|
+
(stop) => stop.stop_id === stoptime.stop_id,
|
|
1134
1148
|
);
|
|
1135
1149
|
stoptime.stop_id =
|
|
1136
1150
|
parentStationStop.parent_station || parentStationStop.stop_id;
|
|
@@ -1155,7 +1169,7 @@ const formatTimetables = (timetables, config) => {
|
|
|
1155
1169
|
if (timetable.include_exceptions === 1) {
|
|
1156
1170
|
const calendarDatesServiceIds = getCalendarDatesServiceIds(
|
|
1157
1171
|
timetable.start_date,
|
|
1158
|
-
timetable.end_date
|
|
1172
|
+
timetable.end_date,
|
|
1159
1173
|
);
|
|
1160
1174
|
serviceIds = uniq([...serviceIds, ...calendarDatesServiceIds]);
|
|
1161
1175
|
}
|
|
@@ -1205,7 +1219,7 @@ const formatTimetables = (timetables, config) => {
|
|
|
1205
1219
|
}
|
|
1206
1220
|
|
|
1207
1221
|
return formattedTimetables.filter(
|
|
1208
|
-
(timetable) => timetable.orderedTrips.length > 0
|
|
1222
|
+
(timetable) => timetable.orderedTrips.length > 0,
|
|
1209
1223
|
);
|
|
1210
1224
|
};
|
|
1211
1225
|
|
|
@@ -1223,14 +1237,14 @@ export function getTimetablePagesForAgency(config) {
|
|
|
1223
1237
|
const timetablePages = getTimetablePages(
|
|
1224
1238
|
{},
|
|
1225
1239
|
[],
|
|
1226
|
-
[['timetable_page_id', 'ASC']]
|
|
1240
|
+
[['timetable_page_id', 'ASC']],
|
|
1227
1241
|
);
|
|
1228
1242
|
|
|
1229
1243
|
// Check if there are any timetable pages defined in `timetable_pages.txt`.
|
|
1230
1244
|
if (timetablePages.length === 0) {
|
|
1231
1245
|
// If no timetablepages, use timetables
|
|
1232
1246
|
return timetables.map((timetable) =>
|
|
1233
|
-
convertTimetableToTimetablePage(timetable, config)
|
|
1247
|
+
convertTimetableToTimetablePage(timetable, config),
|
|
1234
1248
|
);
|
|
1235
1249
|
}
|
|
1236
1250
|
|
|
@@ -1241,15 +1255,15 @@ export function getTimetablePagesForAgency(config) {
|
|
|
1241
1255
|
timetablePage.timetables = sortBy(
|
|
1242
1256
|
timetables.filter(
|
|
1243
1257
|
(timetable) =>
|
|
1244
|
-
timetable.timetable_page_id === timetablePage.timetable_page_id
|
|
1258
|
+
timetable.timetable_page_id === timetablePage.timetable_page_id,
|
|
1245
1259
|
),
|
|
1246
|
-
'timetable_sequence'
|
|
1260
|
+
'timetable_sequence',
|
|
1247
1261
|
);
|
|
1248
1262
|
|
|
1249
1263
|
// Add routes for each timetable.
|
|
1250
1264
|
for (const timetable of timetablePage.timetables) {
|
|
1251
1265
|
timetable.routes = routes.filter((route) =>
|
|
1252
|
-
timetable.route_ids.includes(route.route_id)
|
|
1266
|
+
timetable.route_ids.includes(route.route_id),
|
|
1253
1267
|
);
|
|
1254
1268
|
}
|
|
1255
1269
|
|
|
@@ -1270,7 +1284,7 @@ const getTimetablePageById = (timetablePageId, config) => {
|
|
|
1270
1284
|
|
|
1271
1285
|
if (timetablePages.length > 1) {
|
|
1272
1286
|
throw new Error(
|
|
1273
|
-
`Multiple timetable_pages found for timetable_page_id=${timetablePageId}
|
|
1287
|
+
`Multiple timetable_pages found for timetable_page_id=${timetablePageId}`,
|
|
1274
1288
|
);
|
|
1275
1289
|
}
|
|
1276
1290
|
|
|
@@ -1279,9 +1293,9 @@ const getTimetablePageById = (timetablePageId, config) => {
|
|
|
1279
1293
|
const timetablePage = timetablePages[0];
|
|
1280
1294
|
timetablePage.timetables = sortBy(
|
|
1281
1295
|
timetables.filter(
|
|
1282
|
-
(timetable) => timetable.timetable_page_id === timetablePageId
|
|
1296
|
+
(timetable) => timetable.timetable_page_id === timetablePageId,
|
|
1283
1297
|
),
|
|
1284
|
-
'timetable_sequence'
|
|
1298
|
+
'timetable_sequence',
|
|
1285
1299
|
);
|
|
1286
1300
|
|
|
1287
1301
|
// Add routes for each timetable
|
|
@@ -1297,12 +1311,12 @@ const getTimetablePageById = (timetablePageId, config) => {
|
|
|
1297
1311
|
if (timetables.length > 0) {
|
|
1298
1312
|
// If no timetable_page, use timetable defined in `timetables.txt`.
|
|
1299
1313
|
const timetablePageTimetables = timetables.filter(
|
|
1300
|
-
(timetable) => timetable.timetable_id === timetablePageId
|
|
1314
|
+
(timetable) => timetable.timetable_id === timetablePageId,
|
|
1301
1315
|
);
|
|
1302
1316
|
|
|
1303
1317
|
if (timetablePageTimetables.length === 0) {
|
|
1304
1318
|
throw new Error(
|
|
1305
|
-
`No timetable found for timetable_page_id=${timetablePageId}
|
|
1319
|
+
`No timetable found for timetable_page_id=${timetablePageId}`,
|
|
1306
1320
|
);
|
|
1307
1321
|
}
|
|
1308
1322
|
|
|
@@ -1335,13 +1349,13 @@ const getTimetablePageById = (timetablePageId, config) => {
|
|
|
1335
1349
|
route_id: routeId,
|
|
1336
1350
|
direction_id: directionId,
|
|
1337
1351
|
},
|
|
1338
|
-
['trip_headsign', 'direction_id']
|
|
1352
|
+
['trip_headsign', 'direction_id'],
|
|
1339
1353
|
);
|
|
1340
1354
|
const directions = uniqBy(trips, (trip) => trip.direction_id);
|
|
1341
1355
|
|
|
1342
1356
|
if (directions.length === 0) {
|
|
1343
1357
|
throw new Error(
|
|
1344
|
-
`No trips found for timetable_page_id=${timetablePageId} route_id=${routeId} direction_id=${directionId}
|
|
1358
|
+
`No trips found for timetable_page_id=${timetablePageId} route_id=${routeId} direction_id=${directionId}`,
|
|
1345
1359
|
);
|
|
1346
1360
|
}
|
|
1347
1361
|
|
|
@@ -1362,7 +1376,7 @@ const getTimetablePageById = (timetablePageId, config) => {
|
|
|
1362
1376
|
directions[0],
|
|
1363
1377
|
calendars,
|
|
1364
1378
|
calendarDates,
|
|
1365
|
-
config
|
|
1379
|
+
config,
|
|
1366
1380
|
);
|
|
1367
1381
|
};
|
|
1368
1382
|
|
|
@@ -1437,33 +1451,33 @@ export function getFormattedTimetablePage(timetablePageId, config) {
|
|
|
1437
1451
|
|
|
1438
1452
|
timetablePage.consolidatedTimetables = formatTimetables(
|
|
1439
1453
|
timetablePage.timetables,
|
|
1440
|
-
config
|
|
1454
|
+
config,
|
|
1441
1455
|
);
|
|
1442
1456
|
timetablePage.timetable_page_label = formatTimetablePageLabel(timetablePage);
|
|
1443
1457
|
timetablePage.dayList = formatDays(
|
|
1444
1458
|
getDaysFromCalendars(timetablePage.consolidatedTimetables),
|
|
1445
|
-
config
|
|
1459
|
+
config,
|
|
1446
1460
|
);
|
|
1447
1461
|
timetablePage.dayLists = uniq(
|
|
1448
|
-
timetablePage.consolidatedTimetables.map((timetable) => timetable.dayList)
|
|
1462
|
+
timetablePage.consolidatedTimetables.map((timetable) => timetable.dayList),
|
|
1449
1463
|
);
|
|
1450
1464
|
timetablePage.route_ids = uniq(
|
|
1451
|
-
flatMap(timetablePage.consolidatedTimetables, 'route_ids')
|
|
1465
|
+
flatMap(timetablePage.consolidatedTimetables, 'route_ids'),
|
|
1452
1466
|
);
|
|
1453
1467
|
|
|
1454
1468
|
const timetableRoutes = getRoutes(
|
|
1455
1469
|
{
|
|
1456
1470
|
route_id: timetablePage.route_ids,
|
|
1457
1471
|
},
|
|
1458
|
-
['route_color', 'route_text_color', 'agency_id']
|
|
1472
|
+
['route_color', 'route_text_color', 'agency_id'],
|
|
1459
1473
|
);
|
|
1460
1474
|
|
|
1461
1475
|
timetablePage.routeColors = timetableRoutes.map((route) => route.route_color);
|
|
1462
1476
|
timetablePage.routeTextColors = timetableRoutes.map(
|
|
1463
|
-
(route) => route.route_text_color
|
|
1477
|
+
(route) => route.route_text_color,
|
|
1464
1478
|
);
|
|
1465
1479
|
timetablePage.agency_ids = compact(
|
|
1466
|
-
timetableRoutes.map((route) => route.agency_id)
|
|
1480
|
+
timetableRoutes.map((route) => route.agency_id),
|
|
1467
1481
|
);
|
|
1468
1482
|
|
|
1469
1483
|
// Set default filename.
|
|
@@ -1537,7 +1551,7 @@ export function generateTimetableCSV(timetable) {
|
|
|
1537
1551
|
lines.push([
|
|
1538
1552
|
'',
|
|
1539
1553
|
...timetable.orderedTrips.map((trip) =>
|
|
1540
|
-
formatTripNameForCSV(trip, timetable)
|
|
1554
|
+
formatTripNameForCSV(trip, timetable),
|
|
1541
1555
|
),
|
|
1542
1556
|
]);
|
|
1543
1557
|
|
|
@@ -1592,7 +1606,7 @@ export function generateOverviewHTML(timetablePages, config) {
|
|
|
1592
1606
|
) {
|
|
1593
1607
|
return Number.parseInt(
|
|
1594
1608
|
timetablePage.consolidatedTimetables[0].routes[0].route_short_name,
|
|
1595
|
-
10
|
|
1609
|
+
10,
|
|
1596
1610
|
);
|
|
1597
1611
|
}
|
|
1598
1612
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gtfs-to-html",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
|
|
6
6
|
"keywords": [
|
|
@@ -37,19 +37,19 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@turf/helpers": "^6.5.0",
|
|
39
39
|
"@turf/simplify": "^6.5.0",
|
|
40
|
-
"archiver": "^
|
|
40
|
+
"archiver": "^6.0.0",
|
|
41
41
|
"cli-table": "^0.3.11",
|
|
42
42
|
"copy-dir": "^1.3.0",
|
|
43
43
|
"csv-stringify": "^6.4.0",
|
|
44
44
|
"express": "^4.18.2",
|
|
45
|
-
"gtfs": "^4.
|
|
46
|
-
"js-beautify": "^1.14.
|
|
45
|
+
"gtfs": "^4.5.0",
|
|
46
|
+
"js-beautify": "^1.14.9",
|
|
47
47
|
"lodash-es": "^4.17.21",
|
|
48
48
|
"moment": "^2.29.4",
|
|
49
49
|
"morgan": "^1.10.0",
|
|
50
50
|
"pretty-error": "^4.0.0",
|
|
51
51
|
"pug": "^3.0.2",
|
|
52
|
-
"puppeteer": "^
|
|
52
|
+
"puppeteer": "^21.1.0",
|
|
53
53
|
"sanitize-filename": "^1.6.3",
|
|
54
54
|
"sqlstring": "^2.3.3",
|
|
55
55
|
"timer-machine": "^1.1.0",
|
|
@@ -60,11 +60,11 @@
|
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"husky": "^8.0.3",
|
|
63
|
-
"lint-staged": "^
|
|
64
|
-
"prettier": "^3.0.
|
|
63
|
+
"lint-staged": "^14.0.1",
|
|
64
|
+
"prettier": "^3.0.2"
|
|
65
65
|
},
|
|
66
66
|
"engines": {
|
|
67
|
-
"node": ">=
|
|
67
|
+
"node": ">= 18.0.0"
|
|
68
68
|
},
|
|
69
69
|
"release-it": {
|
|
70
70
|
"github": {
|
|
@@ -21,11 +21,11 @@ title: Related Libraries
|
|
|
21
21
|
|
|
22
22
|
[`https://github.com/blinktaginc/gtfs-to-chart`](https://github.com/blinktaginc/gtfs-to-chart)
|
|
23
23
|
|
|
24
|
-
## Transit
|
|
24
|
+
## Transit Departures Widget
|
|
25
25
|
|
|
26
|
-
The [Transit
|
|
26
|
+
The [Transit Departures Widget](https://github.com/BlinkTagInc/transit-departures-widget) generates a user-friendly transit realtime departures widget in HTML format directly from GTFS and GTFS-RT transit data. Most transit agencies have schedule data in GTFS format and many publish realtime departure information using GTFS-RT. This project generates HTML, JS and CSS for use on a transit agency website to allow users to see when the next vehicle is departing from a specific stop and includes features like caching, auto-refresh, url parameters and custom templates.
|
|
27
27
|
|
|
28
|
-
[`https://github.com/BlinkTagInc/transit-
|
|
28
|
+
[`https://github.com/BlinkTagInc/transit-departures-widget`](https://github.com/BlinkTagInc/transit-departures-widget)
|
|
29
29
|
|
|
30
30
|
## GTFS Text-to-Speech Tester
|
|
31
31
|
|