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.
@@ -61,11 +61,11 @@ function fromGTFSTime(timeString) {
61
61
  function toGTFSTime(time) {
62
62
  return time.format("HH:mm:ss");
63
63
  }
64
- function calendarToCalendarCode(c) {
65
- if (Object.values(c).every((value) => value === null)) {
64
+ function calendarToCalendarCode(calendar) {
65
+ if (Object.values(calendar).every((value) => value === null)) {
66
66
  return "";
67
67
  }
68
- return `${c.monday}${c.tuesday}${c.wednesday}${c.thursday}${c.friday}${c.saturday}${c.sunday}`;
68
+ return `${calendar.monday ?? "0"}${calendar.tuesday ?? "0"}${calendar.wednesday ?? "0"}${calendar.thursday ?? "0"}${calendar.friday ?? "0"}${calendar.saturday ?? "0"}${calendar.sunday ?? "0"}`;
69
69
  }
70
70
  function calendarCodeToCalendar(code) {
71
71
  const days2 = [
@@ -83,6 +83,56 @@ function calendarCodeToCalendar(code) {
83
83
  }
84
84
  return calendar;
85
85
  }
86
+ function calendarToDateList(calendar, startDate, endDate) {
87
+ if (!startDate || !endDate) {
88
+ return [];
89
+ }
90
+ const activeWeekdays = [
91
+ calendar.monday === 1 ? 1 : null,
92
+ calendar.tuesday === 1 ? 2 : null,
93
+ calendar.wednesday === 1 ? 3 : null,
94
+ calendar.thursday === 1 ? 4 : null,
95
+ calendar.friday === 1 ? 5 : null,
96
+ calendar.saturday === 1 ? 6 : null,
97
+ calendar.sunday === 1 ? 7 : null
98
+ ].filter((weekday) => weekday !== null);
99
+ if (activeWeekdays.length === 0) {
100
+ return [];
101
+ }
102
+ const activeWeekdaySet = new Set(activeWeekdays);
103
+ const dates = /* @__PURE__ */ new Set();
104
+ const date = moment(startDate.toString(), "YYYYMMDD");
105
+ const endDateMoment = moment(endDate.toString(), "YYYYMMDD");
106
+ while (date.isSameOrBefore(endDateMoment)) {
107
+ const isoWeekday = date.isoWeekday();
108
+ if (activeWeekdaySet.has(isoWeekday)) {
109
+ dates.add(parseInt(date.format("YYYYMMDD"), 10));
110
+ }
111
+ date.add(1, "day");
112
+ }
113
+ return Array.from(dates);
114
+ }
115
+ function combineCalendars(calendars) {
116
+ const combinedCalendar = {
117
+ monday: 0,
118
+ tuesday: 0,
119
+ wednesday: 0,
120
+ thursday: 0,
121
+ friday: 0,
122
+ saturday: 0,
123
+ sunday: 0
124
+ };
125
+ for (const calendar of calendars) {
126
+ for (const day of Object.keys(
127
+ combinedCalendar
128
+ )) {
129
+ if (calendar[day] === 1) {
130
+ combinedCalendar[day] = 1;
131
+ }
132
+ }
133
+ }
134
+ return combinedCalendar;
135
+ }
86
136
  function secondsAfterMidnight(timeString) {
87
137
  return moment.duration(timeString).asSeconds();
88
138
  }
@@ -493,7 +543,7 @@ function formatTripNameForCSV(trip, timetable) {
493
543
  // package.json
494
544
  var package_default = {
495
545
  name: "gtfs-to-html",
496
- version: "2.12.2",
546
+ version: "2.12.4",
497
547
  private: false,
498
548
  description: "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
499
549
  keywords: [
@@ -543,26 +593,26 @@ var package_default = {
543
593
  postinstall: "node scripts/postinstall.js"
544
594
  },
545
595
  dependencies: {
546
- "@maplibre/maplibre-gl-geocoder": "^1.9.1",
547
- "@turf/helpers": "^7.3.1",
548
- "@turf/simplify": "^7.3.1",
596
+ "@maplibre/maplibre-gl-geocoder": "^1.9.4",
597
+ "@turf/helpers": "^7.3.2",
598
+ "@turf/simplify": "^7.3.2",
549
599
  anchorme: "^3.0.8",
550
600
  archiver: "^7.0.1",
551
601
  "cli-table": "^0.3.11",
552
602
  "css.escape": "^1.5.1",
553
603
  "csv-stringify": "^6.6.0",
554
604
  express: "^5.2.1",
555
- gtfs: "^4.18.2",
605
+ gtfs: "^4.18.3",
556
606
  "gtfs-realtime-pbf-js-module": "^1.0.0",
557
607
  "js-beautify": "^1.15.4",
558
- "lodash-es": "^4.17.21",
559
- "maplibre-gl": "^5.14.0",
608
+ "lodash-es": "^4.17.23",
609
+ "maplibre-gl": "^5.16.0",
560
610
  marked: "^17.0.1",
561
611
  moment: "^2.30.1",
562
612
  pbf: "^4.0.1",
563
613
  "pretty-error": "^4.0.0",
564
614
  pug: "^3.0.3",
565
- puppeteer: "^24.32.0",
615
+ puppeteer: "^24.35.0",
566
616
  "sanitize-filename": "^1.6.3",
567
617
  "sanitize-html": "^2.17.0",
568
618
  sqlstring: "^2.3.3",
@@ -578,7 +628,7 @@ var package_default = {
578
628
  "@types/js-beautify": "^1.14.3",
579
629
  "@types/lodash-es": "^4.17.12",
580
630
  "@types/morgan": "^1.9.10",
581
- "@types/node": "^24",
631
+ "@types/node": "^25",
582
632
  "@types/pug": "^2.0.10",
583
633
  "@types/sanitize-html": "^2.16.0",
584
634
  "@types/sqlstring": "^2.3.2",
@@ -586,7 +636,7 @@ var package_default = {
586
636
  "@types/yargs": "^17.0.35",
587
637
  husky: "^9.1.7",
588
638
  "lint-staged": "^16.2.7",
589
- prettier: "^3.7.4",
639
+ prettier: "^3.8.1",
590
640
  tsup: "^8.5.1",
591
641
  typescript: "^5.9.3"
592
642
  },
@@ -667,6 +717,16 @@ var deduplicateTrips = (trips) => {
667
717
  ).join("|");
668
718
  if (!uniqueTrips.has(tripSignature)) {
669
719
  uniqueTrips.set(tripSignature, trip);
720
+ } else {
721
+ const existingTrip = uniqueTrips.get(tripSignature);
722
+ if (!existingTrip) {
723
+ continue;
724
+ }
725
+ if (!existingTrip.additional_service_ids) {
726
+ existingTrip.additional_service_ids = [];
727
+ }
728
+ existingTrip.additional_service_ids.push(trip.service_id);
729
+ uniqueTrips.set(tripSignature, existingTrip);
670
730
  }
671
731
  }
672
732
  return Array.from(uniqueTrips.values());
@@ -1251,9 +1311,9 @@ var getCalendarsFromTimetable = (timetable) => {
1251
1311
  }
1252
1312
  return db.prepare(`SELECT * FROM calendar ${whereClause}`).all();
1253
1313
  };
1254
- var getCalendarDatesServiceIds = (startDate, endDate) => {
1314
+ var getCalendarDatesForDateRange = (startDate, endDate) => {
1255
1315
  const db = openDb();
1256
- const whereClauses = ["exception_type = 1"];
1316
+ const whereClauses = [];
1257
1317
  if (endDate) {
1258
1318
  whereClauses.push(`date <= ${sqlString.escape(endDate)}`);
1259
1319
  }
@@ -1261,11 +1321,11 @@ var getCalendarDatesServiceIds = (startDate, endDate) => {
1261
1321
  whereClauses.push(`date >= ${sqlString.escape(startDate)}`);
1262
1322
  }
1263
1323
  const calendarDates = db.prepare(
1264
- `SELECT DISTINCT service_id FROM calendar_dates WHERE ${whereClauses.join(
1324
+ `SELECT service_id, date, exception_type FROM calendar_dates WHERE ${whereClauses.join(
1265
1325
  " AND "
1266
1326
  )}`
1267
1327
  ).all();
1268
- return calendarDates.map((calendarDate) => calendarDate.service_id);
1328
+ return calendarDates;
1269
1329
  };
1270
1330
  var getAllStationStopIds = (stopId) => {
1271
1331
  const stops = getStops({
@@ -1348,7 +1408,7 @@ var addTripContinuation = (trip, timetable) => {
1348
1408
  trip.continues_as_route = nextTrip;
1349
1409
  }
1350
1410
  };
1351
- var filterTrips = (timetable, config) => {
1411
+ var filterTrips = (timetable, calendars, config) => {
1352
1412
  let filteredTrips = timetable.orderedTrips;
1353
1413
  for (const trip of filteredTrips) {
1354
1414
  const combinedStoptimes = [];
@@ -1375,7 +1435,26 @@ var filterTrips = (timetable, config) => {
1375
1435
  if (config.showDuplicateTrips === false) {
1376
1436
  filteredTrips = deduplicateTrips(filteredTrips);
1377
1437
  }
1378
- return filteredTrips;
1438
+ const formattedTrips = filteredTrips.map((trip) => {
1439
+ const tripCalendars = calendars.filter((calendar) => {
1440
+ return [
1441
+ trip.service_id,
1442
+ ...trip.additional_service_ids || []
1443
+ ].includes(calendar.service_id);
1444
+ }) ?? [];
1445
+ trip.dayList = formatDays(combineCalendars(tripCalendars), config);
1446
+ trip.dayListLong = formatDaysLong(trip.dayList, config);
1447
+ if (timetable.routes.length === 1) {
1448
+ trip.route_short_name = timetable.routes[0].route_short_name;
1449
+ } else {
1450
+ const route = timetable.routes.find(
1451
+ (route2) => route2.route_id === trip.route_id
1452
+ );
1453
+ trip.route_short_name = route?.route_short_name;
1454
+ }
1455
+ return trip;
1456
+ });
1457
+ return formattedTrips;
1379
1458
  };
1380
1459
  var getTripsForTimetable = (timetable, calendars, config) => {
1381
1460
  const tripQuery = {
@@ -1401,7 +1480,7 @@ var getTripsForTimetable = (timetable, calendars, config) => {
1401
1480
  timetable.service_ids = uniq(trips.map((trip) => trip.service_id));
1402
1481
  const formattedTrips = [];
1403
1482
  for (const trip of trips) {
1404
- const formattedTrip = formatTrip(trip, timetable, calendars, config);
1483
+ const formattedTrip = trip;
1405
1484
  formattedTrip.stoptimes = getStoptimes(
1406
1485
  {
1407
1486
  trip_id: formattedTrip.trip_id
@@ -1476,13 +1555,49 @@ var formatTimetables = (timetables, config) => {
1476
1555
  timetable.warnings = [];
1477
1556
  const dayList = formatDays(timetable, config);
1478
1557
  const calendars = getCalendarsFromTimetable(timetable);
1479
- let serviceIds = calendars.map((calendar) => calendar.service_id);
1558
+ const serviceIds = /* @__PURE__ */ new Set();
1559
+ for (const calendar of calendars) {
1560
+ serviceIds.add(calendar.service_id);
1561
+ }
1480
1562
  if (timetable.include_exceptions === 1) {
1481
- const calendarDatesServiceIds = getCalendarDatesServiceIds(
1563
+ const calendarDates = getCalendarDatesForDateRange(
1482
1564
  timetable.start_date,
1483
1565
  timetable.end_date
1484
1566
  );
1485
- serviceIds = uniq([...serviceIds, ...calendarDatesServiceIds]);
1567
+ const calendarDateGroups = groupBy(calendarDates, "service_id");
1568
+ for (const [serviceId, calendarDateGroup] of Object.entries(
1569
+ calendarDateGroups
1570
+ )) {
1571
+ const calendar = calendars.find(
1572
+ (c) => c.service_id === serviceId
1573
+ );
1574
+ if (calendarDateGroup.some(
1575
+ (calendarDate) => calendarDate.exception_type === 1
1576
+ )) {
1577
+ serviceIds.add(serviceId);
1578
+ }
1579
+ const calendarDateGroupExceptionType2 = calendarDateGroup.filter(
1580
+ (calendarDate) => calendarDate.exception_type === 2
1581
+ );
1582
+ if (timetable.start_date && timetable.end_date && calendar && calendarDateGroupExceptionType2.length > 0) {
1583
+ const datesDuringDateRange = calendarToDateList(
1584
+ calendar,
1585
+ timetable.start_date,
1586
+ timetable.end_date
1587
+ );
1588
+ if (datesDuringDateRange.length === 0) {
1589
+ serviceIds.delete(serviceId);
1590
+ }
1591
+ const everyDateIsExcluded = datesDuringDateRange.every(
1592
+ (dateDuringDateRange) => calendarDateGroupExceptionType2.some(
1593
+ (calendarDate) => calendarDate.date === dateDuringDateRange
1594
+ )
1595
+ );
1596
+ if (everyDateIsExcluded) {
1597
+ serviceIds.delete(serviceId);
1598
+ }
1599
+ }
1600
+ }
1486
1601
  }
1487
1602
  Object.assign(timetable, {
1488
1603
  noServiceSymbolUsed: false,
@@ -1500,7 +1615,7 @@ var formatTimetables = (timetables, config) => {
1500
1615
  noPickupSymbol: config.noPickupSymbol,
1501
1616
  interpolatedStopSymbol: config.interpolatedStopSymbol,
1502
1617
  orientation: timetable.orientation || config.defaultOrientation,
1503
- service_ids: serviceIds,
1618
+ service_ids: Array.from(serviceIds),
1504
1619
  dayList,
1505
1620
  dayListLong: formatDaysLong(dayList, config)
1506
1621
  });
@@ -1515,7 +1630,7 @@ var formatTimetables = (timetables, config) => {
1515
1630
  timetable.trip_ids = uniq(
1516
1631
  timetable.orderedTrips.map((trip) => trip.trip_id)
1517
1632
  );
1518
- timetable.orderedTrips = filterTrips(timetable, config);
1633
+ timetable.orderedTrips = filterTrips(timetable, calendars, config);
1519
1634
  timetable.stops = formatStops(timetable, config);
1520
1635
  return timetable;
1521
1636
  });
@@ -2070,22 +2185,6 @@ function formatDaysLong(dayList, config) {
2070
2185
  const mapObject = zipObject(config.daysShortStrings, config.daysStrings);
2071
2186
  return replaceAll(dayList, mapObject);
2072
2187
  }
2073
- function formatTrip(trip, timetable, calendars, config) {
2074
- trip.calendar = find2(calendars, {
2075
- service_id: trip.service_id
2076
- });
2077
- trip.dayList = formatDays(trip.calendar, config);
2078
- trip.dayListLong = formatDaysLong(trip.dayList, config);
2079
- if (timetable.routes.length === 1) {
2080
- trip.route_short_name = timetable.routes[0].route_short_name;
2081
- } else {
2082
- const route = timetable.routes.find(
2083
- (route2) => route2.route_id === trip.route_id
2084
- );
2085
- trip.route_short_name = route.route_short_name;
2086
- }
2087
- return trip;
2088
- }
2089
2188
  function formatFrequency(frequency, config) {
2090
2189
  const startTime = fromGTFSTime(frequency.start_time);
2091
2190
  const endTime = fromGTFSTime(frequency.end_time);