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 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(c) {
43
- if (Object.values(c).every((value) => value === null)) {
42
+ function calendarToCalendarCode(calendar) {
43
+ if (Object.values(calendar).every((value) => value === null)) {
44
44
  return "";
45
45
  }
46
- return `${c.monday}${c.tuesday}${c.wednesday}${c.thursday}${c.friday}${c.saturday}${c.sunday}`;
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.2",
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.1",
484
- "@turf/helpers": "^7.3.1",
485
- "@turf/simplify": "^7.3.1",
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.2",
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.21",
496
- "maplibre-gl": "^5.14.0",
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.32.0",
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": "^24",
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.7.4",
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 getCalendarDatesServiceIds = (startDate, endDate) => {
1251
+ var getCalendarDatesForDateRange = (startDate, endDate) => {
1192
1252
  const db = openDb();
1193
- const whereClauses = ["exception_type = 1"];
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 DISTINCT service_id FROM calendar_dates WHERE ${whereClauses.join(
1261
+ `SELECT service_id, date, exception_type FROM calendar_dates WHERE ${whereClauses.join(
1202
1262
  " AND "
1203
1263
  )}`
1204
1264
  ).all();
1205
- return calendarDates.map((calendarDate) => calendarDate.service_id);
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
- return filteredTrips;
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 = formatTrip(trip, timetable, calendars, config2);
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
- let serviceIds = calendars.map((calendar) => calendar.service_id);
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 calendarDatesServiceIds = getCalendarDatesServiceIds(
1500
+ const calendarDates = getCalendarDatesForDateRange(
1419
1501
  timetable.start_date,
1420
1502
  timetable.end_date
1421
1503
  );
1422
- serviceIds = uniq([...serviceIds, ...calendarDatesServiceIds]);
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);