gtfs-to-html 2.12.2 → 2.12.4
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/README.md +1 -0
- package/dist/app/index.js +140 -41
- package/dist/app/index.js.map +1 -1
- package/dist/bin/gtfs-to-html.js +140 -41
- package/dist/bin/gtfs-to-html.js.map +1 -1
- package/dist/frontend_libraries/maplibre-gl-geocoder.js +99 -103
- package/dist/frontend_libraries/maplibre-gl.js +4 -4
- package/dist/index.js +140 -41
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/views/default/css/timetable_styles.css +1 -1
- package/views/default/formatting_functions.pug +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ Many transit agencies use `gtfs-to-html` to generate the schedule pages used on
|
|
|
62
62
|
| Agency | Location |
|
|
63
63
|
| ----------------------------------------------------------------------------- | ----------------------------------- |
|
|
64
64
|
| [Basin Transit](https://basin-transit.com) | Morongo Basin, California |
|
|
65
|
+
| [Bayway Transit](https://www.baywaytransit.org) | Panama City, Florida |
|
|
65
66
|
| [Brockton Area Transit Authority](https://ridebat.com) | Brockton, Massachusetts |
|
|
66
67
|
| [Cape Ann Transportation Authority](https://canntran.com) | Gloucester, Massachusetts |
|
|
67
68
|
| [Capital Transit](https://juneaucapitaltransit.org) | Juneau, Alaska |
|
package/dist/app/index.js
CHANGED
|
@@ -39,11 +39,11 @@ function fromGTFSTime(timeString) {
|
|
|
39
39
|
function toGTFSTime(time) {
|
|
40
40
|
return time.format("HH:mm:ss");
|
|
41
41
|
}
|
|
42
|
-
function calendarToCalendarCode(
|
|
43
|
-
if (Object.values(
|
|
42
|
+
function calendarToCalendarCode(calendar) {
|
|
43
|
+
if (Object.values(calendar).every((value) => value === null)) {
|
|
44
44
|
return "";
|
|
45
45
|
}
|
|
46
|
-
return `${
|
|
46
|
+
return `${calendar.monday ?? "0"}${calendar.tuesday ?? "0"}${calendar.wednesday ?? "0"}${calendar.thursday ?? "0"}${calendar.friday ?? "0"}${calendar.saturday ?? "0"}${calendar.sunday ?? "0"}`;
|
|
47
47
|
}
|
|
48
48
|
function calendarCodeToCalendar(code) {
|
|
49
49
|
const days2 = [
|
|
@@ -61,6 +61,56 @@ function calendarCodeToCalendar(code) {
|
|
|
61
61
|
}
|
|
62
62
|
return calendar;
|
|
63
63
|
}
|
|
64
|
+
function calendarToDateList(calendar, startDate, endDate) {
|
|
65
|
+
if (!startDate || !endDate) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const activeWeekdays = [
|
|
69
|
+
calendar.monday === 1 ? 1 : null,
|
|
70
|
+
calendar.tuesday === 1 ? 2 : null,
|
|
71
|
+
calendar.wednesday === 1 ? 3 : null,
|
|
72
|
+
calendar.thursday === 1 ? 4 : null,
|
|
73
|
+
calendar.friday === 1 ? 5 : null,
|
|
74
|
+
calendar.saturday === 1 ? 6 : null,
|
|
75
|
+
calendar.sunday === 1 ? 7 : null
|
|
76
|
+
].filter((weekday) => weekday !== null);
|
|
77
|
+
if (activeWeekdays.length === 0) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
const activeWeekdaySet = new Set(activeWeekdays);
|
|
81
|
+
const dates = /* @__PURE__ */ new Set();
|
|
82
|
+
const date = moment(startDate.toString(), "YYYYMMDD");
|
|
83
|
+
const endDateMoment = moment(endDate.toString(), "YYYYMMDD");
|
|
84
|
+
while (date.isSameOrBefore(endDateMoment)) {
|
|
85
|
+
const isoWeekday = date.isoWeekday();
|
|
86
|
+
if (activeWeekdaySet.has(isoWeekday)) {
|
|
87
|
+
dates.add(parseInt(date.format("YYYYMMDD"), 10));
|
|
88
|
+
}
|
|
89
|
+
date.add(1, "day");
|
|
90
|
+
}
|
|
91
|
+
return Array.from(dates);
|
|
92
|
+
}
|
|
93
|
+
function combineCalendars(calendars) {
|
|
94
|
+
const combinedCalendar = {
|
|
95
|
+
monday: 0,
|
|
96
|
+
tuesday: 0,
|
|
97
|
+
wednesday: 0,
|
|
98
|
+
thursday: 0,
|
|
99
|
+
friday: 0,
|
|
100
|
+
saturday: 0,
|
|
101
|
+
sunday: 0
|
|
102
|
+
};
|
|
103
|
+
for (const calendar of calendars) {
|
|
104
|
+
for (const day of Object.keys(
|
|
105
|
+
combinedCalendar
|
|
106
|
+
)) {
|
|
107
|
+
if (calendar[day] === 1) {
|
|
108
|
+
combinedCalendar[day] = 1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return combinedCalendar;
|
|
113
|
+
}
|
|
64
114
|
function secondsAfterMidnight(timeString) {
|
|
65
115
|
return moment.duration(timeString).asSeconds();
|
|
66
116
|
}
|
|
@@ -430,7 +480,7 @@ function getAgencyGeoJSON(config2) {
|
|
|
430
480
|
// package.json
|
|
431
481
|
var package_default = {
|
|
432
482
|
name: "gtfs-to-html",
|
|
433
|
-
version: "2.12.
|
|
483
|
+
version: "2.12.4",
|
|
434
484
|
private: false,
|
|
435
485
|
description: "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
|
|
436
486
|
keywords: [
|
|
@@ -480,26 +530,26 @@ var package_default = {
|
|
|
480
530
|
postinstall: "node scripts/postinstall.js"
|
|
481
531
|
},
|
|
482
532
|
dependencies: {
|
|
483
|
-
"@maplibre/maplibre-gl-geocoder": "^1.9.
|
|
484
|
-
"@turf/helpers": "^7.3.
|
|
485
|
-
"@turf/simplify": "^7.3.
|
|
533
|
+
"@maplibre/maplibre-gl-geocoder": "^1.9.4",
|
|
534
|
+
"@turf/helpers": "^7.3.2",
|
|
535
|
+
"@turf/simplify": "^7.3.2",
|
|
486
536
|
anchorme: "^3.0.8",
|
|
487
537
|
archiver: "^7.0.1",
|
|
488
538
|
"cli-table": "^0.3.11",
|
|
489
539
|
"css.escape": "^1.5.1",
|
|
490
540
|
"csv-stringify": "^6.6.0",
|
|
491
541
|
express: "^5.2.1",
|
|
492
|
-
gtfs: "^4.18.
|
|
542
|
+
gtfs: "^4.18.3",
|
|
493
543
|
"gtfs-realtime-pbf-js-module": "^1.0.0",
|
|
494
544
|
"js-beautify": "^1.15.4",
|
|
495
|
-
"lodash-es": "^4.17.
|
|
496
|
-
"maplibre-gl": "^5.
|
|
545
|
+
"lodash-es": "^4.17.23",
|
|
546
|
+
"maplibre-gl": "^5.16.0",
|
|
497
547
|
marked: "^17.0.1",
|
|
498
548
|
moment: "^2.30.1",
|
|
499
549
|
pbf: "^4.0.1",
|
|
500
550
|
"pretty-error": "^4.0.0",
|
|
501
551
|
pug: "^3.0.3",
|
|
502
|
-
puppeteer: "^24.
|
|
552
|
+
puppeteer: "^24.35.0",
|
|
503
553
|
"sanitize-filename": "^1.6.3",
|
|
504
554
|
"sanitize-html": "^2.17.0",
|
|
505
555
|
sqlstring: "^2.3.3",
|
|
@@ -515,7 +565,7 @@ var package_default = {
|
|
|
515
565
|
"@types/js-beautify": "^1.14.3",
|
|
516
566
|
"@types/lodash-es": "^4.17.12",
|
|
517
567
|
"@types/morgan": "^1.9.10",
|
|
518
|
-
"@types/node": "^
|
|
568
|
+
"@types/node": "^25",
|
|
519
569
|
"@types/pug": "^2.0.10",
|
|
520
570
|
"@types/sanitize-html": "^2.16.0",
|
|
521
571
|
"@types/sqlstring": "^2.3.2",
|
|
@@ -523,7 +573,7 @@ var package_default = {
|
|
|
523
573
|
"@types/yargs": "^17.0.35",
|
|
524
574
|
husky: "^9.1.7",
|
|
525
575
|
"lint-staged": "^16.2.7",
|
|
526
|
-
prettier: "^3.
|
|
576
|
+
prettier: "^3.8.1",
|
|
527
577
|
tsup: "^8.5.1",
|
|
528
578
|
typescript: "^5.9.3"
|
|
529
579
|
},
|
|
@@ -604,6 +654,16 @@ var deduplicateTrips = (trips) => {
|
|
|
604
654
|
).join("|");
|
|
605
655
|
if (!uniqueTrips.has(tripSignature)) {
|
|
606
656
|
uniqueTrips.set(tripSignature, trip);
|
|
657
|
+
} else {
|
|
658
|
+
const existingTrip = uniqueTrips.get(tripSignature);
|
|
659
|
+
if (!existingTrip) {
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (!existingTrip.additional_service_ids) {
|
|
663
|
+
existingTrip.additional_service_ids = [];
|
|
664
|
+
}
|
|
665
|
+
existingTrip.additional_service_ids.push(trip.service_id);
|
|
666
|
+
uniqueTrips.set(tripSignature, existingTrip);
|
|
607
667
|
}
|
|
608
668
|
}
|
|
609
669
|
return Array.from(uniqueTrips.values());
|
|
@@ -1188,9 +1248,9 @@ var getCalendarsFromTimetable = (timetable) => {
|
|
|
1188
1248
|
}
|
|
1189
1249
|
return db.prepare(`SELECT * FROM calendar ${whereClause}`).all();
|
|
1190
1250
|
};
|
|
1191
|
-
var
|
|
1251
|
+
var getCalendarDatesForDateRange = (startDate, endDate) => {
|
|
1192
1252
|
const db = openDb();
|
|
1193
|
-
const whereClauses = [
|
|
1253
|
+
const whereClauses = [];
|
|
1194
1254
|
if (endDate) {
|
|
1195
1255
|
whereClauses.push(`date <= ${sqlString.escape(endDate)}`);
|
|
1196
1256
|
}
|
|
@@ -1198,11 +1258,11 @@ var getCalendarDatesServiceIds = (startDate, endDate) => {
|
|
|
1198
1258
|
whereClauses.push(`date >= ${sqlString.escape(startDate)}`);
|
|
1199
1259
|
}
|
|
1200
1260
|
const calendarDates = db.prepare(
|
|
1201
|
-
`SELECT
|
|
1261
|
+
`SELECT service_id, date, exception_type FROM calendar_dates WHERE ${whereClauses.join(
|
|
1202
1262
|
" AND "
|
|
1203
1263
|
)}`
|
|
1204
1264
|
).all();
|
|
1205
|
-
return calendarDates
|
|
1265
|
+
return calendarDates;
|
|
1206
1266
|
};
|
|
1207
1267
|
var getAllStationStopIds = (stopId) => {
|
|
1208
1268
|
const stops = getStops({
|
|
@@ -1285,7 +1345,7 @@ var addTripContinuation = (trip, timetable) => {
|
|
|
1285
1345
|
trip.continues_as_route = nextTrip;
|
|
1286
1346
|
}
|
|
1287
1347
|
};
|
|
1288
|
-
var filterTrips = (timetable, config2) => {
|
|
1348
|
+
var filterTrips = (timetable, calendars, config2) => {
|
|
1289
1349
|
let filteredTrips = timetable.orderedTrips;
|
|
1290
1350
|
for (const trip of filteredTrips) {
|
|
1291
1351
|
const combinedStoptimes = [];
|
|
@@ -1312,7 +1372,26 @@ var filterTrips = (timetable, config2) => {
|
|
|
1312
1372
|
if (config2.showDuplicateTrips === false) {
|
|
1313
1373
|
filteredTrips = deduplicateTrips(filteredTrips);
|
|
1314
1374
|
}
|
|
1315
|
-
|
|
1375
|
+
const formattedTrips = filteredTrips.map((trip) => {
|
|
1376
|
+
const tripCalendars = calendars.filter((calendar) => {
|
|
1377
|
+
return [
|
|
1378
|
+
trip.service_id,
|
|
1379
|
+
...trip.additional_service_ids || []
|
|
1380
|
+
].includes(calendar.service_id);
|
|
1381
|
+
}) ?? [];
|
|
1382
|
+
trip.dayList = formatDays(combineCalendars(tripCalendars), config2);
|
|
1383
|
+
trip.dayListLong = formatDaysLong(trip.dayList, config2);
|
|
1384
|
+
if (timetable.routes.length === 1) {
|
|
1385
|
+
trip.route_short_name = timetable.routes[0].route_short_name;
|
|
1386
|
+
} else {
|
|
1387
|
+
const route = timetable.routes.find(
|
|
1388
|
+
(route2) => route2.route_id === trip.route_id
|
|
1389
|
+
);
|
|
1390
|
+
trip.route_short_name = route?.route_short_name;
|
|
1391
|
+
}
|
|
1392
|
+
return trip;
|
|
1393
|
+
});
|
|
1394
|
+
return formattedTrips;
|
|
1316
1395
|
};
|
|
1317
1396
|
var getTripsForTimetable = (timetable, calendars, config2) => {
|
|
1318
1397
|
const tripQuery = {
|
|
@@ -1338,7 +1417,7 @@ var getTripsForTimetable = (timetable, calendars, config2) => {
|
|
|
1338
1417
|
timetable.service_ids = uniq(trips.map((trip) => trip.service_id));
|
|
1339
1418
|
const formattedTrips = [];
|
|
1340
1419
|
for (const trip of trips) {
|
|
1341
|
-
const formattedTrip =
|
|
1420
|
+
const formattedTrip = trip;
|
|
1342
1421
|
formattedTrip.stoptimes = getStoptimes(
|
|
1343
1422
|
{
|
|
1344
1423
|
trip_id: formattedTrip.trip_id
|
|
@@ -1413,13 +1492,49 @@ var formatTimetables = (timetables, config2) => {
|
|
|
1413
1492
|
timetable.warnings = [];
|
|
1414
1493
|
const dayList = formatDays(timetable, config2);
|
|
1415
1494
|
const calendars = getCalendarsFromTimetable(timetable);
|
|
1416
|
-
|
|
1495
|
+
const serviceIds = /* @__PURE__ */ new Set();
|
|
1496
|
+
for (const calendar of calendars) {
|
|
1497
|
+
serviceIds.add(calendar.service_id);
|
|
1498
|
+
}
|
|
1417
1499
|
if (timetable.include_exceptions === 1) {
|
|
1418
|
-
const
|
|
1500
|
+
const calendarDates = getCalendarDatesForDateRange(
|
|
1419
1501
|
timetable.start_date,
|
|
1420
1502
|
timetable.end_date
|
|
1421
1503
|
);
|
|
1422
|
-
|
|
1504
|
+
const calendarDateGroups = groupBy(calendarDates, "service_id");
|
|
1505
|
+
for (const [serviceId, calendarDateGroup] of Object.entries(
|
|
1506
|
+
calendarDateGroups
|
|
1507
|
+
)) {
|
|
1508
|
+
const calendar = calendars.find(
|
|
1509
|
+
(c) => c.service_id === serviceId
|
|
1510
|
+
);
|
|
1511
|
+
if (calendarDateGroup.some(
|
|
1512
|
+
(calendarDate) => calendarDate.exception_type === 1
|
|
1513
|
+
)) {
|
|
1514
|
+
serviceIds.add(serviceId);
|
|
1515
|
+
}
|
|
1516
|
+
const calendarDateGroupExceptionType2 = calendarDateGroup.filter(
|
|
1517
|
+
(calendarDate) => calendarDate.exception_type === 2
|
|
1518
|
+
);
|
|
1519
|
+
if (timetable.start_date && timetable.end_date && calendar && calendarDateGroupExceptionType2.length > 0) {
|
|
1520
|
+
const datesDuringDateRange = calendarToDateList(
|
|
1521
|
+
calendar,
|
|
1522
|
+
timetable.start_date,
|
|
1523
|
+
timetable.end_date
|
|
1524
|
+
);
|
|
1525
|
+
if (datesDuringDateRange.length === 0) {
|
|
1526
|
+
serviceIds.delete(serviceId);
|
|
1527
|
+
}
|
|
1528
|
+
const everyDateIsExcluded = datesDuringDateRange.every(
|
|
1529
|
+
(dateDuringDateRange) => calendarDateGroupExceptionType2.some(
|
|
1530
|
+
(calendarDate) => calendarDate.date === dateDuringDateRange
|
|
1531
|
+
)
|
|
1532
|
+
);
|
|
1533
|
+
if (everyDateIsExcluded) {
|
|
1534
|
+
serviceIds.delete(serviceId);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1423
1538
|
}
|
|
1424
1539
|
Object.assign(timetable, {
|
|
1425
1540
|
noServiceSymbolUsed: false,
|
|
@@ -1437,7 +1552,7 @@ var formatTimetables = (timetables, config2) => {
|
|
|
1437
1552
|
noPickupSymbol: config2.noPickupSymbol,
|
|
1438
1553
|
interpolatedStopSymbol: config2.interpolatedStopSymbol,
|
|
1439
1554
|
orientation: timetable.orientation || config2.defaultOrientation,
|
|
1440
|
-
service_ids: serviceIds,
|
|
1555
|
+
service_ids: Array.from(serviceIds),
|
|
1441
1556
|
dayList,
|
|
1442
1557
|
dayListLong: formatDaysLong(dayList, config2)
|
|
1443
1558
|
});
|
|
@@ -1452,7 +1567,7 @@ var formatTimetables = (timetables, config2) => {
|
|
|
1452
1567
|
timetable.trip_ids = uniq(
|
|
1453
1568
|
timetable.orderedTrips.map((trip) => trip.trip_id)
|
|
1454
1569
|
);
|
|
1455
|
-
timetable.orderedTrips = filterTrips(timetable, config2);
|
|
1570
|
+
timetable.orderedTrips = filterTrips(timetable, calendars, config2);
|
|
1456
1571
|
timetable.stops = formatStops(timetable, config2);
|
|
1457
1572
|
return timetable;
|
|
1458
1573
|
});
|
|
@@ -1953,22 +2068,6 @@ function formatDaysLong(dayList, config2) {
|
|
|
1953
2068
|
const mapObject = zipObject(config2.daysShortStrings, config2.daysStrings);
|
|
1954
2069
|
return replaceAll(dayList, mapObject);
|
|
1955
2070
|
}
|
|
1956
|
-
function formatTrip(trip, timetable, calendars, config2) {
|
|
1957
|
-
trip.calendar = find2(calendars, {
|
|
1958
|
-
service_id: trip.service_id
|
|
1959
|
-
});
|
|
1960
|
-
trip.dayList = formatDays(trip.calendar, config2);
|
|
1961
|
-
trip.dayListLong = formatDaysLong(trip.dayList, config2);
|
|
1962
|
-
if (timetable.routes.length === 1) {
|
|
1963
|
-
trip.route_short_name = timetable.routes[0].route_short_name;
|
|
1964
|
-
} else {
|
|
1965
|
-
const route = timetable.routes.find(
|
|
1966
|
-
(route2) => route2.route_id === trip.route_id
|
|
1967
|
-
);
|
|
1968
|
-
trip.route_short_name = route.route_short_name;
|
|
1969
|
-
}
|
|
1970
|
-
return trip;
|
|
1971
|
-
}
|
|
1972
2071
|
function formatFrequency(frequency, config2) {
|
|
1973
2072
|
const startTime = fromGTFSTime(frequency.start_time);
|
|
1974
2073
|
const endTime = fromGTFSTime(frequency.end_time);
|