gtfs-to-html 2.5.5 → 2.5.7
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 +18 -0
- package/lib/formatters.js +28 -15
- package/lib/utils.js +107 -89
- package/package.json +8 -8
- package/views/default/timetable_horizontal.pug +1 -1
- package/views/default/timetable_vertical.pug +1 -1
- package/www/blog/2021-11-06-CSV-Export.md +1 -1
- package/www/docs/configuration.md +136 -136
- package/www/docs/logging-sql-queries.md +2 -2
- package/www/docs/quick-start.md +41 -38
- package/www/docs/related-libraries.md +3 -3
- package/www/package.json +5 -14
- package/www/yarn.lock +3012 -2131
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,13 +80,13 @@ 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
|
/*
|
|
82
87
|
* Determine if a stoptime is a timepoint.
|
|
83
88
|
*/
|
|
84
|
-
const isTimepoint = (stoptime) => {
|
|
89
|
+
export const isTimepoint = (stoptime) => {
|
|
85
90
|
if (isNullOrEmpty(stoptime.timepoint)) {
|
|
86
91
|
return (
|
|
87
92
|
!isNullOrEmpty(stoptime.arrival_time) &&
|
|
@@ -96,13 +101,15 @@ const isTimepoint = (stoptime) => {
|
|
|
96
101
|
* Find the longest trip (most stops) in a group of trips and return stoptimes.
|
|
97
102
|
*/
|
|
98
103
|
const getLongestTripStoptimes = (trips, config) => {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
const filteredTripStoptimes = trips.map((trip) =>
|
|
105
|
+
trip.stoptimes.filter((stoptime) => {
|
|
106
|
+
// If `showOnlyTimepoint` is true, then filter out all non-timepoints.
|
|
107
|
+
if (config.showOnlyTimepoint === true) {
|
|
108
|
+
return isTimepoint(stoptime);
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
106
113
|
|
|
107
114
|
return maxBy(filteredTripStoptimes, (stoptimes) => size(stoptimes));
|
|
108
115
|
};
|
|
@@ -134,8 +141,8 @@ const findCommonStopId = (trips, config) => {
|
|
|
134
141
|
trip.stoptimes.find(
|
|
135
142
|
(tripStoptime) =>
|
|
136
143
|
tripStoptime.stop_id === stoptime.stop_id &&
|
|
137
|
-
tripStoptime.arrival_time !== null
|
|
138
|
-
)
|
|
144
|
+
tripStoptime.arrival_time !== null,
|
|
145
|
+
),
|
|
139
146
|
);
|
|
140
147
|
});
|
|
141
148
|
|
|
@@ -178,7 +185,7 @@ const deduplicateTrips = (trips, commonStopId) => {
|
|
|
178
185
|
// Only add trip if no existing trip with the same set of timepoints has already been added.
|
|
179
186
|
const tripIsUnique = every(similarTrips, (similarTrip) => {
|
|
180
187
|
const similarTripStoptimes = similarTrip.stoptimes.map(
|
|
181
|
-
(stoptime) => stoptime.departure_time
|
|
188
|
+
(stoptime) => stoptime.departure_time,
|
|
182
189
|
);
|
|
183
190
|
return !isEqual(stoptimes, similarTripStoptimes);
|
|
184
191
|
});
|
|
@@ -227,7 +234,7 @@ const sortTrips = (trips, config) => {
|
|
|
227
234
|
sortedTrips = sortBy(
|
|
228
235
|
trips,
|
|
229
236
|
['firstStoptime', 'lastStoptime'],
|
|
230
|
-
['asc', 'asc']
|
|
237
|
+
['asc', 'asc'],
|
|
231
238
|
);
|
|
232
239
|
} else if (config.sortingAlgorithm === 'end') {
|
|
233
240
|
// Sort trips chronologically using last stoptime of each trip, which can be at different stops.
|
|
@@ -244,7 +251,7 @@ const sortTrips = (trips, config) => {
|
|
|
244
251
|
sortedTrips = sortBy(
|
|
245
252
|
trips,
|
|
246
253
|
['lastStoptime', 'firstStoptime'],
|
|
247
|
-
['asc', 'asc']
|
|
254
|
+
['asc', 'asc'],
|
|
248
255
|
);
|
|
249
256
|
} else if (config.sortingAlgorithm === 'first') {
|
|
250
257
|
// Sort trips chronologically using the stoptime of a the first stop of the longest trip.
|
|
@@ -281,7 +288,7 @@ const getCalendarDatesForTimetable = (timetable, config) => {
|
|
|
281
288
|
service_id: timetable.service_ids,
|
|
282
289
|
},
|
|
283
290
|
[],
|
|
284
|
-
[['date', 'ASC']]
|
|
291
|
+
[['date', 'ASC']],
|
|
285
292
|
);
|
|
286
293
|
const start = fromGTFSDate(timetable.start_date);
|
|
287
294
|
const end = fromGTFSDate(timetable.end_date);
|
|
@@ -294,11 +301,11 @@ const getCalendarDatesForTimetable = (timetable, config) => {
|
|
|
294
301
|
if (moment(calendarDate.date, 'YYYYMMDD').isBetween(start, end)) {
|
|
295
302
|
if (calendarDate.exception_type === 1) {
|
|
296
303
|
filteredCalendarDates.includedDates.push(
|
|
297
|
-
formatDate(calendarDate, config.dateFormat)
|
|
304
|
+
formatDate(calendarDate, config.dateFormat),
|
|
298
305
|
);
|
|
299
306
|
} else if (calendarDate.exception_type === 2) {
|
|
300
307
|
filteredCalendarDates.excludedDates.push(
|
|
301
|
-
formatDate(calendarDate, config.dateFormat)
|
|
308
|
+
formatDate(calendarDate, config.dateFormat),
|
|
302
309
|
);
|
|
303
310
|
}
|
|
304
311
|
}
|
|
@@ -340,14 +347,21 @@ const getDirectionHeadsignFromTimetable = (timetable) => {
|
|
|
340
347
|
direction_id: timetable.direction_id,
|
|
341
348
|
route_id: timetable.route_ids,
|
|
342
349
|
},
|
|
343
|
-
['trip_headsign']
|
|
350
|
+
['trip_headsign'],
|
|
344
351
|
);
|
|
345
352
|
|
|
346
353
|
if (trips.length === 0) {
|
|
347
354
|
return '';
|
|
348
355
|
}
|
|
349
356
|
|
|
350
|
-
|
|
357
|
+
const mostCommonHeadsign = flow(
|
|
358
|
+
countBy,
|
|
359
|
+
entries,
|
|
360
|
+
partialRight(maxBy, last),
|
|
361
|
+
head,
|
|
362
|
+
)(compact(trips.map((trip) => trip.trip_headsign)));
|
|
363
|
+
|
|
364
|
+
return mostCommonHeadsign;
|
|
351
365
|
};
|
|
352
366
|
|
|
353
367
|
/*
|
|
@@ -394,13 +408,13 @@ const getTimetableNotesForTimetable = (timetable, config) => {
|
|
|
394
408
|
// Note references with stop_sequence must also have stop_id.
|
|
395
409
|
if (noteReference.stop_id === '' || noteReference.stop_id === null) {
|
|
396
410
|
config.logWarning(
|
|
397
|
-
`Timetable Note Reference for note_id=${noteReference.note_id} has a \`stop_sequence\` but no \`stop_id\` - ignoring
|
|
411
|
+
`Timetable Note Reference for note_id=${noteReference.note_id} has a \`stop_sequence\` but no \`stop_id\` - ignoring`,
|
|
398
412
|
);
|
|
399
413
|
continue;
|
|
400
414
|
}
|
|
401
415
|
|
|
402
416
|
const stop = timetable.stops.find(
|
|
403
|
-
(stop) => stop.stop_id === noteReference.stop_id
|
|
417
|
+
(stop) => stop.stop_id === noteReference.stop_id,
|
|
404
418
|
);
|
|
405
419
|
|
|
406
420
|
if (!stop) {
|
|
@@ -408,7 +422,7 @@ const getTimetableNotesForTimetable = (timetable, config) => {
|
|
|
408
422
|
}
|
|
409
423
|
|
|
410
424
|
const tripWithMatchingStopSequence = stop.trips.find(
|
|
411
|
-
(trip) => trip.stop_sequence === noteReference.stop_sequence
|
|
425
|
+
(trip) => trip.stop_sequence === noteReference.stop_sequence,
|
|
412
426
|
);
|
|
413
427
|
|
|
414
428
|
if (tripWithMatchingStopSequence) {
|
|
@@ -472,7 +486,7 @@ const convertRouteToTimetablePage = (
|
|
|
472
486
|
direction,
|
|
473
487
|
calendars,
|
|
474
488
|
calendarDates,
|
|
475
|
-
config
|
|
489
|
+
config,
|
|
476
490
|
) => {
|
|
477
491
|
const timetable = {
|
|
478
492
|
route_ids: [route.route_id],
|
|
@@ -501,10 +515,12 @@ const convertRouteToTimetablePage = (
|
|
|
501
515
|
Object.assign(timetable, getDaysFromCalendars(calendars));
|
|
502
516
|
|
|
503
517
|
timetable.start_date = toGTFSDate(
|
|
504
|
-
moment.min(
|
|
518
|
+
moment.min(
|
|
519
|
+
calendars.map((calendar) => fromGTFSDate(calendar.start_date)),
|
|
520
|
+
),
|
|
505
521
|
);
|
|
506
522
|
timetable.end_date = toGTFSDate(
|
|
507
|
-
moment.max(calendars.map((calendar) => fromGTFSDate(calendar.end_date)))
|
|
523
|
+
moment.max(calendars.map((calendar) => fromGTFSDate(calendar.end_date))),
|
|
508
524
|
);
|
|
509
525
|
}
|
|
510
526
|
|
|
@@ -528,7 +544,7 @@ const convertRoutesToTimetablePages = (config) => {
|
|
|
528
544
|
.prepare(
|
|
529
545
|
`SELECT * FROM calendar_dates WHERE exception_type = 1 AND service_id NOT IN (${serviceIds
|
|
530
546
|
.map((serviceId) => `'${serviceId}'`)
|
|
531
|
-
.join(', ')})
|
|
547
|
+
.join(', ')})`,
|
|
532
548
|
)
|
|
533
549
|
.all();
|
|
534
550
|
|
|
@@ -537,7 +553,7 @@ const convertRoutesToTimetablePages = (config) => {
|
|
|
537
553
|
{
|
|
538
554
|
route_id: route.route_id,
|
|
539
555
|
},
|
|
540
|
-
['trip_headsign', 'direction_id']
|
|
556
|
+
['trip_headsign', 'direction_id'],
|
|
541
557
|
);
|
|
542
558
|
const directions = uniqBy(trips, (trip) => trip.direction_id);
|
|
543
559
|
const dayGroups = groupBy(calendars, calendarToCalendarCode);
|
|
@@ -545,7 +561,7 @@ const convertRoutesToTimetablePages = (config) => {
|
|
|
545
561
|
|
|
546
562
|
return directions.map((direction) => [
|
|
547
563
|
Object.values(dayGroups).map((calendars) =>
|
|
548
|
-
convertRouteToTimetablePage(route, direction, calendars, null, config)
|
|
564
|
+
convertRouteToTimetablePage(route, direction, calendars, null, config),
|
|
549
565
|
),
|
|
550
566
|
Object.values(calendarDateGroups).map((calendarDates) =>
|
|
551
567
|
convertRouteToTimetablePage(
|
|
@@ -553,8 +569,8 @@ const convertRoutesToTimetablePages = (config) => {
|
|
|
553
569
|
direction,
|
|
554
570
|
null,
|
|
555
571
|
calendarDates,
|
|
556
|
-
config
|
|
557
|
-
)
|
|
572
|
+
config,
|
|
573
|
+
),
|
|
558
574
|
),
|
|
559
575
|
]);
|
|
560
576
|
});
|
|
@@ -567,7 +583,7 @@ const convertRoutesToTimetablePages = (config) => {
|
|
|
567
583
|
*/
|
|
568
584
|
const generateTripsByFrequencies = (trip, frequencies, config) => {
|
|
569
585
|
const formattedFrequencies = frequencies.map((frequency) =>
|
|
570
|
-
formatFrequency(frequency, config)
|
|
586
|
+
formatFrequency(frequency, config),
|
|
571
587
|
);
|
|
572
588
|
const resetTrip = resetStoptimesToMidnight(trip);
|
|
573
589
|
const trips = [];
|
|
@@ -600,7 +616,7 @@ const generateTripsByFrequencies = (trip, frequencies, config) => {
|
|
|
600
616
|
const duplicateStopsForDifferentArrivalDeparture = (
|
|
601
617
|
stopIds,
|
|
602
618
|
timetable,
|
|
603
|
-
config
|
|
619
|
+
config,
|
|
604
620
|
) => {
|
|
605
621
|
if (config.showArrivalOnDifference === null) {
|
|
606
622
|
return stopIds;
|
|
@@ -610,7 +626,7 @@ const duplicateStopsForDifferentArrivalDeparture = (
|
|
|
610
626
|
for (const stoptime of trip.stoptimes) {
|
|
611
627
|
const timepointDifference = fromGTFSTime(stoptime.departure_time).diff(
|
|
612
628
|
fromGTFSTime(stoptime.arrival_time),
|
|
613
|
-
'minutes'
|
|
629
|
+
'minutes',
|
|
614
630
|
);
|
|
615
631
|
|
|
616
632
|
if (timepointDifference < config.showArrivalOnDifference) {
|
|
@@ -646,12 +662,12 @@ const getStopOrder = (timetable, config) => {
|
|
|
646
662
|
timetable_id: timetable.timetable_id,
|
|
647
663
|
},
|
|
648
664
|
['stop_id'],
|
|
649
|
-
[['stop_sequence', 'ASC']]
|
|
665
|
+
[['stop_sequence', 'ASC']],
|
|
650
666
|
);
|
|
651
667
|
|
|
652
668
|
if (timetableStopOrders.length > 0) {
|
|
653
669
|
return timetableStopOrders.map(
|
|
654
|
-
(timetableStopOrder) => timetableStopOrder.stop_id
|
|
670
|
+
(timetableStopOrder) => timetableStopOrder.stop_id,
|
|
655
671
|
);
|
|
656
672
|
}
|
|
657
673
|
|
|
@@ -660,13 +676,15 @@ const getStopOrder = (timetable, config) => {
|
|
|
660
676
|
const stopGraph = [];
|
|
661
677
|
|
|
662
678
|
for (const trip of timetable.orderedTrips) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
679
|
+
const sortedStopIds = trip.stoptimes
|
|
680
|
+
.filter((stoptime) => {
|
|
681
|
+
// If `showOnlyTimepoint` is true, then filter out all non-timepoints.
|
|
682
|
+
if (config.showOnlyTimepoint === true) {
|
|
683
|
+
return isTimepoint(stoptime);
|
|
684
|
+
}
|
|
685
|
+
return true;
|
|
686
|
+
})
|
|
687
|
+
.map((stoptime) => stoptime.stop_id);
|
|
670
688
|
|
|
671
689
|
for (const [index, stopId] of sortedStopIds.entries()) {
|
|
672
690
|
if (index === sortedStopIds.length - 1) {
|
|
@@ -682,7 +700,7 @@ const getStopOrder = (timetable, config) => {
|
|
|
682
700
|
return duplicateStopsForDifferentArrivalDeparture(
|
|
683
701
|
stopIds,
|
|
684
702
|
timetable,
|
|
685
|
-
config
|
|
703
|
+
config,
|
|
686
704
|
);
|
|
687
705
|
} catch {
|
|
688
706
|
// Ignore errors and move to next strategy.
|
|
@@ -691,7 +709,7 @@ const getStopOrder = (timetable, config) => {
|
|
|
691
709
|
// Finally, fall back to using the stop order from the trip with the most stoptimes.
|
|
692
710
|
const longestTripStoptimes = getLongestTripStoptimes(
|
|
693
711
|
timetable.orderedTrips,
|
|
694
|
-
config
|
|
712
|
+
config,
|
|
695
713
|
);
|
|
696
714
|
const stopIds = longestTripStoptimes.map((stoptime) => stoptime.stop_id);
|
|
697
715
|
|
|
@@ -714,7 +732,7 @@ const getStopsForTimetable = (timetable, config) => {
|
|
|
714
732
|
|
|
715
733
|
if (stops.length === 0) {
|
|
716
734
|
throw new Error(
|
|
717
|
-
`No stop found found for stop_id=${stopId} in timetable_id=${timetable.timetable_id}
|
|
735
|
+
`No stop found found for stop_id=${stopId} in timetable_id=${timetable.timetable_id}`,
|
|
718
736
|
);
|
|
719
737
|
}
|
|
720
738
|
|
|
@@ -743,7 +761,7 @@ const getStopsForTimetable = (timetable, config) => {
|
|
|
743
761
|
|
|
744
762
|
for (const stopAttribute of stopAttributes) {
|
|
745
763
|
const stop = orderedStops.find(
|
|
746
|
-
(stop) => stop.stop_id === stopAttribute.stop_id
|
|
764
|
+
(stop) => stop.stop_id === stopAttribute.stop_id,
|
|
747
765
|
);
|
|
748
766
|
|
|
749
767
|
if (stop) {
|
|
@@ -782,7 +800,7 @@ const getCalendarsFromTimetable = (timetable) => {
|
|
|
782
800
|
|
|
783
801
|
return memo;
|
|
784
802
|
},
|
|
785
|
-
[]
|
|
803
|
+
[],
|
|
786
804
|
);
|
|
787
805
|
|
|
788
806
|
if (dayQueries.length > 0) {
|
|
@@ -814,8 +832,8 @@ const getCalendarDatesServiceIds = (startDate, endDate) => {
|
|
|
814
832
|
const calendarDates = db
|
|
815
833
|
.prepare(
|
|
816
834
|
`SELECT DISTINCT service_id FROM calendar_dates WHERE ${whereClauses.join(
|
|
817
|
-
' AND '
|
|
818
|
-
)}
|
|
835
|
+
' AND ',
|
|
836
|
+
)}`,
|
|
819
837
|
)
|
|
820
838
|
.all();
|
|
821
839
|
return calendarDates.map((calendarDate) => calendarDate.service_id);
|
|
@@ -841,7 +859,7 @@ const getAllStationStopIds = (stopId) => {
|
|
|
841
859
|
{
|
|
842
860
|
parent_station: stop.parent_station,
|
|
843
861
|
},
|
|
844
|
-
['stop_id']
|
|
862
|
+
['stop_id'],
|
|
845
863
|
);
|
|
846
864
|
|
|
847
865
|
return [
|
|
@@ -859,7 +877,7 @@ const getTripsWithSameBlock = (trip, timetable) => {
|
|
|
859
877
|
block_id: trip.block_id,
|
|
860
878
|
service_id: timetable.service_ids,
|
|
861
879
|
},
|
|
862
|
-
['trip_id', 'route_id']
|
|
880
|
+
['trip_id', 'route_id'],
|
|
863
881
|
);
|
|
864
882
|
|
|
865
883
|
for (const blockTrip of trips) {
|
|
@@ -868,12 +886,12 @@ const getTripsWithSameBlock = (trip, timetable) => {
|
|
|
868
886
|
trip_id: blockTrip.trip_id,
|
|
869
887
|
},
|
|
870
888
|
[],
|
|
871
|
-
[['stop_sequence', 'ASC']]
|
|
889
|
+
[['stop_sequence', 'ASC']],
|
|
872
890
|
);
|
|
873
891
|
|
|
874
892
|
if (stopTimes.length === 0) {
|
|
875
893
|
throw new Error(
|
|
876
|
-
`No stoptimes found found for trip_id=${blockTrip.trip_id}
|
|
894
|
+
`No stoptimes found found for trip_id=${blockTrip.trip_id}`,
|
|
877
895
|
);
|
|
878
896
|
}
|
|
879
897
|
|
|
@@ -906,7 +924,7 @@ const addTripContinuation = (trip, timetable) => {
|
|
|
906
924
|
blockTrips,
|
|
907
925
|
(blockTrip) =>
|
|
908
926
|
blockTrip.lastStoptime.arrival_timestamp <=
|
|
909
|
-
firstStoptime.departure_timestamp
|
|
927
|
+
firstStoptime.departure_timestamp,
|
|
910
928
|
);
|
|
911
929
|
|
|
912
930
|
/*
|
|
@@ -936,7 +954,7 @@ const addTripContinuation = (trip, timetable) => {
|
|
|
936
954
|
blockTrips,
|
|
937
955
|
(blockTrip) =>
|
|
938
956
|
blockTrip.firstStoptime.departure_timestamp >=
|
|
939
|
-
lastStoptime.arrival_timestamp
|
|
957
|
+
lastStoptime.arrival_timestamp,
|
|
940
958
|
);
|
|
941
959
|
|
|
942
960
|
// "Continues As" trips must be a different route_id.
|
|
@@ -993,7 +1011,7 @@ const filterTrips = (timetable) => {
|
|
|
993
1011
|
const timetableStopIds = new Set(timetable.stops.map((stop) => stop.stop_id));
|
|
994
1012
|
for (const trip of filteredTrips) {
|
|
995
1013
|
trip.stoptimes = trip.stoptimes.filter((stoptime) =>
|
|
996
|
-
timetableStopIds.has(stoptime.stop_id)
|
|
1014
|
+
timetableStopIds.has(stoptime.stop_id),
|
|
997
1015
|
);
|
|
998
1016
|
}
|
|
999
1017
|
|
|
@@ -1023,10 +1041,10 @@ const getTripsForTimetable = (timetable, calendars, config) => {
|
|
|
1023
1041
|
if (trips.length === 0) {
|
|
1024
1042
|
timetable.warnings.push(
|
|
1025
1043
|
`No trips found for route_id=${timetable.route_ids.join(
|
|
1026
|
-
'_'
|
|
1044
|
+
'_',
|
|
1027
1045
|
)}, direction_id=${timetable.direction_id}, service_ids=${JSON.stringify(
|
|
1028
|
-
timetable.service_ids
|
|
1029
|
-
)}, timetable_id=${timetable.timetable_id}
|
|
1046
|
+
timetable.service_ids,
|
|
1047
|
+
)}, timetable_id=${timetable.timetable_id}`,
|
|
1030
1048
|
);
|
|
1031
1049
|
}
|
|
1032
1050
|
|
|
@@ -1046,7 +1064,7 @@ const getTripsForTimetable = (timetable, calendars, config) => {
|
|
|
1046
1064
|
trip_id: formattedTrip.trip_id,
|
|
1047
1065
|
},
|
|
1048
1066
|
[],
|
|
1049
|
-
[['stop_sequence', 'ASC']]
|
|
1067
|
+
[['stop_sequence', 'ASC']],
|
|
1050
1068
|
);
|
|
1051
1069
|
|
|
1052
1070
|
if (formattedTrip.stoptimes.length === 0) {
|
|
@@ -1055,7 +1073,7 @@ const getTripsForTimetable = (timetable, calendars, config) => {
|
|
|
1055
1073
|
formattedTrip.trip_id
|
|
1056
1074
|
}, route_id=${timetable.route_ids.join('_')}, timetable_id=${
|
|
1057
1075
|
timetable.timetable_id
|
|
1058
|
-
}
|
|
1076
|
+
}`,
|
|
1059
1077
|
);
|
|
1060
1078
|
}
|
|
1061
1079
|
|
|
@@ -1092,7 +1110,7 @@ const getTripsForTimetable = (timetable, calendars, config) => {
|
|
|
1092
1110
|
}
|
|
1093
1111
|
|
|
1094
1112
|
const tripFrequencies = frequencies.filter(
|
|
1095
|
-
(frequency) => frequency.trip_id === trip.trip_id
|
|
1113
|
+
(frequency) => frequency.trip_id === trip.trip_id,
|
|
1096
1114
|
);
|
|
1097
1115
|
|
|
1098
1116
|
if (tripFrequencies.length === 0) {
|
|
@@ -1101,7 +1119,7 @@ const getTripsForTimetable = (timetable, calendars, config) => {
|
|
|
1101
1119
|
const frequencyTrips = generateTripsByFrequencies(
|
|
1102
1120
|
formattedTrip,
|
|
1103
1121
|
frequencies,
|
|
1104
|
-
config
|
|
1122
|
+
config,
|
|
1105
1123
|
);
|
|
1106
1124
|
formattedTrips.push(...frequencyTrips);
|
|
1107
1125
|
timetable.frequencies = frequencies;
|
|
@@ -1124,13 +1142,13 @@ const getTripsForTimetable = (timetable, calendars, config) => {
|
|
|
1124
1142
|
{
|
|
1125
1143
|
stop_id: uniq(stopIds),
|
|
1126
1144
|
},
|
|
1127
|
-
['parent_station', 'stop_id']
|
|
1145
|
+
['parent_station', 'stop_id'],
|
|
1128
1146
|
);
|
|
1129
1147
|
|
|
1130
1148
|
for (const trip of formattedTrips) {
|
|
1131
1149
|
for (const stoptime of trip.stoptimes) {
|
|
1132
1150
|
const parentStationStop = stops.find(
|
|
1133
|
-
(stop) => stop.stop_id === stoptime.stop_id
|
|
1151
|
+
(stop) => stop.stop_id === stoptime.stop_id,
|
|
1134
1152
|
);
|
|
1135
1153
|
stoptime.stop_id =
|
|
1136
1154
|
parentStationStop.parent_station || parentStationStop.stop_id;
|
|
@@ -1155,7 +1173,7 @@ const formatTimetables = (timetables, config) => {
|
|
|
1155
1173
|
if (timetable.include_exceptions === 1) {
|
|
1156
1174
|
const calendarDatesServiceIds = getCalendarDatesServiceIds(
|
|
1157
1175
|
timetable.start_date,
|
|
1158
|
-
timetable.end_date
|
|
1176
|
+
timetable.end_date,
|
|
1159
1177
|
);
|
|
1160
1178
|
serviceIds = uniq([...serviceIds, ...calendarDatesServiceIds]);
|
|
1161
1179
|
}
|
|
@@ -1205,7 +1223,7 @@ const formatTimetables = (timetables, config) => {
|
|
|
1205
1223
|
}
|
|
1206
1224
|
|
|
1207
1225
|
return formattedTimetables.filter(
|
|
1208
|
-
(timetable) => timetable.orderedTrips.length > 0
|
|
1226
|
+
(timetable) => timetable.orderedTrips.length > 0,
|
|
1209
1227
|
);
|
|
1210
1228
|
};
|
|
1211
1229
|
|
|
@@ -1223,14 +1241,14 @@ export function getTimetablePagesForAgency(config) {
|
|
|
1223
1241
|
const timetablePages = getTimetablePages(
|
|
1224
1242
|
{},
|
|
1225
1243
|
[],
|
|
1226
|
-
[['timetable_page_id', 'ASC']]
|
|
1244
|
+
[['timetable_page_id', 'ASC']],
|
|
1227
1245
|
);
|
|
1228
1246
|
|
|
1229
1247
|
// Check if there are any timetable pages defined in `timetable_pages.txt`.
|
|
1230
1248
|
if (timetablePages.length === 0) {
|
|
1231
1249
|
// If no timetablepages, use timetables
|
|
1232
1250
|
return timetables.map((timetable) =>
|
|
1233
|
-
convertTimetableToTimetablePage(timetable, config)
|
|
1251
|
+
convertTimetableToTimetablePage(timetable, config),
|
|
1234
1252
|
);
|
|
1235
1253
|
}
|
|
1236
1254
|
|
|
@@ -1241,15 +1259,15 @@ export function getTimetablePagesForAgency(config) {
|
|
|
1241
1259
|
timetablePage.timetables = sortBy(
|
|
1242
1260
|
timetables.filter(
|
|
1243
1261
|
(timetable) =>
|
|
1244
|
-
timetable.timetable_page_id === timetablePage.timetable_page_id
|
|
1262
|
+
timetable.timetable_page_id === timetablePage.timetable_page_id,
|
|
1245
1263
|
),
|
|
1246
|
-
'timetable_sequence'
|
|
1264
|
+
'timetable_sequence',
|
|
1247
1265
|
);
|
|
1248
1266
|
|
|
1249
1267
|
// Add routes for each timetable.
|
|
1250
1268
|
for (const timetable of timetablePage.timetables) {
|
|
1251
1269
|
timetable.routes = routes.filter((route) =>
|
|
1252
|
-
timetable.route_ids.includes(route.route_id)
|
|
1270
|
+
timetable.route_ids.includes(route.route_id),
|
|
1253
1271
|
);
|
|
1254
1272
|
}
|
|
1255
1273
|
|
|
@@ -1270,7 +1288,7 @@ const getTimetablePageById = (timetablePageId, config) => {
|
|
|
1270
1288
|
|
|
1271
1289
|
if (timetablePages.length > 1) {
|
|
1272
1290
|
throw new Error(
|
|
1273
|
-
`Multiple timetable_pages found for timetable_page_id=${timetablePageId}
|
|
1291
|
+
`Multiple timetable_pages found for timetable_page_id=${timetablePageId}`,
|
|
1274
1292
|
);
|
|
1275
1293
|
}
|
|
1276
1294
|
|
|
@@ -1279,9 +1297,9 @@ const getTimetablePageById = (timetablePageId, config) => {
|
|
|
1279
1297
|
const timetablePage = timetablePages[0];
|
|
1280
1298
|
timetablePage.timetables = sortBy(
|
|
1281
1299
|
timetables.filter(
|
|
1282
|
-
(timetable) => timetable.timetable_page_id === timetablePageId
|
|
1300
|
+
(timetable) => timetable.timetable_page_id === timetablePageId,
|
|
1283
1301
|
),
|
|
1284
|
-
'timetable_sequence'
|
|
1302
|
+
'timetable_sequence',
|
|
1285
1303
|
);
|
|
1286
1304
|
|
|
1287
1305
|
// Add routes for each timetable
|
|
@@ -1297,12 +1315,12 @@ const getTimetablePageById = (timetablePageId, config) => {
|
|
|
1297
1315
|
if (timetables.length > 0) {
|
|
1298
1316
|
// If no timetable_page, use timetable defined in `timetables.txt`.
|
|
1299
1317
|
const timetablePageTimetables = timetables.filter(
|
|
1300
|
-
(timetable) => timetable.timetable_id === timetablePageId
|
|
1318
|
+
(timetable) => timetable.timetable_id === timetablePageId,
|
|
1301
1319
|
);
|
|
1302
1320
|
|
|
1303
1321
|
if (timetablePageTimetables.length === 0) {
|
|
1304
1322
|
throw new Error(
|
|
1305
|
-
`No timetable found for timetable_page_id=${timetablePageId}
|
|
1323
|
+
`No timetable found for timetable_page_id=${timetablePageId}`,
|
|
1306
1324
|
);
|
|
1307
1325
|
}
|
|
1308
1326
|
|
|
@@ -1335,13 +1353,13 @@ const getTimetablePageById = (timetablePageId, config) => {
|
|
|
1335
1353
|
route_id: routeId,
|
|
1336
1354
|
direction_id: directionId,
|
|
1337
1355
|
},
|
|
1338
|
-
['trip_headsign', 'direction_id']
|
|
1356
|
+
['trip_headsign', 'direction_id'],
|
|
1339
1357
|
);
|
|
1340
1358
|
const directions = uniqBy(trips, (trip) => trip.direction_id);
|
|
1341
1359
|
|
|
1342
1360
|
if (directions.length === 0) {
|
|
1343
1361
|
throw new Error(
|
|
1344
|
-
`No trips found for timetable_page_id=${timetablePageId} route_id=${routeId} direction_id=${directionId}
|
|
1362
|
+
`No trips found for timetable_page_id=${timetablePageId} route_id=${routeId} direction_id=${directionId}`,
|
|
1345
1363
|
);
|
|
1346
1364
|
}
|
|
1347
1365
|
|
|
@@ -1362,7 +1380,7 @@ const getTimetablePageById = (timetablePageId, config) => {
|
|
|
1362
1380
|
directions[0],
|
|
1363
1381
|
calendars,
|
|
1364
1382
|
calendarDates,
|
|
1365
|
-
config
|
|
1383
|
+
config,
|
|
1366
1384
|
);
|
|
1367
1385
|
};
|
|
1368
1386
|
|
|
@@ -1437,33 +1455,33 @@ export function getFormattedTimetablePage(timetablePageId, config) {
|
|
|
1437
1455
|
|
|
1438
1456
|
timetablePage.consolidatedTimetables = formatTimetables(
|
|
1439
1457
|
timetablePage.timetables,
|
|
1440
|
-
config
|
|
1458
|
+
config,
|
|
1441
1459
|
);
|
|
1442
1460
|
timetablePage.timetable_page_label = formatTimetablePageLabel(timetablePage);
|
|
1443
1461
|
timetablePage.dayList = formatDays(
|
|
1444
1462
|
getDaysFromCalendars(timetablePage.consolidatedTimetables),
|
|
1445
|
-
config
|
|
1463
|
+
config,
|
|
1446
1464
|
);
|
|
1447
1465
|
timetablePage.dayLists = uniq(
|
|
1448
|
-
timetablePage.consolidatedTimetables.map((timetable) => timetable.dayList)
|
|
1466
|
+
timetablePage.consolidatedTimetables.map((timetable) => timetable.dayList),
|
|
1449
1467
|
);
|
|
1450
1468
|
timetablePage.route_ids = uniq(
|
|
1451
|
-
flatMap(timetablePage.consolidatedTimetables, 'route_ids')
|
|
1469
|
+
flatMap(timetablePage.consolidatedTimetables, 'route_ids'),
|
|
1452
1470
|
);
|
|
1453
1471
|
|
|
1454
1472
|
const timetableRoutes = getRoutes(
|
|
1455
1473
|
{
|
|
1456
1474
|
route_id: timetablePage.route_ids,
|
|
1457
1475
|
},
|
|
1458
|
-
['route_color', 'route_text_color', 'agency_id']
|
|
1476
|
+
['route_color', 'route_text_color', 'agency_id'],
|
|
1459
1477
|
);
|
|
1460
1478
|
|
|
1461
1479
|
timetablePage.routeColors = timetableRoutes.map((route) => route.route_color);
|
|
1462
1480
|
timetablePage.routeTextColors = timetableRoutes.map(
|
|
1463
|
-
(route) => route.route_text_color
|
|
1481
|
+
(route) => route.route_text_color,
|
|
1464
1482
|
);
|
|
1465
1483
|
timetablePage.agency_ids = compact(
|
|
1466
|
-
timetableRoutes.map((route) => route.agency_id)
|
|
1484
|
+
timetableRoutes.map((route) => route.agency_id),
|
|
1467
1485
|
);
|
|
1468
1486
|
|
|
1469
1487
|
// Set default filename.
|
|
@@ -1537,7 +1555,7 @@ export function generateTimetableCSV(timetable) {
|
|
|
1537
1555
|
lines.push([
|
|
1538
1556
|
'',
|
|
1539
1557
|
...timetable.orderedTrips.map((trip) =>
|
|
1540
|
-
formatTripNameForCSV(trip, timetable)
|
|
1558
|
+
formatTripNameForCSV(trip, timetable),
|
|
1541
1559
|
),
|
|
1542
1560
|
]);
|
|
1543
1561
|
|
|
@@ -1592,7 +1610,7 @@ export function generateOverviewHTML(timetablePages, config) {
|
|
|
1592
1610
|
) {
|
|
1593
1611
|
return Number.parseInt(
|
|
1594
1612
|
timetablePage.consolidatedTimetables[0].routes[0].route_short_name,
|
|
1595
|
-
10
|
|
1613
|
+
10,
|
|
1596
1614
|
);
|
|
1597
1615
|
}
|
|
1598
1616
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gtfs-to-html",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.7",
|
|
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.1",
|
|
41
41
|
"cli-table": "^0.3.11",
|
|
42
42
|
"copy-dir": "^1.3.0",
|
|
43
|
-
"csv-stringify": "^6.4.
|
|
43
|
+
"csv-stringify": "^6.4.4",
|
|
44
44
|
"express": "^4.18.2",
|
|
45
|
-
"gtfs": "^4.
|
|
46
|
-
"js-beautify": "^1.14.
|
|
45
|
+
"gtfs": "^4.5.0",
|
|
46
|
+
"js-beautify": "^1.14.11",
|
|
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.5.0",
|
|
53
53
|
"sanitize-filename": "^1.6.3",
|
|
54
54
|
"sqlstring": "^2.3.3",
|
|
55
55
|
"timer-machine": "^1.1.0",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"husky": "^8.0.3",
|
|
63
|
-
"lint-staged": "^
|
|
64
|
-
"prettier": "^3.0.
|
|
63
|
+
"lint-staged": "^15.0.2",
|
|
64
|
+
"prettier": "^3.0.3"
|
|
65
65
|
},
|
|
66
66
|
"engines": {
|
|
67
67
|
"node": ">= 18.0.0"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
th(scope="row" colspan=`${stop.trips.length + 1}`)= stop.stop_city
|
|
29
29
|
- previousCity = stop.stop_city
|
|
30
30
|
|
|
31
|
-
tr.stop-row(id=`stop_id_${formatHtmlId(stop.stop_id)}` data-stop-id=`${stop.stop_id}`)
|
|
31
|
+
tr.stop-row(id=`stop_id_${formatHtmlId(stop.stop_id)}` data-stop-id=`${stop.stop_id}` data-is-timepoint=`${stop.is_timepoint}`)
|
|
32
32
|
th.stop-name-container(scope="row")
|
|
33
33
|
include timetable_stop_name.pug
|
|
34
34
|
|