gtfs-to-html 2.12.12 → 2.12.13

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/dist/app/index.js CHANGED
@@ -1,2547 +1,121 @@
1
- var __defProp = Object.defineProperty;
2
- var __export = (target, all) => {
3
- for (var name in all)
4
- __defProp(target, name, { get: all[name], enumerable: true });
5
- };
6
-
7
- // src/app/index.ts
8
- import { dirname as dirname2, join as join2 } from "path";
9
- import { fileURLToPath as fileURLToPath2 } from "url";
10
- import { readFileSync } from "fs";
1
+ import { E as GtfsToHtmlErrorCode, T as GtfsToHtmlErrorCategory, _ as setDefaultConfig, a as getPathToViewsFolder, c as untildify, d as generateOverviewHTML, g as getTimetablePagesForAgency, h as getFormattedTimetablePage, k as isGtfsToHtmlError, m as generateTimetableHTML, u as formatTimetableLabel, w as GtfsToHtmlError } from "../file-utils-B3ZcDOSK.js";
2
+ import { dirname, join } from "node:path";
3
+ import { openDb } from "gtfs";
4
+ import { readFileSync } from "node:fs";
5
+ import { fileURLToPath } from "node:url";
11
6
  import yargs from "yargs";
12
7
  import { hideBin } from "yargs/helpers";
13
- import { openDb as openDb2 } from "gtfs";
14
8
  import express from "express";
15
9
 
16
- // src/lib/formatters.ts
17
- import {
18
- clone,
19
- find as find2,
20
- first as first2,
21
- groupBy as groupBy2,
22
- last as last2,
23
- omit,
24
- sortBy as sortBy2,
25
- zipObject
26
- } from "lodash-es";
27
- import moment3 from "moment";
28
-
29
- // src/lib/time-utils.ts
30
- import moment from "moment";
31
- function fromGTFSTime(timeString) {
32
- const duration = moment.duration(timeString);
33
- return moment({
34
- hour: duration.hours(),
35
- minute: duration.minutes(),
36
- second: duration.seconds()
37
- });
38
- }
39
- function toGTFSTime(time) {
40
- return time.format("HH:mm:ss");
41
- }
42
- function calendarToCalendarCode(calendar) {
43
- if (Object.values(calendar).every((value) => value === null)) {
44
- return "";
45
- }
46
- return `${calendar.monday ?? "0"}${calendar.tuesday ?? "0"}${calendar.wednesday ?? "0"}${calendar.thursday ?? "0"}${calendar.friday ?? "0"}${calendar.saturday ?? "0"}${calendar.sunday ?? "0"}`;
47
- }
48
- function calendarCodeToCalendar(code) {
49
- const days2 = [
50
- "monday",
51
- "tuesday",
52
- "wednesday",
53
- "thursday",
54
- "friday",
55
- "saturday",
56
- "sunday"
57
- ];
58
- const calendar = {};
59
- for (const [index, day] of days2.entries()) {
60
- calendar[day] = code[index];
61
- }
62
- return calendar;
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
- }
114
- function secondsAfterMidnight(timeString) {
115
- return moment.duration(timeString).asSeconds();
116
- }
117
- function minutesAfterMidnight(timeString) {
118
- return moment.duration(timeString).asMinutes();
119
- }
120
- function updateTimeByOffset(timeString, offsetSeconds) {
121
- const newTime = fromGTFSTime(timeString);
122
- return toGTFSTime(newTime.add(offsetSeconds, "seconds"));
123
- }
124
-
125
- // src/lib/utils.ts
126
- import {
127
- cloneDeep,
128
- compact,
129
- countBy,
130
- difference,
131
- entries,
132
- every as every2,
133
- find,
134
- findLast,
135
- first,
136
- flatMap,
137
- flow,
138
- groupBy,
139
- head,
140
- last,
141
- maxBy,
142
- orderBy,
143
- partialRight,
144
- reduce,
145
- size,
146
- some,
147
- sortBy,
148
- uniq,
149
- uniqBy as uniqBy2,
150
- zip
151
- } from "lodash-es";
152
- import {
153
- getCalendarDates,
154
- getTrips,
155
- getTimetableNotesReferences,
156
- getTimetableNotes,
157
- getRoutes,
158
- getCalendars,
159
- getTimetableStopOrders,
160
- getStops,
161
- getStopAttributes,
162
- getStoptimes,
163
- getFrequencies,
164
- getTimetables,
165
- getTimetablePages,
166
- getAgencies as getAgencies2,
167
- openDb
168
- } from "gtfs";
169
- import { stringify } from "csv-stringify";
170
- import moment2 from "moment";
171
- import sqlString from "sqlstring";
172
- import toposort from "toposort";
173
-
174
- // src/lib/file-utils.ts
175
- import { dirname, join, resolve } from "path";
176
- import cssEscape from "css.escape";
177
- import { createWriteStream } from "fs";
178
- import { createRequire } from "module";
179
- import { fileURLToPath } from "url";
180
- import {
181
- access,
182
- cp,
183
- copyFile,
184
- mkdir,
185
- readdir,
186
- readFile,
187
- rm
188
- } from "fs/promises";
189
- import { homedir } from "os";
190
- import * as _ from "lodash-es";
191
- import { uniqBy } from "lodash-es";
192
- import { ZipArchive } from "archiver";
193
- import beautify from "js-beautify";
194
- import sanitizeHtml from "sanitize-html";
195
- import { renderFile } from "pug";
196
- import puppeteer from "puppeteer";
197
- import sanitize from "sanitize-filename";
198
- import { marked } from "marked";
199
-
200
- // src/lib/template-functions.ts
201
- var template_functions_exports = {};
202
- __export(template_functions_exports, {
203
- formatTripName: () => formatTripName,
204
- formatTripNameForCSV: () => formatTripNameForCSV,
205
- getNotesForStop: () => getNotesForStop,
206
- getNotesForStoptime: () => getNotesForStoptime,
207
- getNotesForTimetableLabel: () => getNotesForTimetableLabel,
208
- getNotesForTrip: () => getNotesForTrip,
209
- hasNotesOrNotices: () => hasNotesOrNotices,
210
- timetableHasDifferentDays: () => timetableHasDifferentDays,
211
- timetablePageHasDifferentDays: () => timetablePageHasDifferentDays,
212
- timetablePageHasDifferentLabels: () => timetablePageHasDifferentLabels
213
- });
214
- import { every } from "lodash-es";
215
- function timetableHasDifferentDays(timetable) {
216
- return !every(timetable.orderedTrips, (trip, idx) => {
217
- if (idx === 0) {
218
- return true;
219
- }
220
- return trip.dayList === timetable.orderedTrips[idx - 1].dayList;
221
- });
222
- }
223
- function timetablePageHasDifferentDays(timetablePage) {
224
- return !every(timetablePage.consolidatedTimetables, (timetable, idx) => {
225
- if (idx === 0) {
226
- return true;
227
- }
228
- return timetable.dayListLong === timetablePage.consolidatedTimetables[idx - 1].dayListLong;
229
- });
230
- }
231
- function timetablePageHasDifferentLabels(timetablePage) {
232
- return !every(timetablePage.consolidatedTimetables, (timetable, idx) => {
233
- if (idx === 0) {
234
- return true;
235
- }
236
- return timetable.timetable_label === timetablePage.consolidatedTimetables[idx - 1].timetable_label;
237
- });
238
- }
239
- function hasNotesOrNotices(timetable) {
240
- return timetable.requestPickupSymbolUsed || timetable.noPickupSymbolUsed || timetable.requestDropoffSymbolUsed || timetable.noDropoffSymbolUsed || timetable.noServiceSymbolUsed || timetable.interpolatedStopSymbolUsed || timetable.notes.length > 0;
241
- }
242
- function getNotesForTimetableLabel(notes) {
243
- return notes.filter((note) => !note.stop_id && !note.trip_id);
244
- }
245
- function getNotesForStop(notes, stop) {
246
- return notes.filter((note) => {
247
- if (note.trip_id) {
248
- return false;
249
- }
250
- if (note.stop_sequence && !stop.trips.some((trip) => trip.stop_sequence === note.stop_sequence)) {
251
- return false;
252
- }
253
- return note.stop_id === stop.stop_id;
254
- });
255
- }
256
- function getNotesForTrip(notes, trip) {
257
- return notes.filter((note) => {
258
- if (note.stop_id) {
259
- return false;
260
- }
261
- return note.trip_id === trip.trip_id;
262
- });
263
- }
264
- function getNotesForStoptime(notes, stoptime) {
265
- return notes.filter((note) => {
266
- if (!note.trip_id && note.stop_id === stoptime.stop_id && note.show_on_stoptime === 1) {
267
- return true;
268
- }
269
- if (!note.stop_id && note.trip_id === stoptime.trip_id && note.show_on_stoptime === 1) {
270
- return true;
271
- }
272
- return note.trip_id === stoptime.trip_id && note.stop_id === stoptime.stop_id;
273
- });
274
- }
275
- function formatTripName(trip, index, timetable) {
276
- let tripName;
277
- if (timetable.routes.length > 1) {
278
- tripName = trip.route_short_name;
279
- } else if (timetable.orientation === "horizontal") {
280
- if (trip.trip_short_name) {
281
- tripName = trip.trip_short_name;
282
- } else {
283
- tripName = `Run #${index + 1}`;
284
- }
285
- }
286
- if (timetableHasDifferentDays(timetable)) {
287
- tripName += ` ${trip.dayList}`;
288
- }
289
- return tripName;
290
- }
291
- function formatTripNameForCSV(trip, timetable) {
292
- let tripName = "";
293
- if (timetable.routes.length > 1) {
294
- tripName += `${trip.route_short_name} - `;
295
- }
296
- if (trip.trip_short_name) {
297
- tripName += trip.trip_short_name;
298
- } else {
299
- tripName += trip.trip_id;
300
- }
301
- if (trip.trip_headsign) {
302
- tripName += ` - ${trip.trip_headsign}`;
303
- }
304
- if (timetableHasDifferentDays(timetable)) {
305
- tripName += ` - ${trip.dayList}`;
306
- }
307
- return tripName;
308
- }
309
-
310
- // src/lib/errors.ts
311
- import { GtfsErrorCategory, isGtfsError } from "gtfs";
312
- var GtfsToHtmlError = class extends Error {
313
- code;
314
- category;
315
- isOperational;
316
- details;
317
- constructor(message, options) {
318
- super(message, { cause: options.cause });
319
- this.name = "GtfsToHtmlError";
320
- this.code = options.code;
321
- this.category = options.category;
322
- this.isOperational = options.isOperational ?? true;
323
- this.details = options.details;
324
- }
325
- };
326
- function isGtfsToHtmlError(error) {
327
- if (!error || typeof error !== "object") {
328
- return false;
329
- }
330
- const candidate = error;
331
- return candidate.name === "GtfsToHtmlError" && typeof candidate.message === "string" && typeof candidate.code === "string" && typeof candidate.category === "string" && typeof candidate.isOperational === "boolean";
332
- }
333
-
334
- // src/lib/file-utils.ts
335
- var homeDirectory = homedir();
336
- var localRequire = createRequire(import.meta.url);
337
- function getPathToThisModuleFolder() {
338
- try {
339
- return dirname(localRequire.resolve("gtfs-to-html/package.json"));
340
- } catch {
341
- const moduleDirectory = dirname(fileURLToPath(import.meta.url));
342
- if (moduleDirectory.endsWith("/dist/bin") || moduleDirectory.endsWith("/dist/app")) {
343
- return resolve(moduleDirectory, "../../");
344
- }
345
- if (moduleDirectory.endsWith("/dist")) {
346
- return resolve(moduleDirectory, "../");
347
- }
348
- return resolve(moduleDirectory, "../../");
349
- }
350
- }
351
- function getPathToViewsFolder(config2) {
352
- if (config2.templatePath) {
353
- return untildify(config2.templatePath);
354
- }
355
- return join(getPathToThisModuleFolder(), "views/default");
356
- }
357
- function getPathToTemplateFile(templateFileName, config2) {
358
- const fullTemplateFileName = config2.noHead !== true ? `${templateFileName}_full.pug` : `${templateFileName}.pug`;
359
- return join(getPathToViewsFolder(config2), fullTemplateFileName);
360
- }
361
- function generateTimetablePageFileName(timetablePage, config2) {
362
- if (timetablePage.filename) {
363
- return sanitize(timetablePage.filename);
364
- }
365
- if (config2.groupTimetablesIntoPages === true && uniqBy(timetablePage.timetables, "route_id").length === 1) {
366
- const route = timetablePage.timetables[0].routes[0];
367
- return sanitize(`${formatRouteNameForFilename(route).toLowerCase()}.html`);
368
- }
369
- const timetable = timetablePage.timetables[0];
370
- if (timetable.timetable_id) {
371
- return sanitize(
372
- `${timetable.timetable_id.replace(/\|/g, "_").toLowerCase()}.html`
373
- );
374
- }
375
- let filename = "";
376
- for (const route of timetable.routes) {
377
- filename += `_${formatRouteNameForFilename(route)}`;
378
- }
379
- if (!isNullOrEmpty(timetable.direction_id)) {
380
- filename += `_${timetable.direction_id}`;
381
- }
382
- filename += `_${formatDays(timetable, config2).replace(/\s/g, "")}.html`;
383
- return sanitize(filename.toLowerCase());
384
- }
385
- async function renderTemplate(templateFileName, templateVars, config2) {
386
- const templatePath = getPathToTemplateFile(templateFileName, config2);
387
- const html = await renderFile(templatePath, {
388
- _,
389
- cssEscape,
390
- md: (text) => sanitizeHtml(marked.parseInline(text)),
391
- ...template_functions_exports,
392
- formatRouteColor,
393
- formatRouteTextColor,
394
- ...templateVars
395
- });
396
- if (config2.beautify === true) {
397
- return beautify.html_beautify(html, {
398
- indent_size: 2
399
- });
400
- }
401
- return html;
402
- }
403
- function untildify(pathWithTilde) {
404
- return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
405
- }
406
-
407
- // src/lib/geojson-utils.ts
408
- import { getShapesAsGeoJSON, getStopsAsGeoJSON } from "gtfs";
409
- import simplify from "@turf/simplify";
410
- import { featureCollection, round } from "@turf/helpers";
411
-
412
- // src/lib/log-utils.ts
413
- import { clearLine, cursorTo } from "readline";
414
- import { noop } from "lodash-es";
415
- import * as colors from "yoctocolors";
416
- import { getAgencies, getFeedInfo, isGtfsError as isGtfsError2, formatGtfsError } from "gtfs";
417
- import Table from "cli-table";
418
- function logWarning(config2) {
419
- if (config2.logFunction) {
420
- return config2.logFunction;
421
- }
422
- return (text) => {
423
- process.stdout.write(`
424
- ${formatWarning(text)}
425
- `);
426
- };
427
- }
428
- function formatWarning(text) {
429
- const warningMessage = `${colors.underline("Warning")}: ${text}`;
430
- return colors.yellow(warningMessage);
431
- }
432
-
433
- // src/lib/trip-id-utils.ts
434
- var getBaseTripId = (tripId) => tripId.replace(/_freq_\d+$/, "");
435
- var getBaseTripIds = (trips) => Array.from(new Set(trips.map((trip) => getBaseTripId(trip.trip_id))));
436
-
437
- // src/lib/geojson-utils.ts
438
- var mergeGeojson = (...geojsons) => featureCollection(geojsons.flatMap((geojson) => geojson.features));
439
- var truncateGeoJSONDecimals = (geojson, config2) => {
440
- for (const feature of geojson.features) {
441
- if (feature.geometry.coordinates) {
442
- if (feature.geometry.type.toLowerCase() === "point") {
443
- feature.geometry.coordinates = feature.geometry.coordinates.map(
444
- (number) => round(number, config2.coordinatePrecision)
445
- );
446
- } else if (feature.geometry.type.toLowerCase() === "linestring") {
447
- feature.geometry.coordinates = feature.geometry.coordinates.map(
448
- (coordinate) => coordinate.map(
449
- (number) => round(number, config2.coordinatePrecision)
450
- )
451
- );
452
- } else if (feature.geometry.type.toLowerCase() === "multilinestring") {
453
- feature.geometry.coordinates = feature.geometry.coordinates.map(
454
- (linestring) => linestring.map(
455
- (coordinate) => coordinate.map(
456
- (number) => round(number, config2.coordinatePrecision)
457
- )
458
- )
459
- );
460
- }
461
- }
462
- }
463
- return geojson;
464
- };
465
- function getTimetableGeoJSON(timetable, config2) {
466
- const tripIds = getBaseTripIds(timetable.orderedTrips);
467
- const shapesGeojsons = timetable.route_ids.map(
468
- (routeId) => getShapesAsGeoJSON({
469
- route_id: routeId,
470
- direction_id: timetable.direction_id,
471
- trip_id: tripIds
472
- })
473
- );
474
- const stopsGeojsons = timetable.route_ids.map(
475
- (routeId) => getStopsAsGeoJSON({
476
- route_id: routeId,
477
- direction_id: timetable.direction_id,
478
- trip_id: tripIds
479
- })
480
- );
481
- const geojson = mergeGeojson(...shapesGeojsons, ...stopsGeojsons);
482
- let simplifiedGeojson;
483
- try {
484
- simplifiedGeojson = simplify(geojson, {
485
- tolerance: 1 / 10 ** config2.coordinatePrecision,
486
- highQuality: true
487
- });
488
- } catch {
489
- timetable.warnings.push(
490
- `Timetable ${timetable.timetable_id} - Unable to simplify geojson`
491
- );
492
- simplifiedGeojson = geojson;
493
- }
494
- return truncateGeoJSONDecimals(simplifiedGeojson, config2);
495
- }
496
- function getAgencyGeoJSON(config2) {
497
- const shapesGeojsons = getShapesAsGeoJSON();
498
- const stopsGeojsons = getStopsAsGeoJSON();
499
- const geojson = mergeGeojson(shapesGeojsons, stopsGeojsons);
500
- let simplifiedGeojson;
501
- try {
502
- simplifiedGeojson = simplify(geojson, {
503
- tolerance: 1 / 10 ** config2.coordinatePrecision,
504
- highQuality: true
505
- });
506
- } catch {
507
- logWarning(config2)("Unable to simplify geojson");
508
- simplifiedGeojson = geojson;
509
- }
510
- return truncateGeoJSONDecimals(simplifiedGeojson, config2);
511
- }
512
-
513
- // package.json
514
- var package_default = {
515
- name: "gtfs-to-html",
516
- version: "2.12.12",
517
- private: false,
518
- description: "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
519
- keywords: [
520
- "transit",
521
- "gtfs",
522
- "gtfs-realtime",
523
- "transportation",
524
- "timetables"
525
- ],
526
- homepage: "https://gtfstohtml.com",
527
- bugs: {
528
- url: "https://github.com/blinktaginc/gtfs-to-html/issues"
529
- },
530
- repository: "git://github.com/blinktaginc/gtfs-to-html",
531
- license: "MIT",
532
- author: "Brendan Nee <brendan@blinktag.com>",
533
- contributors: [
534
- "Evan Siroky <evan.siroky@yahoo.com>",
535
- "Nathan Selikoff",
536
- "Aaron Antrim <aaron@trilliumtransit.com>",
537
- "Thomas Craig <thomas@trilliumtransit.com>",
538
- "Holly Kvalheim",
539
- "Pawajoro",
540
- "Andrea Mignone",
541
- "Evo Stamatov",
542
- "Sebastian Knopf"
543
- ],
544
- type: "module",
545
- main: "./dist/index.js",
546
- types: "./dist/index.d.ts",
547
- files: [
548
- "dist",
549
- "docker",
550
- "examples",
551
- "scripts",
552
- "views/default",
553
- "config-sample.json"
554
- ],
555
- bin: {
556
- "gtfs-to-html": "dist/bin/gtfs-to-html.js"
557
- },
558
- scripts: {
559
- build: "tsup && node scripts/copy-browser-assets.js",
560
- start: "node ./dist/app",
561
- prepare: "husky && pnpm run build",
562
- prepack: "husky && pnpm run build"
563
- },
564
- dependencies: {
565
- "@turf/helpers": "^7.3.5",
566
- "@turf/simplify": "^7.3.5",
567
- archiver: "^8.0.0",
568
- "cli-table": "^0.3.11",
569
- "css.escape": "^1.5.1",
570
- "csv-stringify": "^6.7.0",
571
- express: "^5.2.1",
572
- gtfs: "^4.18.7",
573
- "js-beautify": "^1.15.4",
574
- "lodash-es": "^4.18.1",
575
- marked: "^18.0.4",
576
- moment: "^2.30.1",
577
- "pretty-error": "^4.0.0",
578
- pug: "^3.0.4",
579
- puppeteer: "^25.1.0",
580
- "sanitize-filename": "^1.6.4",
581
- "sanitize-html": "^2.17.4",
582
- sqlstring: "^2.3.3",
583
- toposort: "^2.0.2",
584
- yargs: "^18.0.0",
585
- yoctocolors: "^2.1.2"
586
- },
587
- devDependencies: {
588
- "@maplibre/maplibre-gl-geocoder": "^1.9.4",
589
- "@types/archiver": "^7.0.0",
590
- "@types/cli-table": "^0.3.4",
591
- "@types/express": "^5.0.6",
592
- "@types/js-beautify": "^1.14.3",
593
- "@types/lodash-es": "^4.17.12",
594
- "@types/node": "^25",
595
- "@types/pug": "^2.0.10",
596
- "@types/sanitize-html": "^2.16.1",
597
- "@types/sqlstring": "^2.3.2",
598
- "@types/toposort": "^2.0.7",
599
- "@types/yargs": "^17.0.35",
600
- anchorme: "^3.0.8",
601
- "gtfs-realtime-pbf-js-module": "^1.0.0",
602
- husky: "^9.1.7",
603
- "lint-staged": "^17.0.5",
604
- "maplibre-gl": "^5.24.0",
605
- pbf: "^5.0.0",
606
- prettier: "^3.8.3",
607
- tsup: "^8.5.1",
608
- typescript: "^6.0.3"
609
- },
610
- engines: {
611
- node: ">= 22"
612
- },
613
- packageManager: "pnpm@11.4.0",
614
- "release-it": {
615
- github: {
616
- release: true
617
- },
618
- plugins: {
619
- "@release-it/keep-a-changelog": {
620
- filename: "CHANGELOG.md"
621
- }
622
- },
623
- hooks: {
624
- "after:bump": "pnpm run build"
625
- }
626
- },
627
- prettier: {
628
- singleQuote: true
629
- },
630
- "lint-staged": {
631
- "*.{js,ts,json}": "prettier --write"
632
- }
633
- };
634
-
635
- // src/lib/utils.ts
636
- var { version } = package_default;
637
- var isTimepoint = (stoptime) => {
638
- if (isNullOrEmpty(stoptime.timepoint)) {
639
- return !isNullOrEmpty(stoptime.arrival_time) && !isNullOrEmpty(stoptime.departure_time);
640
- }
641
- return stoptime.timepoint === 1;
642
- };
643
- var getLongestTripStoptimes = (trips, config2) => {
644
- const filteredTripStoptimes = trips.map(
645
- (trip) => trip.stoptimes.filter((stoptime) => {
646
- if (config2.showOnlyTimepoint === true) {
647
- return isTimepoint(stoptime);
648
- }
649
- return true;
650
- })
651
- );
652
- return maxBy(filteredTripStoptimes, (stoptimes) => size(stoptimes));
653
- };
654
- var findCommonStopId = (trips, config2) => {
655
- const longestTripStoptimes = getLongestTripStoptimes(trips, config2);
656
- if (!longestTripStoptimes) {
657
- return null;
658
- }
659
- const commonStoptime = longestTripStoptimes.find((stoptime, idx) => {
660
- if (idx === 0 && stoptime.stop_id === last(longestTripStoptimes)?.stop_id) {
661
- return false;
662
- }
663
- if (isNullOrEmpty(stoptime.arrival_time)) {
664
- return false;
665
- }
666
- return every2(
667
- trips,
668
- (trip) => trip.stoptimes.find(
669
- (tripStoptime) => tripStoptime.stop_id === stoptime.stop_id && tripStoptime.arrival_time !== null
670
- )
671
- );
672
- });
673
- return commonStoptime ? commonStoptime.stop_id : null;
674
- };
675
- var deduplicateTrips = (trips) => {
676
- if (trips.length <= 1) {
677
- return trips;
678
- }
679
- const uniqueTrips = /* @__PURE__ */ new Map();
680
- for (const trip of trips) {
681
- const tripSignature = trip.stoptimes.map(
682
- (stoptime) => `${stoptime.stop_id}|${stoptime.departure_time}|${stoptime.arrival_time}`
683
- ).join("|");
684
- if (!uniqueTrips.has(tripSignature)) {
685
- uniqueTrips.set(tripSignature, trip);
686
- } else {
687
- const existingTrip = uniqueTrips.get(tripSignature);
688
- if (!existingTrip) {
689
- continue;
690
- }
691
- if (!existingTrip.additional_service_ids) {
692
- existingTrip.additional_service_ids = [];
693
- }
694
- existingTrip.additional_service_ids.push(trip.service_id);
695
- uniqueTrips.set(tripSignature, existingTrip);
696
- }
697
- }
698
- return Array.from(uniqueTrips.values());
699
- };
700
- var sortTrips = (trips, config2) => {
701
- let sortedTrips;
702
- let commonStopId;
703
- if (config2.sortingAlgorithm === "common") {
704
- commonStopId = findCommonStopId(trips, config2);
705
- if (commonStopId) {
706
- sortedTrips = sortTripsByStoptimeAtStop(trips, commonStopId);
707
- } else {
708
- sortedTrips = sortTrips(trips, {
709
- ...config2,
710
- sortingAlgorithm: "beginning"
711
- });
712
- }
713
- } else if (config2.sortingAlgorithm === "beginning") {
714
- for (const trip of trips) {
715
- if (trip.stoptimes.length === 0) {
716
- continue;
717
- }
718
- trip.firstStoptime = timeToSeconds(trip.stoptimes[0].departure_time);
719
- trip.lastStoptime = timeToSeconds(
720
- trip.stoptimes[trip.stoptimes.length - 1].departure_time
721
- );
722
- }
723
- sortedTrips = sortBy(trips, ["firstStoptime", "lastStoptime"]);
724
- } else if (config2.sortingAlgorithm === "end") {
725
- for (const trip of trips) {
726
- if (trip.stoptimes.length === 0) {
727
- continue;
728
- }
729
- trip.firstStoptime = timeToSeconds(trip.stoptimes[0].departure_time);
730
- trip.lastStoptime = timeToSeconds(
731
- trip.stoptimes[trip.stoptimes.length - 1].departure_time
732
- );
733
- }
734
- sortedTrips = sortBy(trips, ["lastStoptime", "firstStoptime"]);
735
- } else if (config2.sortingAlgorithm === "first") {
736
- const longestTripStoptimes = getLongestTripStoptimes(trips, config2);
737
- const firstStopId = first(longestTripStoptimes).stop_id;
738
- sortedTrips = sortTripsByStoptimeAtStop(trips, firstStopId);
739
- } else if (config2.sortingAlgorithm === "last") {
740
- const longestTripStoptimes = getLongestTripStoptimes(trips, config2);
741
- const lastStopId = last(longestTripStoptimes).stop_id;
742
- sortedTrips = sortTripsByStoptimeAtStop(trips, lastStopId);
743
- }
744
- return sortedTrips ?? [];
745
- };
746
- var sortTripsByStoptimeAtStop = (trips, stopId) => sortBy(trips, (trip) => {
747
- const stoptime = find(trip.stoptimes, { stop_id: stopId });
748
- return stoptime ? timeToSeconds(stoptime.departure_time) : void 0;
749
- });
750
- var getCalendarDatesForTimetable = (timetable, config2) => {
751
- const calendarDates = getCalendarDates(
752
- {
753
- service_id: timetable.service_ids
754
- },
755
- [],
756
- [["date", "ASC"]]
757
- );
758
- const start = moment2(timetable.start_date, "YYYYMMDD");
759
- const end = moment2(timetable.end_date, "YYYYMMDD");
760
- const excludedDates = /* @__PURE__ */ new Set();
761
- const includedDates = /* @__PURE__ */ new Set();
762
- for (const calendarDate of calendarDates) {
763
- if (moment2(calendarDate.date, "YYYYMMDD").isBetween(
764
- start,
765
- end,
766
- void 0,
767
- "[]"
768
- )) {
769
- if (calendarDate.exception_type === 1) {
770
- includedDates.add(formatDate(calendarDate, config2.dateFormat));
771
- } else if (calendarDate.exception_type === 2) {
772
- excludedDates.add(formatDate(calendarDate, config2.dateFormat));
773
- }
774
- }
775
- }
776
- const includedAndExcludedDates = new Set(
777
- [...excludedDates].filter((date) => includedDates.has(date))
778
- );
779
- return {
780
- excludedDates: [...excludedDates].filter(
781
- (date) => !includedAndExcludedDates.has(date)
782
- ),
783
- includedDates: [...includedDates].filter(
784
- (date) => !includedAndExcludedDates.has(date)
785
- )
786
- };
787
- };
788
- var getDaysFromCalendars = (calendars) => {
789
- const days2 = {
790
- monday: 0,
791
- tuesday: 0,
792
- wednesday: 0,
793
- thursday: 0,
794
- friday: 0,
795
- saturday: 0,
796
- sunday: 0
797
- };
798
- for (const calendar of calendars) {
799
- for (const day of Object.keys(days2)) {
800
- days2[day] = days2[day] | calendar[day];
801
- }
802
- }
803
- return days2;
804
- };
805
- var getDirectionHeadsignFromTimetable = (timetable) => {
806
- const trips = getTrips(
807
- {
808
- direction_id: timetable.direction_id,
809
- route_id: timetable.route_ids
810
- },
811
- ["trip_headsign"]
812
- );
813
- if (trips.length === 0) {
814
- return "";
815
- }
816
- const mostCommonHeadsign = flow(
817
- countBy,
818
- entries,
819
- partialRight(maxBy, last),
820
- head
821
- )(compact(trips.map((trip) => trip.trip_headsign)));
822
- return mostCommonHeadsign;
823
- };
824
- var getTimetableNotesForTimetable = (timetable, config2) => {
825
- const noteReferences = [
826
- // Get all notes for this timetable.
827
- ...getTimetableNotesReferences({
828
- timetable_id: timetable.timetable_id
829
- }),
830
- // Get all notes for this route.
831
- ...getTimetableNotesReferences({
832
- route_id: timetable.routes.map((route) => route.route_id),
833
- timetable_id: null
834
- }),
835
- // Get all notes for all trips in this timetable.
836
- ...getTimetableNotesReferences({
837
- trip_id: getBaseTripIds(timetable.orderedTrips)
838
- }),
839
- // Get all notes for all stops in this timetable.
840
- ...getTimetableNotesReferences({
841
- stop_id: timetable.stops.map((stop) => stop.stop_id),
842
- trip_id: null,
843
- route_id: null,
844
- timetable_id: null
845
- })
846
- ];
847
- const usedNoteReferences = [];
848
- for (const noteReference of noteReferences) {
849
- if (noteReference.stop_sequence === "" || noteReference.stop_sequence === null) {
850
- usedNoteReferences.push(noteReference);
851
- continue;
852
- }
853
- if (noteReference.stop_id === "" || noteReference.stop_id === null) {
854
- timetable.warnings.push(
855
- `Timetable Note Reference for note_id=${noteReference.note_id} has a \`stop_sequence\` but no \`stop_id\` - ignoring`
856
- );
857
- continue;
858
- }
859
- const stop = timetable.stops.find(
860
- (stop2) => stop2.stop_id === noteReference.stop_id
861
- );
862
- if (!stop) {
863
- continue;
864
- }
865
- const tripWithMatchingStopSequence = stop.trips.find(
866
- (trip) => trip.stop_sequence === noteReference.stop_sequence
867
- );
868
- if (tripWithMatchingStopSequence) {
869
- usedNoteReferences.push(noteReference);
870
- }
871
- }
872
- const notes = getTimetableNotes({
873
- note_id: usedNoteReferences.map((noteReference) => noteReference.note_id)
874
- });
875
- const symbols = "abcdefghijklmnopqrstuvwxyz".split("");
876
- let symbolIndex = 0;
877
- for (const note of notes) {
878
- if (note.symbol === "" || note.symbol === null) {
879
- note.symbol = symbolIndex < symbols.length - 1 ? symbols[symbolIndex] : symbolIndex - symbols.length;
880
- symbolIndex += 1;
881
- }
882
- }
883
- const formattedNotes = usedNoteReferences.map((noteReference) => ({
884
- ...noteReference,
885
- ...notes.find((note) => note.note_id === noteReference.note_id)
886
- }));
887
- return sortBy(formattedNotes, "symbol");
888
- };
889
- var createTimetablePage = ({
890
- timetablePageId,
891
- timetables,
892
- config: config2
893
- }) => {
894
- const updatedTimetables = timetables.map((timetable) => {
895
- if (!timetable.routes) {
896
- timetable.routes = getRoutes({
897
- route_id: timetable.route_ids
898
- });
899
- }
900
- return timetable;
901
- });
902
- const timetablePage = {
903
- timetable_page_id: timetablePageId,
904
- timetables: updatedTimetables,
905
- routes: updatedTimetables.flatMap((timetable) => timetable.routes)
906
- };
907
- const filename = generateTimetablePageFileName(timetablePage, config2);
908
- return {
909
- ...timetablePage,
910
- filename
911
- };
912
- };
913
- var createTimetable = ({
914
- route,
915
- directionId,
916
- tripHeadsign,
917
- calendars,
918
- calendarDates
919
- }) => {
920
- const serviceIds = uniq([
921
- ...calendars?.map((calendar) => calendar.service_id) ?? [],
922
- ...calendarDates?.map((calendarDate) => calendarDate.service_id) ?? []
923
- ]);
924
- const days2 = {
925
- monday: null,
926
- tuesday: null,
927
- wednesday: null,
928
- thursday: null,
929
- friday: null,
930
- saturday: null,
931
- sunday: null
932
- };
933
- let startDate = null;
934
- let endDate = null;
935
- if (calendars && calendars.length > 0) {
936
- Object.assign(days2, getDaysFromCalendars(calendars));
937
- startDate = parseInt(
938
- moment2.min(
939
- calendars.map((calendar) => moment2(calendar.start_date, "YYYYMMDD"))
940
- ).format("YYYYMMDD"),
941
- 10
942
- );
943
- endDate = parseInt(
944
- moment2.max(calendars.map((calendar) => moment2(calendar.end_date, "YYYYMMDD"))).format("YYYYMMDD"),
945
- 10
946
- );
947
- }
948
- const timetableId = formatTimetableId({
949
- routeIds: [route.route_id],
950
- directionId,
951
- days: days2,
952
- dates: calendarDates?.map((calendarDate) => calendarDate.date)
953
- });
954
- return {
955
- timetable_id: timetableId,
956
- route_ids: [route.route_id],
957
- direction_id: directionId === null ? null : directionId,
958
- direction_name: tripHeadsign === null ? null : tripHeadsign,
959
- routes: [route],
960
- include_exceptions: calendarDates && calendarDates.length > 0 ? 1 : 0,
961
- service_ids: serviceIds,
962
- service_notes: null,
963
- timetable_label: null,
964
- start_time: null,
965
- end_time: null,
966
- orientation: null,
967
- timetable_sequence: null,
968
- show_trip_continuation: null,
969
- start_date: startDate,
970
- end_date: endDate,
971
- ...days2
972
- };
973
- };
974
- var convertRoutesToTimetablePages = (config2) => {
975
- const routes = getRoutes();
976
- const timetablePages = [];
977
- const { calendars, calendarDates } = getCalendarsFromConfig(config2);
978
- for (const route of routes) {
979
- const trips = getTrips(
980
- {
981
- route_id: route.route_id
982
- },
983
- ["trip_headsign", "direction_id", "trip_id", "service_id"]
984
- );
985
- const uniqueTripDirections = orderBy(
986
- uniqBy2(trips, (trip) => trip.direction_id),
987
- "direction_id"
988
- );
989
- const sortedCalendars = orderBy(calendars, calendarToCalendarCode, "desc");
990
- const calendarGroups = groupBy(sortedCalendars, calendarToCalendarCode);
991
- const calendarDateGroups = groupBy(calendarDates, "service_id");
992
- const timetables = [];
993
- for (const uniqueTripDirection of uniqueTripDirections) {
994
- for (const calendars2 of Object.values(calendarGroups)) {
995
- const tripsForCalendars = trips.filter(
996
- (trip) => some(calendars2, { service_id: trip.service_id })
997
- );
998
- if (tripsForCalendars.length > 0) {
999
- timetables.push(
1000
- createTimetable({
1001
- route,
1002
- directionId: uniqueTripDirection.direction_id,
1003
- tripHeadsign: uniqueTripDirection.trip_headsign,
1004
- calendars: calendars2
1005
- })
1006
- );
1007
- }
1008
- }
1009
- for (const calendarDates2 of Object.values(calendarDateGroups)) {
1010
- const tripsForCalendarDates = trips.filter(
1011
- (trip) => some(calendarDates2, { service_id: trip.service_id })
1012
- );
1013
- if (tripsForCalendarDates.length > 0) {
1014
- timetables.push(
1015
- createTimetable({
1016
- route,
1017
- directionId: uniqueTripDirection.direction_id,
1018
- tripHeadsign: uniqueTripDirection.trip_headsign,
1019
- calendarDates: calendarDates2
1020
- })
1021
- );
1022
- }
1023
- }
1024
- }
1025
- if (timetables.length === 0) {
1026
- continue;
1027
- }
1028
- if (config2.groupTimetablesIntoPages === true) {
1029
- timetablePages.push(
1030
- createTimetablePage({
1031
- timetablePageId: `route_${route.route_id}`,
1032
- timetables,
1033
- config: config2
1034
- })
1035
- );
1036
- } else {
1037
- for (const timetable of timetables) {
1038
- timetablePages.push(
1039
- createTimetablePage({
1040
- timetablePageId: timetable.timetable_id,
1041
- timetables: [timetable],
1042
- config: config2
1043
- })
1044
- );
1045
- }
1046
- }
1047
- }
1048
- return timetablePages;
1049
- };
1050
- var generateTripsByFrequencies = (trip, frequencies, config2) => {
1051
- const formattedFrequencies = frequencies.map(
1052
- (frequency) => formatFrequency(frequency, config2)
1053
- );
1054
- const resetTrip = resetStoptimesToMidnight(trip);
1055
- const trips = [];
1056
- for (const frequency of formattedFrequencies) {
1057
- const startSeconds = secondsAfterMidnight(frequency.start_time);
1058
- const endSeconds = secondsAfterMidnight(frequency.end_time);
1059
- for (let offset = startSeconds; offset < endSeconds; offset += frequency.headway_secs) {
1060
- const newTrip = cloneDeep(resetTrip);
1061
- trips.push({
1062
- ...newTrip,
1063
- trip_id: `${resetTrip.trip_id}_freq_${trips.length}`,
1064
- stoptimes: updateStoptimesByOffset(newTrip, offset)
1065
- });
1066
- }
1067
- }
1068
- return trips;
1069
- };
1070
- var duplicateStopsForDifferentArrivalDeparture = (stopIds, timetable, config2) => {
1071
- if (config2.showArrivalOnDifference === null || config2.showArrivalOnDifference === void 0) {
1072
- return stopIds;
1073
- }
1074
- for (const trip of timetable.orderedTrips) {
1075
- for (const stoptime of trip.stoptimes) {
1076
- const timepointDifference = fromGTFSTime(stoptime.departure_time).diff(
1077
- fromGTFSTime(stoptime.arrival_time),
1078
- "minutes"
1079
- );
1080
- if (timepointDifference < config2.showArrivalOnDifference) {
1081
- continue;
1082
- }
1083
- const index = stopIds.indexOf(stoptime.stop_id);
1084
- if (index === 0 || index === stopIds.length - 1) {
1085
- continue;
1086
- }
1087
- if (stoptime.stop_id === stopIds[index + 1] || stoptime.stop_id === stopIds[index - 1]) {
1088
- continue;
1089
- }
1090
- stopIds.splice(index, 0, stoptime.stop_id);
1091
- }
1092
- }
1093
- return stopIds;
1094
- };
1095
- var getStopOrder = (timetable, config2) => {
1096
- const timetableStopOrders = getTimetableStopOrders(
1097
- {
1098
- timetable_id: timetable.timetable_id
1099
- },
1100
- ["stop_id"],
1101
- [["stop_sequence", "ASC"]]
1102
- );
1103
- if (timetableStopOrders.length > 0) {
1104
- return timetableStopOrders.map(
1105
- (timetableStopOrder) => timetableStopOrder.stop_id
1106
- );
1107
- }
1108
- try {
1109
- const stopGraph = [];
1110
- const timepointStopIds = new Set(
1111
- timetable.orderedTrips.flatMap(
1112
- (trip) => trip.stoptimes.filter((stoptime) => isTimepoint(stoptime)).map((stoptime) => stoptime.stop_id)
1113
- )
1114
- );
1115
- for (const trip of timetable.orderedTrips) {
1116
- const sortedStopIds = trip.stoptimes.filter((stoptime) => {
1117
- if (config2.showOnlyTimepoint === true) {
1118
- return timepointStopIds.has(stoptime.stop_id);
1119
- }
1120
- return true;
1121
- }).map((stoptime) => stoptime.stop_id);
1122
- for (const [index, stopId] of sortedStopIds.entries()) {
1123
- if (index === sortedStopIds.length - 1) {
1124
- continue;
1125
- }
1126
- stopGraph.push([stopId, sortedStopIds[index + 1]]);
1127
- }
1128
- }
1129
- if (stopGraph.length === 0 && config2.showOnlyTimepoint === true) {
1130
- timetable.warnings.push(
1131
- `Timetable ${timetable.timetable_id}'s trips have stoptimes with timepoints but \`showOnlyTimepoint\` is true. Try setting \`showOnlyTimepoint\` to false.`
1132
- );
1133
- }
1134
- const stopIds = toposort(stopGraph);
1135
- return duplicateStopsForDifferentArrivalDeparture(
1136
- stopIds,
1137
- timetable,
1138
- config2
1139
- );
1140
- } catch {
1141
- const longestTripStoptimes = getLongestTripStoptimes(
1142
- timetable.orderedTrips,
1143
- config2
1144
- );
1145
- const stopIds = longestTripStoptimes.map(
1146
- (stoptime) => stoptime.stop_id
1147
- );
1148
- const missingStopIds = difference(
1149
- new Set(
1150
- timetable.orderedTrips.flatMap(
1151
- (trip) => trip.stoptimes.map((stoptime) => stoptime.stop_id)
1152
- )
1153
- ),
1154
- new Set(stopIds)
1155
- );
1156
- if (missingStopIds.length > 0) {
1157
- timetable.warnings.push(
1158
- `Timetable ${timetable.timetable_id} stops are unable to be topologically sorted and has no \`timetable_stop_order.txt\`. Falling back to using the using the stop order from trip with most stoptimes, but this does not include stop_ids ${formatListForDisplay(missingStopIds)}. Try manually specifying stops with \`timetable_stop_order.txt\`. Read more at https://gtfstohtml.com/docs/timetable-stop-order`
1159
- );
1160
- }
1161
- return duplicateStopsForDifferentArrivalDeparture(
1162
- stopIds,
1163
- timetable,
1164
- config2
1165
- );
1166
- }
1167
- };
1168
- var getStopsForTimetable = (timetable, config2) => {
1169
- if (timetable.orderedTrips.length === 0) {
1170
- return [];
1171
- }
1172
- const orderedStopIds = getStopOrder(timetable, config2);
1173
- const orderedStops = orderedStopIds.map((stopId, index) => {
1174
- const stops = getStops({
1175
- stop_id: stopId
1176
- });
1177
- if (stops.length === 0) {
1178
- throw new GtfsToHtmlError(
1179
- `No stop found found for stop_id=${stopId} in timetable_id=${timetable.timetable_id}`,
1180
- {
1181
- code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
1182
- category: "query" /* QUERY */,
1183
- details: {
1184
- entity: "stop",
1185
- stopId,
1186
- timetableId: timetable.timetable_id
1187
- }
1188
- }
1189
- );
1190
- }
1191
- const stop = {
1192
- ...stops[0],
1193
- trips: []
1194
- };
1195
- if (index < orderedStopIds.length - 1 && stopId === orderedStopIds[index + 1]) {
1196
- stop.type = "arrival";
1197
- } else if (index > 0 && stopId === orderedStopIds[index - 1]) {
1198
- stop.type = "departure";
1199
- }
1200
- return stop;
1201
- });
1202
- if (config2.showStopCity) {
1203
- const stopAttributes = getStopAttributes({
1204
- stop_id: orderedStopIds
1205
- });
1206
- for (const stopAttribute of stopAttributes) {
1207
- const stop = orderedStops.find(
1208
- (stop2) => stop2.stop_id === stopAttribute.stop_id
1209
- );
1210
- if (stop) {
1211
- stop.stop_city = stopAttribute.stop_city;
1212
- }
1213
- }
1214
- }
1215
- return orderedStops;
1216
- };
1217
- var getCalendarsFromConfig = (config2) => {
1218
- const db = openDb();
1219
- let whereClause = "";
1220
- const whereClauses = [];
1221
- if (config2.endDate) {
1222
- if (!moment2(config2.endDate).isValid()) {
1223
- throw new GtfsToHtmlError(
1224
- `Invalid endDate=${config2.endDate} in config.json`,
1225
- {
1226
- code: "GTFS_TO_HTML_CONFIG_DATE_INVALID" /* CONFIG_DATE_INVALID */,
1227
- category: "config" /* CONFIG */,
1228
- details: { field: "endDate", value: config2.endDate }
1229
- }
1230
- );
1231
- }
1232
- whereClauses.push(
1233
- `start_date <= ${sqlString.escape(moment2(config2.endDate).format("YYYYMMDD"))}`
1234
- );
1235
- }
1236
- if (config2.startDate) {
1237
- if (!moment2(config2.startDate).isValid()) {
1238
- throw new GtfsToHtmlError(
1239
- `Invalid startDate=${config2.startDate} in config.json`,
1240
- {
1241
- code: "GTFS_TO_HTML_CONFIG_DATE_INVALID" /* CONFIG_DATE_INVALID */,
1242
- category: "config" /* CONFIG */,
1243
- details: { field: "startDate", value: config2.startDate }
1244
- }
1245
- );
1246
- }
1247
- whereClauses.push(
1248
- `end_date >= ${sqlString.escape(moment2(config2.startDate).format("YYYYMMDD"))}`
1249
- );
1250
- }
1251
- if (whereClauses.length > 0) {
1252
- whereClause = `WHERE ${whereClauses.join(" AND ")}`;
1253
- }
1254
- const calendars = db.prepare(`SELECT * FROM calendar ${whereClause}`).all();
1255
- const serviceIds = calendars.map((calendar) => calendar.service_id);
1256
- const calendarDatesQuery = serviceIds.length > 0 ? `SELECT * FROM calendar_dates WHERE exception_type = 1 AND service_id NOT IN (${serviceIds.map((serviceId) => sqlString.escape(serviceId)).join(", ")})` : "SELECT * FROM calendar_dates WHERE exception_type = 1";
1257
- const calendarDates = db.prepare(calendarDatesQuery).all();
1258
- return {
1259
- calendars,
1260
- calendarDates
1261
- };
1262
- };
1263
- var getCalendarsFromTimetable = (timetable) => {
1264
- const db = openDb();
1265
- let whereClause = "";
1266
- const whereClauses = [];
1267
- if (timetable.end_date) {
1268
- if (!moment2(timetable.end_date, "YYYYMMDD", true).isValid()) {
1269
- throw new GtfsToHtmlError(
1270
- `Invalid end_date=${timetable.end_date} for timetable_id=${timetable.timetable_id}`,
1271
- {
1272
- code: "GTFS_TO_HTML_QUERY_INVALID" /* QUERY_INVALID */,
1273
- category: "validation" /* VALIDATION */,
1274
- details: {
1275
- field: "end_date",
1276
- value: timetable.end_date,
1277
- timetableId: timetable.timetable_id
1278
- }
1279
- }
1280
- );
1281
- }
1282
- whereClauses.push(`start_date <= ${sqlString.escape(timetable.end_date)}`);
1283
- }
1284
- if (timetable.start_date) {
1285
- if (!moment2(timetable.start_date, "YYYYMMDD", true).isValid()) {
1286
- throw new GtfsToHtmlError(
1287
- `Invalid start_date=${timetable.start_date} for timetable_id=${timetable.timetable_id}`,
1288
- {
1289
- code: "GTFS_TO_HTML_QUERY_INVALID" /* QUERY_INVALID */,
1290
- category: "validation" /* VALIDATION */,
1291
- details: {
1292
- field: "start_date",
1293
- value: timetable.start_date,
1294
- timetableId: timetable.timetable_id
1295
- }
1296
- }
1297
- );
1298
- }
1299
- whereClauses.push(`end_date >= ${sqlString.escape(timetable.start_date)}`);
1300
- }
1301
- const days2 = getDaysFromCalendars([timetable]);
1302
- const dayQueries = reduce(
1303
- days2,
1304
- (memo, value, key) => {
1305
- if (value === 1) {
1306
- memo.push(`${key} = 1`);
1307
- }
1308
- return memo;
1309
- },
1310
- []
1311
- );
1312
- if (dayQueries.length > 0) {
1313
- whereClauses.push(`(${dayQueries.join(" OR ")})`);
1314
- }
1315
- if (whereClauses.length > 0) {
1316
- whereClause = `WHERE ${whereClauses.join(" AND ")}`;
1317
- }
1318
- return db.prepare(`SELECT * FROM calendar ${whereClause}`).all();
1319
- };
1320
- var getCalendarDatesForDateRange = (startDate, endDate) => {
1321
- const db = openDb();
1322
- const whereClauses = [];
1323
- if (endDate) {
1324
- whereClauses.push(`date <= ${sqlString.escape(endDate)}`);
1325
- }
1326
- if (startDate) {
1327
- whereClauses.push(`date >= ${sqlString.escape(startDate)}`);
1328
- }
1329
- const whereClause = whereClauses.length > 0 ? ` WHERE ${whereClauses.join(" AND ")}` : "";
1330
- const calendarDates = db.prepare(
1331
- `SELECT service_id, date, exception_type FROM calendar_dates${whereClause}`
1332
- ).all();
1333
- return calendarDates;
1334
- };
1335
- var getAllStationStopIds = (stopId) => {
1336
- const stops = getStops({
1337
- stop_id: stopId
1338
- });
1339
- if (stops.length === 0) {
1340
- throw new GtfsToHtmlError(`No stop found for stop_id=${stopId}`, {
1341
- code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
1342
- category: "query" /* QUERY */,
1343
- details: { entity: "stop", stopId }
1344
- });
1345
- }
1346
- const stop = stops[0];
1347
- if (isNullOrEmpty(stop.parent_station)) {
1348
- return [stopId];
1349
- }
1350
- const stopsInParentStation = getStops(
1351
- {
1352
- parent_station: stop.parent_station
1353
- },
1354
- ["stop_id"]
1355
- );
1356
- return [
1357
- stop.parent_station,
1358
- ...stopsInParentStation.map((stop2) => stop2.stop_id)
1359
- ];
1360
- };
1361
- var getTripsWithSameBlock = (trip, timetable) => {
1362
- const trips = getTrips(
1363
- {
1364
- block_id: trip.block_id,
1365
- service_id: timetable.service_ids
1366
- },
1367
- ["trip_id", "route_id"]
1368
- );
1369
- for (const blockTrip of trips) {
1370
- const stopTimes = getStoptimes(
1371
- {
1372
- trip_id: blockTrip.trip_id
1373
- },
1374
- [],
1375
- [["stop_sequence", "ASC"]]
1376
- );
1377
- if (stopTimes.length === 0) {
1378
- throw new GtfsToHtmlError(
1379
- `No stoptimes found found for trip_id=${blockTrip.trip_id}`,
1380
- {
1381
- code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
1382
- category: "query" /* QUERY */,
1383
- details: { entity: "stoptime", tripId: blockTrip.trip_id }
1384
- }
1385
- );
1386
- }
1387
- blockTrip.firstStoptime = first(stopTimes);
1388
- blockTrip.lastStoptime = last(stopTimes);
1389
- }
1390
- return sortBy(trips, (trip2) => trip2.firstStoptime.departure_timestamp);
1391
- };
1392
- var addTripContinuation = (trip, timetable) => {
1393
- if (!trip.block_id || trip.stoptimes.length === 0) {
1394
- return;
1395
- }
1396
- const maxContinuesAsWaitingTimeSeconds = 60 * 60;
1397
- const firstStoptime = first(trip.stoptimes);
1398
- const firstStopIds = getAllStationStopIds(firstStoptime.stop_id);
1399
- const lastStoptime = last(trip.stoptimes);
1400
- const lastStopIds = getAllStationStopIds(lastStoptime.stop_id);
1401
- const blockTrips = getTripsWithSameBlock(trip, timetable);
1402
- const previousTrip = findLast(
1403
- blockTrips,
1404
- (blockTrip) => blockTrip.lastStoptime.arrival_timestamp <= firstStoptime.departure_timestamp
1405
- );
1406
- if (previousTrip && previousTrip.route_id !== trip.route_id && previousTrip.lastStoptime.arrival_timestamp >= firstStoptime.departure_timestamp - maxContinuesAsWaitingTimeSeconds && firstStopIds.includes(previousTrip.lastStoptime.stop_id)) {
1407
- const routes = getRoutes({
1408
- route_id: previousTrip.route_id
1409
- });
1410
- previousTrip.route = routes[0];
1411
- trip.continues_from_route = previousTrip;
1412
- }
1413
- const nextTrip = find(
1414
- blockTrips,
1415
- (blockTrip) => blockTrip.firstStoptime.departure_timestamp >= lastStoptime.arrival_timestamp
1416
- );
1417
- if (nextTrip && nextTrip.route_id !== trip.route_id && nextTrip.firstStoptime.departure_timestamp <= lastStoptime.arrival_timestamp + maxContinuesAsWaitingTimeSeconds && lastStopIds.includes(nextTrip.firstStoptime.stop_id)) {
1418
- const routes = getRoutes({
1419
- route_id: nextTrip.route_id
1420
- });
1421
- nextTrip.route = routes[0];
1422
- trip.continues_as_route = nextTrip;
1423
- }
1424
- };
1425
- var filterTrips = (timetable, calendars, config2) => {
1426
- let filteredTrips = timetable.orderedTrips;
1427
- for (const trip of filteredTrips) {
1428
- const combinedStoptimes = [];
1429
- for (const [index, stoptime] of trip.stoptimes.entries()) {
1430
- if (index === 0 || stoptime.stop_id !== trip.stoptimes[index - 1].stop_id) {
1431
- combinedStoptimes.push(stoptime);
1432
- } else {
1433
- combinedStoptimes[combinedStoptimes.length - 1].departure_time = stoptime.departure_time;
1434
- }
1435
- }
1436
- trip.stoptimes = combinedStoptimes;
1437
- }
1438
- const timetableStopIds = new Set(
1439
- timetable.stops.map((stop) => stop.stop_id)
1440
- );
1441
- for (const trip of filteredTrips) {
1442
- trip.stoptimes = trip.stoptimes.filter(
1443
- (stoptime) => timetableStopIds.has(stoptime.stop_id)
1444
- );
1445
- }
1446
- filteredTrips = filteredTrips.filter(
1447
- (trip) => trip.stoptimes.length > 1
1448
- );
1449
- if (config2.showDuplicateTrips === false) {
1450
- filteredTrips = deduplicateTrips(filteredTrips);
1451
- }
1452
- const dayNames = [
1453
- "monday",
1454
- "tuesday",
1455
- "wednesday",
1456
- "thursday",
1457
- "friday",
1458
- "saturday",
1459
- "sunday"
1460
- ];
1461
- const timetableDays = dayNames.filter((day) => timetable[day] === 1);
1462
- if (timetableDays.length > 1) {
1463
- const warnedServiceIds = /* @__PURE__ */ new Set();
1464
- for (const trip of filteredTrips) {
1465
- const tripServiceIds = [
1466
- trip.service_id,
1467
- ...trip.additional_service_ids ?? []
1468
- ];
1469
- const tripCalendars = calendars.filter(
1470
- (c) => tripServiceIds.includes(c.service_id)
1471
- );
1472
- if (tripCalendars.length === 0) {
1473
- continue;
1474
- }
1475
- const tripDays = getDaysFromCalendars(tripCalendars);
1476
- const missingDays = timetableDays.filter(
1477
- (day) => (tripDays[day] ?? 0) !== 1
1478
- );
1479
- if (missingDays.length > 0) {
1480
- const serviceIdKey = tripServiceIds.sort().join("|");
1481
- if (!warnedServiceIds.has(serviceIdKey)) {
1482
- warnedServiceIds.add(serviceIdKey);
1483
- const tripDayList = formatDays(tripDays, config2);
1484
- const timetableDayList = formatDays(timetable, config2);
1485
- timetable.warnings.push(
1486
- `Timetable ${timetable.timetable_id} (Routes: ${timetable.routes.map((route) => route.route_short_name).join(", ")}) covers ${timetableDayList} but some trips (service_id=${tripServiceIds.join(", ")}) only run on ${tripDayList}. This may indicate a data issue in the GTFS or that you should generate separate timetables for different days of the week.`
1487
- );
1488
- }
1489
- }
1490
- }
1491
- }
1492
- const formattedTrips = filteredTrips.map((trip) => {
1493
- const tripCalendars = calendars.filter((calendar) => {
1494
- return [
1495
- trip.service_id,
1496
- ...trip.additional_service_ids || []
1497
- ].includes(calendar.service_id);
1498
- }) ?? [];
1499
- trip.dayList = formatDays(combineCalendars(tripCalendars), config2);
1500
- trip.dayListLong = formatDaysLong(trip.dayList, config2);
1501
- if (timetable.routes.length === 1) {
1502
- trip.route_short_name = timetable.routes[0].route_short_name;
1503
- } else {
1504
- const route = timetable.routes.find(
1505
- (route2) => route2.route_id === trip.route_id
1506
- );
1507
- trip.route_short_name = route?.route_short_name;
1508
- }
1509
- return trip;
1510
- });
1511
- return formattedTrips;
1512
- };
1513
- var getTripsForTimetable = (timetable, calendars, config2) => {
1514
- const tripQuery = {
1515
- route_id: timetable.route_ids,
1516
- service_id: timetable.service_ids
1517
- };
1518
- if (!isNullOrEmpty(timetable.direction_id)) {
1519
- tripQuery.direction_id = timetable.direction_id;
1520
- }
1521
- const trips = getTrips(tripQuery);
1522
- if (trips.length === 0) {
1523
- timetable.warnings.push(
1524
- `No trips found for route_id=${timetable.route_ids.join(
1525
- "_"
1526
- )}, direction_id=${timetable.direction_id}, service_ids=${JSON.stringify(
1527
- timetable.service_ids
1528
- )}, timetable_id=${timetable.timetable_id}`
1529
- );
1530
- }
1531
- const frequencies = getFrequencies({
1532
- trip_id: trips.map((trip) => trip.trip_id)
1533
- });
1534
- timetable.service_ids = uniq(trips.map((trip) => trip.service_id));
1535
- const formattedTrips = [];
1536
- for (const trip of trips) {
1537
- const formattedTrip = trip;
1538
- formattedTrip.stoptimes = getStoptimes(
1539
- {
1540
- trip_id: formattedTrip.trip_id
1541
- },
1542
- [],
1543
- [["stop_sequence", "ASC"]]
1544
- );
1545
- if (formattedTrip.stoptimes.length === 0) {
1546
- timetable.warnings.push(
1547
- `No stoptimes found for trip_id=${formattedTrip.trip_id}, route_id=${timetable.route_ids.join("_")}, timetable_id=${timetable.timetable_id}`
1548
- );
1549
- }
1550
- if (timetable.start_timestamp !== "" && timetable.start_timestamp !== null && timetable.start_timestamp !== void 0 && trip.stoptimes[0].arrival_timestamp < timetable.start_timestamp) {
1551
- return;
1552
- }
1553
- if (timetable.end_timestamp !== "" && timetable.end_timestamp !== null && timetable.end_timestamp !== void 0 && trip.stoptimes[0].arrival_timestamp >= timetable.end_timestamp) {
1554
- return;
1555
- }
1556
- if (timetable.show_trip_continuation) {
1557
- addTripContinuation(formattedTrip, timetable);
1558
- if (formattedTrip.continues_as_route) {
1559
- timetable.has_continues_as_route = true;
1560
- }
1561
- if (formattedTrip.continues_from_route) {
1562
- timetable.has_continues_from_route = true;
1563
- }
1564
- }
1565
- const tripFrequencies = frequencies.filter(
1566
- (frequency) => frequency.trip_id === trip.trip_id
1567
- );
1568
- if (tripFrequencies.length === 0) {
1569
- formattedTrips.push(formattedTrip);
1570
- } else {
1571
- const frequencyTrips = generateTripsByFrequencies(
1572
- formattedTrip,
1573
- frequencies,
1574
- config2
1575
- );
1576
- formattedTrips.push(...frequencyTrips);
1577
- timetable.frequencies = frequencies;
1578
- timetable.frequencyExactTimes = some(frequencies, {
1579
- exact_times: 1
1580
- });
1581
- }
1582
- }
1583
- if (config2.useParentStation) {
1584
- const stopIds = [];
1585
- for (const trip of formattedTrips) {
1586
- for (const stoptime of trip.stoptimes) {
1587
- stopIds.push(stoptime.stop_id);
1588
- }
1589
- }
1590
- const stops = getStops(
1591
- {
1592
- stop_id: uniq(stopIds)
1593
- },
1594
- ["parent_station", "stop_id"]
1595
- );
1596
- for (const trip of formattedTrips) {
1597
- for (const stoptime of trip.stoptimes) {
1598
- const stop = stops.find((stop2) => stop2.stop_id === stoptime.stop_id);
1599
- if (stop?.parent_station) {
1600
- stoptime.stop_id = stop.parent_station;
1601
- }
1602
- }
1603
- }
1604
- }
1605
- return sortTrips(formattedTrips, config2);
1606
- };
1607
- var formatTimetables = (timetables, config2) => {
1608
- const formattedTimetables = timetables.map((timetable) => {
1609
- timetable.warnings = [];
1610
- const dayList = formatDays(timetable, config2);
1611
- const calendars = getCalendarsFromTimetable(timetable);
1612
- const serviceIds = /* @__PURE__ */ new Set();
1613
- for (const calendar of calendars) {
1614
- serviceIds.add(calendar.service_id);
1615
- }
1616
- if (timetable.include_exceptions === 1) {
1617
- const calendarDates = getCalendarDatesForDateRange(
1618
- timetable.start_date,
1619
- timetable.end_date
1620
- );
1621
- const calendarDateGroups = groupBy(calendarDates, "service_id");
1622
- for (const [serviceId, calendarDateGroup] of Object.entries(
1623
- calendarDateGroups
1624
- )) {
1625
- const calendar = calendars.find(
1626
- (c) => c.service_id === serviceId
1627
- );
1628
- if (calendarDateGroup.some(
1629
- (calendarDate) => calendarDate.exception_type === 1
1630
- )) {
1631
- serviceIds.add(serviceId);
1632
- }
1633
- const calendarDateGroupExceptionType2 = calendarDateGroup.filter(
1634
- (calendarDate) => calendarDate.exception_type === 2
1635
- );
1636
- if (timetable.start_date && timetable.end_date && calendar && calendarDateGroupExceptionType2.length > 0) {
1637
- const datesDuringDateRange = calendarToDateList(
1638
- calendar,
1639
- timetable.start_date,
1640
- timetable.end_date
1641
- );
1642
- if (datesDuringDateRange.length === 0) {
1643
- serviceIds.delete(serviceId);
1644
- }
1645
- const everyDateIsExcluded = datesDuringDateRange.every(
1646
- (dateDuringDateRange) => calendarDateGroupExceptionType2.some(
1647
- (calendarDate) => calendarDate.date === dateDuringDateRange
1648
- )
1649
- );
1650
- if (everyDateIsExcluded) {
1651
- serviceIds.delete(serviceId);
1652
- }
1653
- }
1654
- }
1655
- }
1656
- Object.assign(timetable, {
1657
- noServiceSymbolUsed: false,
1658
- requestDropoffSymbolUsed: false,
1659
- noDropoffSymbolUsed: false,
1660
- requestPickupSymbolUsed: false,
1661
- noPickupSymbolUsed: false,
1662
- interpolatedStopSymbolUsed: false,
1663
- showStopCity: config2.showStopCity,
1664
- showStopDescription: config2.showStopDescription,
1665
- noServiceSymbol: config2.noServiceSymbol,
1666
- requestDropoffSymbol: config2.requestDropoffSymbol,
1667
- noDropoffSymbol: config2.noDropoffSymbol,
1668
- requestPickupSymbol: config2.requestPickupSymbol,
1669
- noPickupSymbol: config2.noPickupSymbol,
1670
- interpolatedStopSymbol: config2.interpolatedStopSymbol,
1671
- orientation: timetable.orientation || config2.defaultOrientation,
1672
- service_ids: Array.from(serviceIds),
1673
- dayList,
1674
- dayListLong: formatDaysLong(dayList, config2)
1675
- });
1676
- timetable.orderedTrips = getTripsForTimetable(timetable, calendars, config2);
1677
- timetable.stops = getStopsForTimetable(timetable, config2);
1678
- timetable.calendarDates = getCalendarDatesForTimetable(timetable, config2);
1679
- timetable.timetable_label = formatTimetableLabel(timetable);
1680
- timetable.notes = getTimetableNotesForTimetable(timetable, config2);
1681
- if (config2.showMap) {
1682
- timetable.geojson = getTimetableGeoJSON(timetable, config2);
1683
- }
1684
- timetable.trip_ids = uniq(getBaseTripIds(timetable.orderedTrips));
1685
- timetable.orderedTrips = filterTrips(timetable, calendars, config2);
1686
- timetable.stops = formatStops(timetable, config2);
1687
- return timetable;
1688
- });
1689
- if (config2.allowEmptyTimetables) {
1690
- return formattedTimetables;
1691
- }
1692
- return formattedTimetables.filter(
1693
- (timetable) => timetable.orderedTrips.length > 0
1694
- );
1695
- };
1696
- function getTimetablePagesForAgency(config2) {
1697
- const timetables = mergeTimetablesWithSameId(getTimetables());
1698
- const routes = getRoutes();
1699
- const formattedTimetables = timetables.map((timetable) => {
1700
- return {
1701
- ...timetable,
1702
- routes: routes.filter(
1703
- (route) => timetable.route_ids.includes(route.route_id)
1704
- )
1705
- };
1706
- });
1707
- if (timetables.length === 0) {
1708
- return convertRoutesToTimetablePages(config2);
1709
- }
1710
- const timetablePages = getTimetablePages(
1711
- {},
1712
- [],
1713
- [["timetable_page_id", "ASC"]]
1714
- );
1715
- if (timetablePages.length === 0) {
1716
- return formattedTimetables.map(
1717
- (timetable) => createTimetablePage({
1718
- timetablePageId: timetable.timetable_id,
1719
- timetables: [timetable],
1720
- config: config2
1721
- })
1722
- );
1723
- }
1724
- return timetablePages.map((timetablePage) => {
1725
- return {
1726
- ...timetablePage,
1727
- timetables: sortBy(
1728
- formattedTimetables.filter(
1729
- (timetable) => timetable.timetable_page_id === timetablePage.timetable_page_id
1730
- ),
1731
- "timetable_sequence"
1732
- )
1733
- };
1734
- });
1735
- }
1736
- var getDataForTimetablePageById = (timetablePageId) => {
1737
- let calendarCode;
1738
- let calendars;
1739
- let calendarDates;
1740
- let serviceId;
1741
- let directionId = "";
1742
- const parts = timetablePageId?.split("|") ?? [];
1743
- if (parts.length > 2) {
1744
- directionId = Number.parseInt(parts.pop(), 10);
1745
- calendarCode = parts.pop();
1746
- } else if (parts.length > 1) {
1747
- directionId = null;
1748
- calendarCode = parts.pop();
1749
- }
1750
- const routeId = parts.join("|");
1751
- const routes = getRoutes({
1752
- route_id: routeId
1753
- });
1754
- const trips = getTrips(
1755
- {
1756
- route_id: routeId,
1757
- direction_id: directionId
1758
- },
1759
- ["trip_headsign", "direction_id"]
1760
- );
1761
- const uniqueTripDirections = uniqBy2(trips, (trip) => trip.direction_id);
1762
- if (uniqueTripDirections.length === 0) {
1763
- throw new GtfsToHtmlError(
1764
- `No trips found for timetable_page_id=${timetablePageId} route_id=${routeId} direction_id=${directionId}`,
1765
- {
1766
- code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
1767
- category: "query" /* QUERY */,
1768
- details: {
1769
- entity: "trip",
1770
- timetablePageId,
1771
- routeId,
1772
- directionId
1773
- }
1774
- }
1775
- );
1776
- }
1777
- if (/^[01]*$/.test(calendarCode ?? "")) {
1778
- calendars = getCalendars({
1779
- ...calendarCodeToCalendar(calendarCode)
1780
- });
1781
- } else {
1782
- serviceId = calendarCode;
1783
- calendarDates = getCalendarDates({
1784
- exception_type: 1,
1785
- service_id: serviceId
1786
- });
1787
- }
1788
- return {
1789
- calendars,
1790
- calendarDates,
1791
- route: routes[0],
1792
- directionId: uniqueTripDirections[0].direction_id,
1793
- tripHeadsign: uniqueTripDirections[0].trip_headsign
1794
- };
1795
- };
1796
- var getTimetablePageById = (timetablePageId, config2) => {
1797
- const timetablePages = getTimetablePages({
1798
- timetable_page_id: timetablePageId
1799
- });
1800
- const timetables = mergeTimetablesWithSameId(
1801
- getTimetables()
1802
- );
1803
- if (timetablePages.length > 1) {
1804
- throw new GtfsToHtmlError(
1805
- `Multiple timetable_pages found for timetable_page_id=${timetablePageId}`,
1806
- {
1807
- code: "GTFS_TO_HTML_QUERY_RESULT_AMBIGUOUS" /* QUERY_RESULT_AMBIGUOUS */,
1808
- category: "query" /* QUERY */,
1809
- details: { entity: "timetable_page", timetablePageId }
1810
- }
1811
- );
1812
- }
1813
- if (timetablePages.length === 1) {
1814
- const timetablePage = timetablePages[0];
1815
- timetablePage.timetables = sortBy(
1816
- timetables.filter(
1817
- (timetable2) => timetable2.timetable_page_id === timetablePageId
1818
- ),
1819
- "timetable_sequence"
1820
- );
1821
- for (const timetable2 of timetablePage.timetables) {
1822
- timetable2.routes = getRoutes({
1823
- route_id: timetable2.route_ids
1824
- });
1825
- }
1826
- return timetablePage;
1827
- }
1828
- if (timetables.length > 0) {
1829
- const timetablePageTimetables = timetables.filter(
1830
- (timetable2) => timetable2.timetable_id === timetablePageId
1831
- );
1832
- if (timetablePageTimetables.length === 0) {
1833
- throw new GtfsToHtmlError(
1834
- `No timetable found for timetable_page_id=${timetablePageId}`,
1835
- {
1836
- code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
1837
- category: "query" /* QUERY */,
1838
- details: { entity: "timetable", timetablePageId }
1839
- }
1840
- );
1841
- }
1842
- return createTimetablePage({
1843
- timetablePageId,
1844
- timetables: [timetablePageTimetables[0]],
1845
- config: config2
1846
- });
1847
- }
1848
- if (timetablePageId.startsWith("route_")) {
1849
- const routes = getRoutes({
1850
- route_id: timetablePageId.slice("route_".length)
1851
- });
1852
- if (routes.length === 0) {
1853
- throw new GtfsToHtmlError(
1854
- `No route found for timetable_page_id=${timetablePageId}`,
1855
- {
1856
- code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
1857
- category: "query" /* QUERY */,
1858
- details: { entity: "route", timetablePageId }
1859
- }
1860
- );
1861
- }
1862
- const { calendars: calendars2, calendarDates: calendarDates2 } = getCalendarsFromConfig(config2);
1863
- const trips = getTrips(
1864
- {
1865
- route_id: routes[0].route_id
1866
- },
1867
- ["trip_headsign", "direction_id", "trip_id", "service_id"]
1868
- );
1869
- const uniqueTripDirections = orderBy(
1870
- uniqBy2(trips, (trip) => trip.direction_id),
1871
- "direction_id"
1872
- );
1873
- const sortedCalendars = orderBy(calendars2, calendarToCalendarCode, "desc");
1874
- const calendarGroups = groupBy(sortedCalendars, calendarToCalendarCode);
1875
- const calendarDateGroups = groupBy(calendarDates2, "service_id");
1876
- const timetables2 = [];
1877
- for (const uniqueTripDirection of uniqueTripDirections) {
1878
- for (const calendars3 of Object.values(calendarGroups)) {
1879
- const tripsForCalendars = trips.filter(
1880
- (trip) => some(calendars3, { service_id: trip.service_id })
1881
- );
1882
- if (tripsForCalendars.length > 0) {
1883
- timetables2.push(
1884
- createTimetable({
1885
- route: routes[0],
1886
- directionId: uniqueTripDirection.direction_id,
1887
- tripHeadsign: uniqueTripDirection.trip_headsign,
1888
- calendars: calendars3
1889
- })
1890
- );
1891
- }
1892
- }
1893
- for (const calendarDates3 of Object.values(calendarDateGroups)) {
1894
- const tripsForCalendarDates = trips.filter(
1895
- (trip) => some(calendarDates3, { service_id: trip.service_id })
1896
- );
1897
- if (tripsForCalendarDates.length > 0) {
1898
- timetables2.push(
1899
- createTimetable({
1900
- route: routes[0],
1901
- directionId: uniqueTripDirection.direction_id,
1902
- tripHeadsign: uniqueTripDirection.trip_headsign,
1903
- calendarDates: calendarDates3
1904
- })
1905
- );
1906
- }
1907
- }
1908
- }
1909
- return createTimetablePage({
1910
- timetablePageId,
1911
- timetables: timetables2,
1912
- config: config2
1913
- });
1914
- }
1915
- const { calendars, calendarDates, route, directionId, tripHeadsign } = getDataForTimetablePageById(timetablePageId);
1916
- const timetable = createTimetable({
1917
- route,
1918
- directionId,
1919
- tripHeadsign,
1920
- calendars,
1921
- calendarDates
1922
- });
1923
- return createTimetablePage({
1924
- timetablePageId,
1925
- timetables: [timetable],
1926
- config: config2
1927
- });
1928
- };
1929
- function setDefaultConfig(initialConfig) {
1930
- const defaults = {
1931
- allowEmptyTimetables: false,
1932
- beautify: false,
1933
- coordinatePrecision: 5,
1934
- dateFormat: "MMM D, YYYY",
1935
- daysShortStrings: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
1936
- daysStrings: [
1937
- "Monday",
1938
- "Tuesday",
1939
- "Wednesday",
1940
- "Thursday",
1941
- "Friday",
1942
- "Saturday",
1943
- "Sunday"
1944
- ],
1945
- defaultOrientation: "vertical",
1946
- interpolatedStopSymbol: "\u2022",
1947
- interpolatedStopText: "Estimated time of arrival",
1948
- groupTimetablesIntoPages: true,
1949
- gtfsToHtmlVersion: version,
1950
- linkStopUrls: false,
1951
- mapStyleUrl: "https://tiles.openfreemap.org/styles/positron",
1952
- menuType: "jump",
1953
- noDropoffSymbol: "\u2021",
1954
- noDropoffText: "No drop off available",
1955
- noHead: false,
1956
- noPickupSymbol: "**",
1957
- noPickupText: "No pickup available",
1958
- noRegularServiceDaysText: "No regular service days",
1959
- noServiceSymbol: "-",
1960
- noServiceText: "No service at this stop",
1961
- outputFormat: "html",
1962
- overwriteExistingFiles: true,
1963
- requestDropoffSymbol: "\u2020",
1964
- requestDropoffText: "Must request drop off",
1965
- requestPickupSymbol: "***",
1966
- requestPickupText: "Request stop - call for pickup",
1967
- serviceNotProvidedOnText: "Service not provided on",
1968
- serviceProvidedOnText: "Service provided on",
1969
- showArrivalOnDifference: 0.2,
1970
- showCalendarExceptions: true,
1971
- showDuplicateTrips: false,
1972
- showMap: false,
1973
- showOnlyTimepoint: false,
1974
- showRouteTitle: true,
1975
- showStopCity: false,
1976
- showStopDescription: false,
1977
- showStoptimesForRequestStops: true,
1978
- skipImport: false,
1979
- sortingAlgorithm: "common",
1980
- timeFormat: "h:mma",
1981
- useParentStation: true,
1982
- verbose: true,
1983
- zipOutput: false
1984
- };
1985
- const config2 = Object.assign(defaults, initialConfig);
1986
- if (config2.outputFormat === "pdf") {
1987
- config2.noHead = false;
1988
- config2.menuType = "none";
1989
- }
1990
- config2.hasGtfsRealtimeVehiclePositions = config2.agencies.some(
1991
- (agency) => agency.realtimeVehiclePositions?.url
1992
- );
1993
- config2.hasGtfsRealtimeTripUpdates = config2.agencies.some(
1994
- (agency) => agency.realtimeTripUpdates?.url
1995
- );
1996
- config2.hasGtfsRealtimeAlerts = config2.agencies.some(
1997
- (agency) => agency.realtimeAlerts?.url
1998
- );
1999
- return config2;
2000
- }
2001
- function getFormattedTimetablePage(timetablePageId, config2) {
2002
- const timetablePage = getTimetablePageById(
2003
- timetablePageId,
2004
- config2
2005
- );
2006
- const consolidatedTimetables = formatTimetables(
2007
- timetablePage.timetables,
2008
- config2
2009
- );
2010
- for (const timetable of consolidatedTimetables) {
2011
- if (isNullOrEmpty(timetable.direction_name)) {
2012
- timetable.direction_name = getDirectionHeadsignFromTimetable(timetable);
2013
- }
2014
- if (!timetable.routes) {
2015
- timetable.routes = getRoutes({
2016
- route_id: timetable.route_ids
2017
- });
2018
- }
2019
- }
2020
- const uniqueRoutes = uniqBy2(
2021
- flatMap(consolidatedTimetables, (timetable) => timetable.routes),
2022
- "route_id"
2023
- );
2024
- const formattedTimetablePage = {
2025
- ...timetablePage,
2026
- consolidatedTimetables,
2027
- dayList: formatDays(getDaysFromCalendars(consolidatedTimetables), config2),
2028
- dayLists: uniq(
2029
- consolidatedTimetables.map((timetable) => timetable.dayList)
2030
- ),
2031
- route_ids: uniqueRoutes.map((route) => route.route_id),
2032
- agency_ids: uniq(compact(uniqueRoutes.map((route) => route.agency_id))),
2033
- filename: timetablePage.filename ?? `${timetablePage.timetable_page_id}.html`,
2034
- timetable_page_label: timetablePage.timetable_page_label ?? formatListForDisplay(uniqueRoutes.map((route) => formatRouteName(route)))
2035
- };
2036
- return formattedTimetablePage;
2037
- }
2038
- function generateTimetableHTML(timetablePage, config2) {
2039
- const agencies = getAgencies2();
2040
- const templateVars = {
2041
- timetablePage,
2042
- config: config2,
2043
- title: `${timetablePage.timetable_page_label} | ${formatListForDisplay(agencies.map((agency) => agency.agency_name))}`
2044
- };
2045
- return renderTemplate("timetablepage", templateVars, config2);
2046
- }
2047
- function generateOverviewHTML(timetablePages, config2) {
2048
- const agencies = getAgencies2();
2049
- if (agencies.length === 0) {
2050
- throw new GtfsToHtmlError("No agencies found", {
2051
- code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
2052
- category: "query" /* QUERY */,
2053
- details: { entity: "agency" }
2054
- });
2055
- }
2056
- const geojson = config2.showMap ? getAgencyGeoJSON(config2) : void 0;
2057
- const templateVars = {
2058
- agency: {
2059
- ...first(agencies),
2060
- geojson
2061
- },
2062
- // Legacy agency object
2063
- agencies,
2064
- geojson,
2065
- config: config2,
2066
- timetablePages,
2067
- title: `${formatListForDisplay(agencies.map((agency) => agency.agency_name))} Timetables`
2068
- };
2069
- return renderTemplate("overview", templateVars, config2);
2070
- }
2071
-
2072
- // src/lib/formatters.ts
2073
- function replaceAll(string, mapObject) {
2074
- const re = new RegExp(Object.keys(mapObject).join("|"), "gi");
2075
- return string.replace(re, (matched) => mapObject[matched]);
2076
- }
2077
- function isNullOrEmpty(value) {
2078
- return value === null || value === "";
2079
- }
2080
- function formatDate(date, dateFormat) {
2081
- if (date.holiday_name) {
2082
- return date.holiday_name;
2083
- }
2084
- return moment3(date.date, "YYYYMMDD").format(dateFormat);
2085
- }
2086
- function timeToSeconds(time) {
2087
- return moment3.duration(time).asSeconds();
2088
- }
2089
- function formatStopTime(stoptime, timetable, config2) {
2090
- stoptime.classes = [];
2091
- if (stoptime.type === "arrival" && stoptime.arrival_time) {
2092
- const arrivalTime = fromGTFSTime(stoptime.arrival_time);
2093
- stoptime.formatted_time = arrivalTime.format(config2.timeFormat);
2094
- stoptime.classes.push(arrivalTime.format("a"));
2095
- } else if (stoptime.type === "departure" && stoptime.departure_time) {
2096
- const departureTime = fromGTFSTime(stoptime.departure_time);
2097
- stoptime.formatted_time = departureTime.format(config2.timeFormat);
2098
- stoptime.classes.push(departureTime.format("a"));
2099
- }
2100
- if (stoptime.pickup_type === 1) {
2101
- stoptime.noPickup = true;
2102
- stoptime.classes.push("no-pickup");
2103
- if (timetable.noPickupSymbol !== null) {
2104
- timetable.noPickupSymbolUsed = true;
2105
- }
2106
- } else if (stoptime.pickup_type === 2 || stoptime.pickup_type === 3) {
2107
- stoptime.requestPickup = true;
2108
- stoptime.classes.push("request-pickup");
2109
- if (timetable.requestPickupSymbol !== null) {
2110
- timetable.requestPickupSymbolUsed = true;
2111
- }
2112
- }
2113
- if (stoptime.drop_off_type === 1) {
2114
- stoptime.noDropoff = true;
2115
- stoptime.classes.push("no-drop-off");
2116
- if (timetable.noDropoffSymbol !== null) {
2117
- timetable.noDropoffSymbolUsed = true;
2118
- }
2119
- } else if (stoptime.drop_off_type === 2 || stoptime.drop_off_type === 3) {
2120
- stoptime.requestDropoff = true;
2121
- stoptime.classes.push("request-drop-off");
2122
- if (timetable.requestDropoffSymbol !== null) {
2123
- timetable.requestDropoffSymbolUsed = true;
2124
- }
2125
- }
2126
- if (stoptime.timepoint === 0 || stoptime.departure_time === "") {
2127
- stoptime.interpolated = true;
2128
- stoptime.classes.push("interpolated");
2129
- if (timetable.interpolatedStopSymbol !== null) {
2130
- timetable.interpolatedStopSymbolUsed = true;
2131
- }
2132
- }
2133
- if (stoptime.timepoint === null && stoptime.departure_time === null && stoptime.stop_sequence === null) {
2134
- stoptime.skipped = true;
2135
- stoptime.classes.push("skipped");
2136
- if (timetable.noServiceSymbol !== null) {
2137
- timetable.noServiceSymbolUsed = true;
2138
- }
2139
- }
2140
- if (stoptime.timepoint === 1) {
2141
- stoptime.classes.push("timepoint");
2142
- }
2143
- return stoptime;
2144
- }
2145
- function filterHourlyTimes(stops) {
2146
- const firstStopTimes = [];
2147
- const firstTripMinutes = minutesAfterMidnight(stops[0].trips[0].arrival_time);
2148
- for (const trip of stops[0].trips) {
2149
- const minutes = minutesAfterMidnight(trip.arrival_time);
2150
- if (minutes >= firstTripMinutes + 60) {
2151
- break;
2152
- }
2153
- firstStopTimes.push(fromGTFSTime(trip.arrival_time));
2154
- }
2155
- const firstStopTimesAndIndex = firstStopTimes.map((time, idx) => ({
2156
- idx,
2157
- time
2158
- }));
2159
- const sortedFirstStopTimesAndIndex = sortBy2(
2160
- firstStopTimesAndIndex,
2161
- (item) => Number.parseInt(item.time.format("m"), 10)
2162
- );
2163
- return stops.map((stop) => {
2164
- stop.hourlyTimes = sortedFirstStopTimesAndIndex.map(
2165
- (item) => fromGTFSTime(stop.trips[item.idx].arrival_time).format(":mm")
2166
- );
2167
- return stop;
2168
- });
2169
- }
2170
- var days = [
2171
- "monday",
2172
- "tuesday",
2173
- "wednesday",
2174
- "thursday",
2175
- "friday",
2176
- "saturday",
2177
- "sunday"
2178
- ];
2179
- function formatDays(calendar, config2) {
2180
- const daysShort = config2.daysShortStrings;
2181
- let daysInARow = 0;
2182
- let dayString = "";
2183
- if (!calendar) {
2184
- return "";
2185
- }
2186
- for (let i = 0; i <= 6; i += 1) {
2187
- const currentDayOperating = calendar[days[i]] === 1;
2188
- const previousDayOperating = i > 0 ? calendar[days[i - 1]] === 1 : false;
2189
- const nextDayOperating = i < 6 ? calendar[days[i + 1]] === 1 : false;
2190
- if (currentDayOperating) {
2191
- if (dayString.length > 0) {
2192
- if (!previousDayOperating) {
2193
- dayString += ", ";
2194
- } else if (daysInARow === 1) {
2195
- dayString += "-";
2196
- }
2197
- }
2198
- daysInARow += 1;
2199
- if (dayString.length === 0 || !nextDayOperating || i === 6 || !previousDayOperating) {
2200
- dayString += daysShort[i];
2201
- }
2202
- } else {
2203
- daysInARow = 0;
2204
- }
2205
- }
2206
- if (dayString.length === 0) {
2207
- dayString = config2.noRegularServiceDaysText;
2208
- }
2209
- return dayString;
2210
- }
2211
- function formatDaysLong(dayList, config2) {
2212
- const mapObject = zipObject(config2.daysShortStrings, config2.daysStrings);
2213
- return replaceAll(dayList, mapObject);
2214
- }
2215
- function formatFrequency(frequency, config2) {
2216
- const startTime = fromGTFSTime(frequency.start_time);
2217
- const endTime = fromGTFSTime(frequency.end_time);
2218
- const headway = moment3.duration(frequency.headway_secs, "seconds");
2219
- frequency.start_formatted_time = startTime.format(config2.timeFormat);
2220
- frequency.end_formatted_time = endTime.format(config2.timeFormat);
2221
- frequency.headway_min = Math.round(headway.asMinutes());
2222
- return frequency;
2223
- }
2224
- function formatTimetableId({
2225
- routeIds,
2226
- directionId,
2227
- days: days2,
2228
- dates
2229
- }) {
2230
- let timetableId = routeIds.join("_");
2231
- if (calendarToCalendarCode(days2)) {
2232
- timetableId += `|${calendarToCalendarCode(days2)}`;
2233
- } else if (dates && dates.length > 0) {
2234
- timetableId += `|${dates.join("_")}`;
2235
- }
2236
- if (!isNullOrEmpty(directionId)) {
2237
- timetableId += `|${directionId}`;
2238
- }
2239
- return timetableId;
2240
- }
2241
- function createEmptyStoptime(stopId, tripId) {
2242
- return {
2243
- id: null,
2244
- trip_id: tripId,
2245
- arrival_time: null,
2246
- departure_time: null,
2247
- stop_id: stopId,
2248
- stop_sequence: null,
2249
- stop_headsign: null,
2250
- pickup_type: null,
2251
- drop_off_type: null,
2252
- continuous_pickup: null,
2253
- continuous_drop_off: null,
2254
- shape_dist_traveled: null,
2255
- timepoint: null
2256
- };
2257
- }
2258
- function formatStops(timetable, config2) {
2259
- for (const trip of timetable.orderedTrips) {
2260
- let stopIndex = -1;
2261
- for (const [idx, stoptime] of trip.stoptimes.entries()) {
2262
- const stop = find2(timetable.stops, (st, idx2) => {
2263
- if (st.stop_id === stoptime.stop_id && idx2 > stopIndex) {
2264
- stopIndex = idx2;
2265
- return true;
2266
- }
2267
- return false;
2268
- });
2269
- if (!stop) {
2270
- continue;
2271
- }
2272
- if (idx === 0) {
2273
- stoptime.drop_off_type = 0;
2274
- }
2275
- if (idx === trip.stoptimes.length - 1) {
2276
- stoptime.pickup_type = 0;
2277
- }
2278
- if (stop.type === "arrival" && idx < trip.stoptimes.length - 1) {
2279
- const departureStoptime = clone(stoptime);
2280
- departureStoptime.type = "departure";
2281
- timetable.stops[stopIndex + 1].trips.push(
2282
- formatStopTime(departureStoptime, timetable, config2)
2283
- );
2284
- }
2285
- if (!(stop.type === "arrival" && idx === 0)) {
2286
- stoptime.type = "arrival";
2287
- stop.trips.push(formatStopTime(stoptime, timetable, config2));
2288
- }
2289
- }
2290
- for (const stop of timetable.stops) {
2291
- const lastStopTime = last2(stop.trips);
2292
- if (!lastStopTime || lastStopTime.trip_id !== trip.trip_id) {
2293
- stop.trips.push(
2294
- formatStopTime(
2295
- createEmptyStoptime(stop.stop_id, trip.trip_id),
2296
- timetable,
2297
- config2
2298
- )
2299
- );
2300
- }
2301
- }
2302
- }
2303
- if (timetable.orientation === "hourly") {
2304
- timetable.stops = filterHourlyTimes(timetable.stops);
2305
- }
2306
- for (const stop of timetable.stops) {
2307
- stop.is_timepoint = stop.trips.some((stoptime) => isTimepoint(stoptime));
2308
- }
2309
- return timetable.stops;
2310
- }
2311
- function resetStoptimesToMidnight(trip) {
2312
- const offsetSeconds = secondsAfterMidnight(
2313
- first2(trip.stoptimes).departure_time
2314
- );
2315
- if (offsetSeconds > 0) {
2316
- for (const stoptime of trip.stoptimes) {
2317
- stoptime.departure_time = toGTFSTime(
2318
- fromGTFSTime(stoptime.departure_time).subtract(
2319
- offsetSeconds,
2320
- "seconds"
2321
- )
2322
- );
2323
- stoptime.arrival_time = toGTFSTime(
2324
- fromGTFSTime(stoptime.arrival_time).subtract(offsetSeconds, "seconds")
2325
- );
2326
- }
2327
- }
2328
- return trip;
2329
- }
2330
- function updateStoptimesByOffset(trip, offsetSeconds) {
2331
- return trip.stoptimes.map((stoptime) => {
2332
- delete stoptime._id;
2333
- stoptime.departure_time = updateTimeByOffset(
2334
- stoptime.departure_time,
2335
- offsetSeconds
2336
- );
2337
- stoptime.arrival_time = updateTimeByOffset(
2338
- stoptime.arrival_time,
2339
- offsetSeconds
2340
- );
2341
- stoptime.trip_id = trip.trip_id;
2342
- return stoptime;
2343
- });
2344
- }
2345
- function formatRouteColor(route) {
2346
- return route.route_color ? `#${route.route_color}` : "#000000";
2347
- }
2348
- function formatRouteTextColor(route) {
2349
- return route.route_text_color ? `#${route.route_text_color}` : "#FFFFFF";
2350
- }
2351
- function formatTimetableLabel(timetable) {
2352
- if (!isNullOrEmpty(timetable.timetable_label)) {
2353
- return timetable.timetable_label;
2354
- }
2355
- let timetableLabel = "";
2356
- if (timetable.routes && timetable.routes.length > 0) {
2357
- timetableLabel += "Route ";
2358
- if (!isNullOrEmpty(timetable.routes[0].route_short_name)) {
2359
- timetableLabel += timetable.routes[0].route_short_name;
2360
- } else if (!isNullOrEmpty(timetable.routes[0].route_long_name)) {
2361
- timetableLabel += timetable.routes[0].route_long_name;
2362
- }
2363
- }
2364
- if (timetable.stops && timetable.stops.length > 0) {
2365
- const firstStop = timetable.stops[0].stop_name;
2366
- const lastStop = timetable.stops[timetable.stops.length - 1].stop_name;
2367
- if (firstStop === lastStop) {
2368
- if (!isNullOrEmpty(timetable.routes[0].route_long_name)) {
2369
- timetableLabel += ` - ${timetable.routes[0].route_long_name}`;
2370
- }
2371
- timetableLabel += " - Loop";
2372
- } else {
2373
- timetableLabel += ` - ${firstStop} to ${lastStop}`;
2374
- }
2375
- } else if (timetable.direction_name !== null) {
2376
- timetableLabel += ` to ${timetable.direction_name}`;
2377
- }
2378
- return timetableLabel;
2379
- }
2380
- var formatRouteName = (route) => {
2381
- if (route.route_long_name === null || route.route_long_name === "") {
2382
- return `Route ${route.route_short_name}`;
2383
- }
2384
- return route.route_long_name ?? "Unknown";
2385
- };
2386
- var formatRouteNameForFilename = (route) => {
2387
- if (route.route_short_name) {
2388
- return route.route_short_name.replace(/\s/g, "-");
2389
- } else if (route.route_long_name) {
2390
- return route.route_long_name.replace(/\s/g, "-");
2391
- }
2392
- return "Unknown";
2393
- };
2394
- var formatListForDisplay = (list) => {
2395
- return new Intl.ListFormat("en-US", {
2396
- style: "long",
2397
- type: "conjunction"
2398
- }).format(list);
2399
- };
2400
- function mergeTimetablesWithSameId(timetables) {
2401
- if (timetables.length === 0) {
2402
- return [];
2403
- }
2404
- const mergedTimetables = groupBy2(timetables, "timetable_id");
2405
- return Object.values(mergedTimetables).map((timetableGroup) => {
2406
- const mergedTimetable = omit(timetableGroup[0], "route_id");
2407
- mergedTimetable.route_ids = timetableGroup.map(
2408
- (timetable) => timetable.route_id
2409
- );
2410
- return mergedTimetable;
2411
- });
2412
- }
2413
-
2414
- // src/app/index.ts
2415
- var argv = yargs(hideBin(process.argv)).option("c", {
2416
- alias: "configPath",
2417
- describe: "Path to config file",
2418
- default: "./config.json",
2419
- type: "string"
10
+ //#region src/app/index.ts
11
+ const argv = yargs(hideBin(process.argv)).option("c", {
12
+ alias: "configPath",
13
+ describe: "Path to config file",
14
+ default: "./config.json",
15
+ type: "string"
2420
16
  }).parseSync();
2421
- var app = express();
2422
- var configPath = argv.configPath || join2(process.cwd(), "config.json");
2423
- var selectedConfig = JSON.parse(readFileSync(configPath, "utf8"));
2424
- var config = setDefaultConfig(selectedConfig);
17
+ const app = express();
18
+ const configPath = argv.configPath || join(process.cwd(), "config.json");
19
+ const config = setDefaultConfig(JSON.parse(readFileSync(configPath, "utf8")));
2425
20
  config.noHead = false;
2426
21
  config.assetPath = "/";
2427
22
  config.logFunction = console.log;
2428
23
  try {
2429
- openDb2(config);
24
+ openDb(config);
2430
25
  } catch (error) {
2431
- console.error(
2432
- `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists and run gtfs-to-html to import GTFS before running this app.`
2433
- );
2434
- throw new GtfsToHtmlError(
2435
- `Unable to open sqlite database "${config.sqlitePath}"`,
2436
- {
2437
- code: "GTFS_TO_HTML_DATABASE_OPEN_FAILED" /* DATABASE_OPEN_FAILED */,
2438
- category: "database" /* DATABASE */,
2439
- details: { sqlitePath: config.sqlitePath, dbCode: error?.code },
2440
- cause: error
2441
- }
2442
- );
26
+ console.error(`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists and run gtfs-to-html to import GTFS before running this app.`);
27
+ throw new GtfsToHtmlError(`Unable to open sqlite database "${config.sqlitePath}"`, {
28
+ code: "GTFS_TO_HTML_DATABASE_OPEN_FAILED",
29
+ category: "database",
30
+ details: {
31
+ sqlitePath: config.sqlitePath,
32
+ dbCode: error?.code
33
+ },
34
+ cause: error
35
+ });
2443
36
  }
2444
37
  app.set("views", getPathToViewsFolder(config));
2445
38
  app.set("view engine", "pug");
2446
39
  app.use((req, res, next) => {
2447
- console.log(`${req.method} ${req.url}`);
2448
- next();
40
+ console.log(`${req.method} ${req.url}`);
41
+ next();
2449
42
  });
2450
- var staticAssetPath = config.templatePath === void 0 ? getPathToViewsFolder(config) : untildify(config.templatePath);
43
+ const staticAssetPath = config.templatePath === void 0 ? getPathToViewsFolder(config) : untildify(config.templatePath);
2451
44
  app.use(express.static(staticAssetPath));
2452
- var browserAssetsPath = join2(
2453
- dirname2(fileURLToPath2(import.meta.url)),
2454
- "../browser"
2455
- );
45
+ const browserAssetsPath = join(dirname(fileURLToPath(import.meta.url)), "../browser");
2456
46
  app.use("/js", express.static(browserAssetsPath));
2457
47
  app.use("/css", express.static(browserAssetsPath));
2458
48
  app.get("/", async (req, res, next) => {
2459
- try {
2460
- const timetablePages = [];
2461
- const timetablePageIds = getTimetablePagesForAgency(config).map(
2462
- (timetablePage) => timetablePage.timetable_page_id
2463
- );
2464
- for (const timetablePageId of timetablePageIds) {
2465
- if (!timetablePageId) {
2466
- continue;
2467
- }
2468
- const timetablePage = await getFormattedTimetablePage(
2469
- timetablePageId,
2470
- config
2471
- );
2472
- if (!timetablePage.consolidatedTimetables || timetablePage.consolidatedTimetables.length === 0) {
2473
- console.error(
2474
- `No timetables found for timetable_page_id=${timetablePage.timetable_page_id}`
2475
- );
2476
- continue;
2477
- }
2478
- timetablePage.relativePath = `/timetables/${timetablePage.timetable_page_id}`;
2479
- for (const timetable of timetablePage.consolidatedTimetables) {
2480
- timetable.timetable_label = formatTimetableLabel(timetable);
2481
- }
2482
- timetablePages.push(timetablePage);
2483
- }
2484
- const html = await generateOverviewHTML(timetablePages, config);
2485
- res.send(html);
2486
- } catch (error) {
2487
- next(error);
2488
- }
49
+ try {
50
+ const timetablePages = [];
51
+ const timetablePageIds = getTimetablePagesForAgency(config).map((timetablePage) => timetablePage.timetable_page_id);
52
+ for (const timetablePageId of timetablePageIds) {
53
+ if (!timetablePageId) continue;
54
+ const timetablePage = await getFormattedTimetablePage(timetablePageId, config);
55
+ if (!timetablePage.consolidatedTimetables || timetablePage.consolidatedTimetables.length === 0) {
56
+ console.error(`No timetables found for timetable_page_id=${timetablePage.timetable_page_id}`);
57
+ continue;
58
+ }
59
+ timetablePage.relativePath = `/timetables/${timetablePage.timetable_page_id}`;
60
+ for (const timetable of timetablePage.consolidatedTimetables) timetable.timetable_label = formatTimetableLabel(timetable);
61
+ timetablePages.push(timetablePage);
62
+ }
63
+ const html = await generateOverviewHTML(timetablePages, config);
64
+ res.send(html);
65
+ } catch (error) {
66
+ next(error);
67
+ }
2489
68
  });
2490
69
  app.get("/timetables/:timetablePageId", async (req, res, next) => {
2491
- const { timetablePageId } = req.params;
2492
- if (!timetablePageId) {
2493
- res.status(400).send("No timetablePageId provided");
2494
- return;
2495
- }
2496
- try {
2497
- const timetablePage = await getFormattedTimetablePage(
2498
- timetablePageId,
2499
- config
2500
- );
2501
- if (!timetablePage || !timetablePage.consolidatedTimetables || timetablePage.consolidatedTimetables.length === 0) {
2502
- res.status(404).send("Timetable page not found");
2503
- return;
2504
- }
2505
- const html = await generateTimetableHTML(timetablePage, config);
2506
- res.send(html);
2507
- } catch (error) {
2508
- if (isGtfsToHtmlError(error) && error.code === "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */) {
2509
- res.status(404).send("Timetable page not found");
2510
- return;
2511
- }
2512
- next(error);
2513
- }
70
+ const { timetablePageId } = req.params;
71
+ if (!timetablePageId) {
72
+ res.status(400).send("No timetablePageId provided");
73
+ return;
74
+ }
75
+ try {
76
+ const timetablePage = await getFormattedTimetablePage(timetablePageId, config);
77
+ if (!timetablePage || !timetablePage.consolidatedTimetables || timetablePage.consolidatedTimetables.length === 0) {
78
+ res.status(404).send("Timetable page not found");
79
+ return;
80
+ }
81
+ const html = await generateTimetableHTML(timetablePage, config);
82
+ res.send(html);
83
+ } catch (error) {
84
+ if (isGtfsToHtmlError(error) && error.code === "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND") {
85
+ res.status(404).send("Timetable page not found");
86
+ return;
87
+ }
88
+ next(error);
89
+ }
2514
90
  });
2515
91
  app.use((req, res) => {
2516
- res.status(404).send("Not Found");
92
+ res.status(404).send("Not Found");
2517
93
  });
2518
- app.use(
2519
- (err, req, res, next) => {
2520
- console.error(err.stack);
2521
- res.status(500).send("Something broke!");
2522
- }
2523
- );
2524
- var startServer = async (port2) => {
2525
- try {
2526
- await new Promise((resolve2, reject) => {
2527
- const server = app.listen(port2).once("listening", () => {
2528
- console.log(`Express server listening on port ${port2}`);
2529
- resolve2();
2530
- }).once("error", (err) => {
2531
- if (err.code === "EADDRINUSE") {
2532
- console.log(`Port ${port2} is in use, trying ${port2 + 1}`);
2533
- server.close();
2534
- resolve2(startServer(port2 + 1));
2535
- } else {
2536
- reject(err);
2537
- }
2538
- });
2539
- });
2540
- } catch (err) {
2541
- console.error("Failed to start server:", err);
2542
- process.exit(1);
2543
- }
2544
- };
2545
- var port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3e3;
2546
- startServer(port);
94
+ app.use((err, req, res, next) => {
95
+ console.error(err.stack);
96
+ res.status(500).send("Something broke!");
97
+ });
98
+ const startServer = async (port) => {
99
+ try {
100
+ await new Promise((resolve, reject) => {
101
+ const server = app.listen(port).once("listening", () => {
102
+ console.log(`Express server listening on port ${port}`);
103
+ resolve();
104
+ }).once("error", (err) => {
105
+ if (err.code === "EADDRINUSE") {
106
+ console.log(`Port ${port} is in use, trying ${port + 1}`);
107
+ server.close();
108
+ resolve(startServer(port + 1));
109
+ } else reject(err);
110
+ });
111
+ });
112
+ } catch (err) {
113
+ console.error("Failed to start server:", err);
114
+ process.exit(1);
115
+ }
116
+ };
117
+ startServer(process.env.PORT ? parseInt(process.env.PORT, 10) : 3e3);
118
+
119
+ //#endregion
120
+ export { };
2547
121
  //# sourceMappingURL=index.js.map