gtfs-to-html 2.10.0 → 2.10.1

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/index.d.ts CHANGED
@@ -51,9 +51,7 @@ interface Config {
51
51
  useParentStation?: boolean;
52
52
  verbose?: boolean;
53
53
  zipOutput?: boolean;
54
- log?: (text: string) => void;
55
- logWarning?: (text: string) => void;
56
- logError?: (text: string) => void;
54
+ logFunction?: (text: string) => void;
57
55
  }
58
56
 
59
57
  declare const gtfsToHtml: (initialConfig: Config) => Promise<string>;
package/dist/index.js CHANGED
@@ -106,6 +106,7 @@ import {
106
106
  cloneDeep,
107
107
  compact,
108
108
  countBy,
109
+ difference,
109
110
  entries,
110
111
  every as every2,
111
112
  find,
@@ -155,6 +156,163 @@ import { getShapesAsGeoJSON, getStopsAsGeoJSON } from "gtfs";
155
156
  import { flatMap } from "lodash-es";
156
157
  import simplify from "@turf/simplify";
157
158
  import { featureCollection, round } from "@turf/helpers";
159
+
160
+ // src/lib/log-utils.ts
161
+ import { clearLine, cursorTo } from "node:readline";
162
+ import { noop } from "lodash-es";
163
+ import * as colors from "yoctocolors";
164
+ import { getFeedInfo } from "gtfs";
165
+ import Table from "cli-table";
166
+ function generateLogText(outputStats, config) {
167
+ const feedInfo = getFeedInfo();
168
+ const feedVersion = feedInfo.length > 0 && feedInfo[0].feed_version ? feedInfo[0].feed_version : "Unknown";
169
+ const logText = [
170
+ `Feed Version: ${feedVersion}`,
171
+ `GTFS-to-HTML Version: ${config.gtfsToHtmlVersion}`,
172
+ `Date Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
173
+ `Timetable Page Count: ${outputStats.timetablePages}`,
174
+ `Timetable Count: ${outputStats.timetables}`,
175
+ `Calendar Service ID Count: ${outputStats.calendars}`,
176
+ `Route Count: ${outputStats.routes}`,
177
+ `Trip Count: ${outputStats.trips}`,
178
+ `Stop Count: ${outputStats.stops}`
179
+ ];
180
+ for (const agency of config.agencies) {
181
+ if (agency.url) {
182
+ logText.push(`Source: ${agency.url}`);
183
+ } else if (agency.path) {
184
+ logText.push(`Source: ${agency.path}`);
185
+ }
186
+ }
187
+ if (outputStats.warnings.length > 0) {
188
+ logText.push("", "Warnings:", ...outputStats.warnings);
189
+ }
190
+ return logText.join("\n");
191
+ }
192
+ function log(config) {
193
+ if (config.verbose === false) {
194
+ return noop;
195
+ }
196
+ if (config.logFunction) {
197
+ return config.logFunction;
198
+ }
199
+ return (text, overwrite) => {
200
+ if (overwrite === true && process.stdout.isTTY) {
201
+ clearLine(process.stdout, 0);
202
+ cursorTo(process.stdout, 0);
203
+ } else {
204
+ process.stdout.write("\n");
205
+ }
206
+ process.stdout.write(text);
207
+ };
208
+ }
209
+ function logWarning(config) {
210
+ if (config.logFunction) {
211
+ return config.logFunction;
212
+ }
213
+ return (text) => {
214
+ process.stdout.write(`
215
+ ${formatWarning(text)}
216
+ `);
217
+ };
218
+ }
219
+ function logError(config) {
220
+ if (config.logFunction) {
221
+ return config.logFunction;
222
+ }
223
+ return (text) => {
224
+ process.stdout.write(`
225
+ ${formatError(text)}
226
+ `);
227
+ };
228
+ }
229
+ function formatWarning(text) {
230
+ const warningMessage = `${colors.underline("Warning")}: ${text}`;
231
+ return colors.yellow(warningMessage);
232
+ }
233
+ function formatError(error) {
234
+ const messageText = error instanceof Error ? error.message : error;
235
+ const errorMessage = `${colors.underline("Error")}: ${messageText.replace(
236
+ "Error: ",
237
+ ""
238
+ )}`;
239
+ return colors.red(errorMessage);
240
+ }
241
+ function logStats(config) {
242
+ if (config.logFunction) {
243
+ return noop;
244
+ }
245
+ return (stats) => {
246
+ const table = new Table({
247
+ colWidths: [40, 20],
248
+ head: ["Item", "Count"]
249
+ });
250
+ table.push(
251
+ ["\u{1F4C4} Timetable Pages", stats.timetablePages],
252
+ ["\u{1F551} Timetables", stats.timetables],
253
+ ["\u{1F4C5} Calendar Service IDs", stats.calendars],
254
+ ["\u{1F504} Routes", stats.routes],
255
+ ["\u{1F68D} Trips", stats.trips],
256
+ ["\u{1F6D1} Stops", stats.stops],
257
+ ["\u26D4\uFE0F Warnings", stats.warnings.length]
258
+ );
259
+ log(config)(table.toString());
260
+ };
261
+ }
262
+ var generateProgressBarString = (barTotal, barProgress, size2 = 40) => {
263
+ const line = "-";
264
+ const slider = "=";
265
+ if (!barTotal) {
266
+ throw new Error("Total value is either not provided or invalid");
267
+ }
268
+ if (!barProgress && barProgress !== 0) {
269
+ throw new Error("Current value is either not provided or invalid");
270
+ }
271
+ if (isNaN(barTotal)) {
272
+ throw new Error("Total value is not an integer");
273
+ }
274
+ if (isNaN(barProgress)) {
275
+ throw new Error("Current value is not an integer");
276
+ }
277
+ if (isNaN(size2)) {
278
+ throw new Error("Size is not an integer");
279
+ }
280
+ if (barProgress > barTotal) {
281
+ return slider.repeat(size2 + 2);
282
+ }
283
+ const percentage = barProgress / barTotal;
284
+ const progress = Math.round(size2 * percentage);
285
+ const emptyProgress = size2 - progress;
286
+ const progressText = slider.repeat(progress);
287
+ const emptyProgressText = line.repeat(emptyProgress);
288
+ return progressText + emptyProgressText;
289
+ };
290
+ function progressBar(formatString, barTotal, config) {
291
+ let barProgress = 0;
292
+ if (config.verbose === false) {
293
+ return {
294
+ increment: noop,
295
+ interrupt: noop
296
+ };
297
+ }
298
+ if (barTotal === 0) {
299
+ return null;
300
+ }
301
+ const renderProgressString = () => formatString.replace("{value}", barProgress).replace("{total}", barTotal).replace("{bar}", generateProgressBarString(barTotal, barProgress));
302
+ log(config)(renderProgressString(), true);
303
+ return {
304
+ interrupt(text) {
305
+ logWarning(config)(text);
306
+ log(config)("");
307
+ },
308
+ increment() {
309
+ barProgress += 1;
310
+ log(config)(renderProgressString(), true);
311
+ }
312
+ };
313
+ }
314
+
315
+ // src/lib/geojson-utils.ts
158
316
  var mergeGeojson = (...geojsons) => featureCollection(flatMap(geojsons, (geojson) => geojson.features));
159
317
  var truncateGeoJSONDecimals = (geojson, config) => {
160
318
  for (const feature of geojson.features) {
@@ -182,18 +340,6 @@ var truncateGeoJSONDecimals = (geojson, config) => {
182
340
  }
183
341
  return geojson;
184
342
  };
185
- var simplifyGeoJSON = (geojson, config) => {
186
- try {
187
- const simplifiedGeojson = simplify(geojson, {
188
- tolerance: 1 / 10 ** config.coordinatePrecision,
189
- highQuality: true
190
- });
191
- return truncateGeoJSONDecimals(simplifiedGeojson, config);
192
- } catch {
193
- config.logWarning("Unable to simplify geojson");
194
- return truncateGeoJSONDecimals(geojson, config);
195
- }
196
- };
197
343
  function getTimetableGeoJSON(timetable, config) {
198
344
  const shapesGeojsons = timetable.route_ids.map(
199
345
  (routeId) => getShapesAsGeoJSON({
@@ -210,13 +356,35 @@ function getTimetableGeoJSON(timetable, config) {
210
356
  })
211
357
  );
212
358
  const geojson = mergeGeojson(...shapesGeojsons, ...stopsGeojsons);
213
- return simplifyGeoJSON(geojson, config);
359
+ let simplifiedGeojson;
360
+ try {
361
+ simplifiedGeojson = simplify(geojson, {
362
+ tolerance: 1 / 10 ** config.coordinatePrecision,
363
+ highQuality: true
364
+ });
365
+ } catch {
366
+ timetable.warnings.push(
367
+ `Timetable ${timetable.timetable_id} - Unable to simplify geojson`
368
+ );
369
+ simplifiedGeojson = geojson;
370
+ }
371
+ return truncateGeoJSONDecimals(simplifiedGeojson, config);
214
372
  }
215
373
  function getAgencyGeoJSON(config) {
216
374
  const shapesGeojsons = getShapesAsGeoJSON();
217
375
  const stopsGeojsons = getStopsAsGeoJSON();
218
376
  const geojson = mergeGeojson(shapesGeojsons, stopsGeojsons);
219
- return simplifyGeoJSON(geojson, config);
377
+ let simplifiedGeojson;
378
+ try {
379
+ simplifiedGeojson = simplify(geojson, {
380
+ tolerance: 1 / 10 ** config.coordinatePrecision,
381
+ highQuality: true
382
+ });
383
+ } catch {
384
+ logWarning(config)("Unable to simplify geojson");
385
+ simplifiedGeojson = geojson;
386
+ }
387
+ return truncateGeoJSONDecimals(simplifiedGeojson, config);
220
388
  }
221
389
 
222
390
  // src/lib/template-functions.ts
@@ -334,7 +502,7 @@ function formatTripNameForCSV(trip, timetable) {
334
502
  }
335
503
 
336
504
  // package.json
337
- var version = "2.10.0";
505
+ var version = "2.10.1";
338
506
 
339
507
  // src/lib/utils.ts
340
508
  var isTimepoint = (stoptime) => {
@@ -564,7 +732,7 @@ var getTimetableNotesForTimetable = (timetable, config) => {
564
732
  continue;
565
733
  }
566
734
  if (noteReference.stop_id === "" || noteReference.stop_id === null) {
567
- config.logWarning(
735
+ timetable.warnings.push(
568
736
  `Timetable Note Reference for note_id=${noteReference.note_id} has a \`stop_sequence\` but no \`stop_id\` - ignoring`
569
737
  );
570
738
  continue;
@@ -797,9 +965,19 @@ var getStopOrder = (timetable, config) => {
797
965
  config
798
966
  );
799
967
  const stopIds = longestTripStoptimes.map((stoptime) => stoptime.stop_id);
800
- config.logWarning(
801
- `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 can result in timetables with some stops missing. Try manually specifying stops with \`timetable_stop_order.txt\`.`
968
+ const missingStopIds = difference(
969
+ uniq(
970
+ timetable.orderedTrips.flatMap(
971
+ (trip) => trip.stoptimes.map((stoptime) => stoptime.stop_id)
972
+ )
973
+ ),
974
+ uniq(stopIds)
802
975
  );
976
+ if (missingStopIds.length > 0) {
977
+ timetable.warnings.push(
978
+ `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 ${new Intl.ListFormat("en", { style: "long", type: "conjunction" }).format(missingStopIds)}. Try manually specifying stops with \`timetable_stop_order.txt\`. Read more at https://gtfstohtml.com/docs/timetable-stop-order`
979
+ );
980
+ }
803
981
  return duplicateStopsForDifferentArrivalDeparture(
804
982
  stopIds,
805
983
  timetable,
@@ -1974,166 +2152,10 @@ async function renderPdf(htmlPath) {
1974
2152
  await browser.close();
1975
2153
  }
1976
2154
 
1977
- // src/lib/log-utils.ts
1978
- import { clearLine, cursorTo } from "node:readline";
1979
- import { noop } from "lodash-es";
1980
- import * as colors from "yoctocolors";
1981
- import { getFeedInfo } from "gtfs";
1982
- import Table from "cli-table";
1983
- function generateLogText(outputStats, config) {
1984
- const feedInfo = getFeedInfo();
1985
- const feedVersion = feedInfo.length > 0 && feedInfo[0].feed_version ? feedInfo[0].feed_version : "Unknown";
1986
- const logText = [
1987
- `Feed Version: ${feedVersion}`,
1988
- `GTFS-to-HTML Version: ${config.gtfsToHtmlVersion}`,
1989
- `Date Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
1990
- `Timetable Page Count: ${outputStats.timetablePages}`,
1991
- `Timetable Count: ${outputStats.timetables}`,
1992
- `Calendar Service ID Count: ${outputStats.calendars}`,
1993
- `Route Count: ${outputStats.routes}`,
1994
- `Trip Count: ${outputStats.trips}`,
1995
- `Stop Count: ${outputStats.stops}`
1996
- ];
1997
- for (const agency of config.agencies) {
1998
- if (agency.url) {
1999
- logText.push(`Source: ${agency.url}`);
2000
- } else if (agency.path) {
2001
- logText.push(`Source: ${agency.path}`);
2002
- }
2003
- }
2004
- if (outputStats.warnings.length > 0) {
2005
- logText.push("", "Warnings:", ...outputStats.warnings);
2006
- }
2007
- return logText.join("\n");
2008
- }
2009
- function log(config) {
2010
- if (config.verbose === false) {
2011
- return noop;
2012
- }
2013
- if (config.logFunction) {
2014
- return config.logFunction;
2015
- }
2016
- return (text, overwrite) => {
2017
- if (overwrite === true && process.stdout.isTTY) {
2018
- clearLine(process.stdout, 0);
2019
- cursorTo(process.stdout, 0);
2020
- } else {
2021
- process.stdout.write("\n");
2022
- }
2023
- process.stdout.write(text);
2024
- };
2025
- }
2026
- function logWarning(config) {
2027
- if (config.logFunction) {
2028
- return config.logFunction;
2029
- }
2030
- return (text) => {
2031
- process.stdout.write(`
2032
- ${formatWarning(text)}
2033
- `);
2034
- };
2035
- }
2036
- function logError(config) {
2037
- if (config.logFunction) {
2038
- return config.logFunction;
2039
- }
2040
- return (text) => {
2041
- process.stdout.write(`
2042
- ${formatError(text)}
2043
- `);
2044
- };
2045
- }
2046
- function formatWarning(text) {
2047
- const warningMessage = `${colors.underline("Warning")}: ${text}`;
2048
- return colors.yellow(warningMessage);
2049
- }
2050
- function formatError(error) {
2051
- const messageText = error instanceof Error ? error.message : error;
2052
- const errorMessage = `${colors.underline("Error")}: ${messageText.replace(
2053
- "Error: ",
2054
- ""
2055
- )}`;
2056
- return colors.red(errorMessage);
2057
- }
2058
- function logStats(stats, config) {
2059
- if (config.logFunction) {
2060
- return;
2061
- }
2062
- const table = new Table({
2063
- colWidths: [40, 20],
2064
- head: ["Item", "Count"]
2065
- });
2066
- table.push(
2067
- ["\u{1F4C4} Timetable Pages", stats.timetablePages],
2068
- ["\u{1F551} Timetables", stats.timetables],
2069
- ["\u{1F4C5} Calendar Service IDs", stats.calendars],
2070
- ["\u{1F504} Routes", stats.routes],
2071
- ["\u{1F68D} Trips", stats.trips],
2072
- ["\u{1F6D1} Stops", stats.stops],
2073
- ["\u26D4\uFE0F Warnings", stats.warnings.length]
2074
- );
2075
- config.log(table.toString());
2076
- }
2077
- var generateProgressBarString = (barTotal, barProgress, size2 = 40) => {
2078
- const line = "-";
2079
- const slider = "=";
2080
- if (!barTotal) {
2081
- throw new Error("Total value is either not provided or invalid");
2082
- }
2083
- if (!barProgress && barProgress !== 0) {
2084
- throw new Error("Current value is either not provided or invalid");
2085
- }
2086
- if (isNaN(barTotal)) {
2087
- throw new Error("Total value is not an integer");
2088
- }
2089
- if (isNaN(barProgress)) {
2090
- throw new Error("Current value is not an integer");
2091
- }
2092
- if (isNaN(size2)) {
2093
- throw new Error("Size is not an integer");
2094
- }
2095
- if (barProgress > barTotal) {
2096
- return slider.repeat(size2 + 2);
2097
- }
2098
- const percentage = barProgress / barTotal;
2099
- const progress = Math.round(size2 * percentage);
2100
- const emptyProgress = size2 - progress;
2101
- const progressText = slider.repeat(progress);
2102
- const emptyProgressText = line.repeat(emptyProgress);
2103
- return progressText + emptyProgressText;
2104
- };
2105
- function progressBar(formatString, barTotal, config) {
2106
- let barProgress = 0;
2107
- if (config.verbose === false) {
2108
- return {
2109
- increment: noop,
2110
- interrupt: noop
2111
- };
2112
- }
2113
- if (barTotal === 0) {
2114
- return null;
2115
- }
2116
- const renderProgressString = () => formatString.replace("{value}", barProgress).replace("{total}", barTotal).replace("{bar}", generateProgressBarString(barTotal, barProgress));
2117
- config.log(renderProgressString(), true);
2118
- return {
2119
- interrupt(text) {
2120
- config.logWarning(text);
2121
- config.log("");
2122
- },
2123
- increment() {
2124
- barProgress += 1;
2125
- config.log(renderProgressString(), true);
2126
- }
2127
- };
2128
- }
2129
-
2130
2155
  // src/lib/gtfs-to-html.ts
2131
2156
  var gtfsToHtml = async (initialConfig) => {
2132
2157
  const config = setDefaultConfig(initialConfig);
2133
2158
  const timer = new Timer();
2134
- config.log = log(config);
2135
- config.logWarning = logWarning(config);
2136
- config.logError = logError(config);
2137
2159
  const agencyKey = config.agencies.map(
2138
2160
  (agency) => agency.agencyKey ?? agency.agency_key ?? "unknown"
2139
2161
  ).join("-");
@@ -2144,7 +2166,7 @@ var gtfsToHtml = async (initialConfig) => {
2144
2166
  openDb2(config);
2145
2167
  } catch (error) {
2146
2168
  if (error?.code === "SQLITE_CANTOPEN") {
2147
- config.logError(
2169
+ logError(config)(
2148
2170
  `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
2149
2171
  );
2150
2172
  }
@@ -2252,12 +2274,12 @@ var gtfsToHtml = async (initialConfig) => {
2252
2274
  outputPath,
2253
2275
  config.zipOutput ? "/timetables.zip" : ""
2254
2276
  );
2255
- config.log(
2277
+ log(config)(
2256
2278
  `${agencyKey}: ${config.outputFormat.toUpperCase()} timetables created at ${fullOutputPath}`
2257
2279
  );
2258
- logStats(stats, config);
2280
+ logStats(config)(stats);
2259
2281
  const seconds = Math.round(timer.time() / 1e3);
2260
- config.log(
2282
+ log(config)(
2261
2283
  `${agencyKey}: ${config.outputFormat.toUpperCase()} timetable generation required ${seconds} seconds`
2262
2284
  );
2263
2285
  timer.stop();