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