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.
@@ -103,6 +103,7 @@ import {
103
103
  cloneDeep,
104
104
  compact,
105
105
  countBy,
106
+ difference,
106
107
  entries,
107
108
  every as every2,
108
109
  find,
@@ -152,6 +153,163 @@ import { getShapesAsGeoJSON, getStopsAsGeoJSON } from "gtfs";
152
153
  import { flatMap } from "lodash-es";
153
154
  import simplify from "@turf/simplify";
154
155
  import { featureCollection, round } from "@turf/helpers";
156
+
157
+ // src/lib/log-utils.ts
158
+ import { clearLine, cursorTo } from "node:readline";
159
+ import { noop } from "lodash-es";
160
+ import * as colors from "yoctocolors";
161
+ import { getFeedInfo } from "gtfs";
162
+ import Table from "cli-table";
163
+ function generateLogText(outputStats, config) {
164
+ const feedInfo = getFeedInfo();
165
+ const feedVersion = feedInfo.length > 0 && feedInfo[0].feed_version ? feedInfo[0].feed_version : "Unknown";
166
+ const logText = [
167
+ `Feed Version: ${feedVersion}`,
168
+ `GTFS-to-HTML Version: ${config.gtfsToHtmlVersion}`,
169
+ `Date Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
170
+ `Timetable Page Count: ${outputStats.timetablePages}`,
171
+ `Timetable Count: ${outputStats.timetables}`,
172
+ `Calendar Service ID Count: ${outputStats.calendars}`,
173
+ `Route Count: ${outputStats.routes}`,
174
+ `Trip Count: ${outputStats.trips}`,
175
+ `Stop Count: ${outputStats.stops}`
176
+ ];
177
+ for (const agency of config.agencies) {
178
+ if (agency.url) {
179
+ logText.push(`Source: ${agency.url}`);
180
+ } else if (agency.path) {
181
+ logText.push(`Source: ${agency.path}`);
182
+ }
183
+ }
184
+ if (outputStats.warnings.length > 0) {
185
+ logText.push("", "Warnings:", ...outputStats.warnings);
186
+ }
187
+ return logText.join("\n");
188
+ }
189
+ function log(config) {
190
+ if (config.verbose === false) {
191
+ return noop;
192
+ }
193
+ if (config.logFunction) {
194
+ return config.logFunction;
195
+ }
196
+ return (text, overwrite) => {
197
+ if (overwrite === true && process.stdout.isTTY) {
198
+ clearLine(process.stdout, 0);
199
+ cursorTo(process.stdout, 0);
200
+ } else {
201
+ process.stdout.write("\n");
202
+ }
203
+ process.stdout.write(text);
204
+ };
205
+ }
206
+ function logWarning(config) {
207
+ if (config.logFunction) {
208
+ return config.logFunction;
209
+ }
210
+ return (text) => {
211
+ process.stdout.write(`
212
+ ${formatWarning(text)}
213
+ `);
214
+ };
215
+ }
216
+ function logError(config) {
217
+ if (config.logFunction) {
218
+ return config.logFunction;
219
+ }
220
+ return (text) => {
221
+ process.stdout.write(`
222
+ ${formatError(text)}
223
+ `);
224
+ };
225
+ }
226
+ function formatWarning(text) {
227
+ const warningMessage = `${colors.underline("Warning")}: ${text}`;
228
+ return colors.yellow(warningMessage);
229
+ }
230
+ function formatError(error) {
231
+ const messageText = error instanceof Error ? error.message : error;
232
+ const errorMessage = `${colors.underline("Error")}: ${messageText.replace(
233
+ "Error: ",
234
+ ""
235
+ )}`;
236
+ return colors.red(errorMessage);
237
+ }
238
+ function logStats(config) {
239
+ if (config.logFunction) {
240
+ return noop;
241
+ }
242
+ return (stats) => {
243
+ const table = new Table({
244
+ colWidths: [40, 20],
245
+ head: ["Item", "Count"]
246
+ });
247
+ table.push(
248
+ ["\u{1F4C4} Timetable Pages", stats.timetablePages],
249
+ ["\u{1F551} Timetables", stats.timetables],
250
+ ["\u{1F4C5} Calendar Service IDs", stats.calendars],
251
+ ["\u{1F504} Routes", stats.routes],
252
+ ["\u{1F68D} Trips", stats.trips],
253
+ ["\u{1F6D1} Stops", stats.stops],
254
+ ["\u26D4\uFE0F Warnings", stats.warnings.length]
255
+ );
256
+ log(config)(table.toString());
257
+ };
258
+ }
259
+ var generateProgressBarString = (barTotal, barProgress, size2 = 40) => {
260
+ const line = "-";
261
+ const slider = "=";
262
+ if (!barTotal) {
263
+ throw new Error("Total value is either not provided or invalid");
264
+ }
265
+ if (!barProgress && barProgress !== 0) {
266
+ throw new Error("Current value is either not provided or invalid");
267
+ }
268
+ if (isNaN(barTotal)) {
269
+ throw new Error("Total value is not an integer");
270
+ }
271
+ if (isNaN(barProgress)) {
272
+ throw new Error("Current value is not an integer");
273
+ }
274
+ if (isNaN(size2)) {
275
+ throw new Error("Size is not an integer");
276
+ }
277
+ if (barProgress > barTotal) {
278
+ return slider.repeat(size2 + 2);
279
+ }
280
+ const percentage = barProgress / barTotal;
281
+ const progress = Math.round(size2 * percentage);
282
+ const emptyProgress = size2 - progress;
283
+ const progressText = slider.repeat(progress);
284
+ const emptyProgressText = line.repeat(emptyProgress);
285
+ return progressText + emptyProgressText;
286
+ };
287
+ function progressBar(formatString, barTotal, config) {
288
+ let barProgress = 0;
289
+ if (config.verbose === false) {
290
+ return {
291
+ increment: noop,
292
+ interrupt: noop
293
+ };
294
+ }
295
+ if (barTotal === 0) {
296
+ return null;
297
+ }
298
+ const renderProgressString = () => formatString.replace("{value}", barProgress).replace("{total}", barTotal).replace("{bar}", generateProgressBarString(barTotal, barProgress));
299
+ log(config)(renderProgressString(), true);
300
+ return {
301
+ interrupt(text) {
302
+ logWarning(config)(text);
303
+ log(config)("");
304
+ },
305
+ increment() {
306
+ barProgress += 1;
307
+ log(config)(renderProgressString(), true);
308
+ }
309
+ };
310
+ }
311
+
312
+ // src/lib/geojson-utils.ts
155
313
  var mergeGeojson = (...geojsons) => featureCollection(flatMap(geojsons, (geojson) => geojson.features));
156
314
  var truncateGeoJSONDecimals = (geojson, config) => {
157
315
  for (const feature of geojson.features) {
@@ -179,18 +337,6 @@ var truncateGeoJSONDecimals = (geojson, config) => {
179
337
  }
180
338
  return geojson;
181
339
  };
182
- var simplifyGeoJSON = (geojson, config) => {
183
- try {
184
- const simplifiedGeojson = simplify(geojson, {
185
- tolerance: 1 / 10 ** config.coordinatePrecision,
186
- highQuality: true
187
- });
188
- return truncateGeoJSONDecimals(simplifiedGeojson, config);
189
- } catch {
190
- config.logWarning("Unable to simplify geojson");
191
- return truncateGeoJSONDecimals(geojson, config);
192
- }
193
- };
194
340
  function getTimetableGeoJSON(timetable, config) {
195
341
  const shapesGeojsons = timetable.route_ids.map(
196
342
  (routeId) => getShapesAsGeoJSON({
@@ -207,13 +353,35 @@ function getTimetableGeoJSON(timetable, config) {
207
353
  })
208
354
  );
209
355
  const geojson = mergeGeojson(...shapesGeojsons, ...stopsGeojsons);
210
- return simplifyGeoJSON(geojson, config);
356
+ let simplifiedGeojson;
357
+ try {
358
+ simplifiedGeojson = simplify(geojson, {
359
+ tolerance: 1 / 10 ** config.coordinatePrecision,
360
+ highQuality: true
361
+ });
362
+ } catch {
363
+ timetable.warnings.push(
364
+ `Timetable ${timetable.timetable_id} - Unable to simplify geojson`
365
+ );
366
+ simplifiedGeojson = geojson;
367
+ }
368
+ return truncateGeoJSONDecimals(simplifiedGeojson, config);
211
369
  }
212
370
  function getAgencyGeoJSON(config) {
213
371
  const shapesGeojsons = getShapesAsGeoJSON();
214
372
  const stopsGeojsons = getStopsAsGeoJSON();
215
373
  const geojson = mergeGeojson(shapesGeojsons, stopsGeojsons);
216
- return simplifyGeoJSON(geojson, config);
374
+ let simplifiedGeojson;
375
+ try {
376
+ simplifiedGeojson = simplify(geojson, {
377
+ tolerance: 1 / 10 ** config.coordinatePrecision,
378
+ highQuality: true
379
+ });
380
+ } catch {
381
+ logWarning(config)("Unable to simplify geojson");
382
+ simplifiedGeojson = geojson;
383
+ }
384
+ return truncateGeoJSONDecimals(simplifiedGeojson, config);
217
385
  }
218
386
 
219
387
  // src/lib/template-functions.ts
@@ -331,7 +499,7 @@ function formatTripNameForCSV(trip, timetable) {
331
499
  }
332
500
 
333
501
  // package.json
334
- var version = "2.10.0";
502
+ var version = "2.10.1";
335
503
 
336
504
  // src/lib/utils.ts
337
505
  var isTimepoint = (stoptime) => {
@@ -561,7 +729,7 @@ var getTimetableNotesForTimetable = (timetable, config) => {
561
729
  continue;
562
730
  }
563
731
  if (noteReference.stop_id === "" || noteReference.stop_id === null) {
564
- config.logWarning(
732
+ timetable.warnings.push(
565
733
  `Timetable Note Reference for note_id=${noteReference.note_id} has a \`stop_sequence\` but no \`stop_id\` - ignoring`
566
734
  );
567
735
  continue;
@@ -794,9 +962,19 @@ var getStopOrder = (timetable, config) => {
794
962
  config
795
963
  );
796
964
  const stopIds = longestTripStoptimes.map((stoptime) => stoptime.stop_id);
797
- config.logWarning(
798
- `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\`.`
965
+ const missingStopIds = difference(
966
+ uniq(
967
+ timetable.orderedTrips.flatMap(
968
+ (trip) => trip.stoptimes.map((stoptime) => stoptime.stop_id)
969
+ )
970
+ ),
971
+ uniq(stopIds)
799
972
  );
973
+ if (missingStopIds.length > 0) {
974
+ timetable.warnings.push(
975
+ `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`
976
+ );
977
+ }
800
978
  return duplicateStopsForDifferentArrivalDeparture(
801
979
  stopIds,
802
980
  timetable,
@@ -1996,159 +2174,6 @@ async function renderPdf(htmlPath) {
1996
2174
  await browser.close();
1997
2175
  }
1998
2176
 
1999
- // src/lib/log-utils.ts
2000
- import { clearLine, cursorTo } from "node:readline";
2001
- import { noop } from "lodash-es";
2002
- import * as colors from "yoctocolors";
2003
- import { getFeedInfo } from "gtfs";
2004
- import Table from "cli-table";
2005
- function generateLogText(outputStats, config) {
2006
- const feedInfo = getFeedInfo();
2007
- const feedVersion = feedInfo.length > 0 && feedInfo[0].feed_version ? feedInfo[0].feed_version : "Unknown";
2008
- const logText = [
2009
- `Feed Version: ${feedVersion}`,
2010
- `GTFS-to-HTML Version: ${config.gtfsToHtmlVersion}`,
2011
- `Date Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
2012
- `Timetable Page Count: ${outputStats.timetablePages}`,
2013
- `Timetable Count: ${outputStats.timetables}`,
2014
- `Calendar Service ID Count: ${outputStats.calendars}`,
2015
- `Route Count: ${outputStats.routes}`,
2016
- `Trip Count: ${outputStats.trips}`,
2017
- `Stop Count: ${outputStats.stops}`
2018
- ];
2019
- for (const agency of config.agencies) {
2020
- if (agency.url) {
2021
- logText.push(`Source: ${agency.url}`);
2022
- } else if (agency.path) {
2023
- logText.push(`Source: ${agency.path}`);
2024
- }
2025
- }
2026
- if (outputStats.warnings.length > 0) {
2027
- logText.push("", "Warnings:", ...outputStats.warnings);
2028
- }
2029
- return logText.join("\n");
2030
- }
2031
- function log(config) {
2032
- if (config.verbose === false) {
2033
- return noop;
2034
- }
2035
- if (config.logFunction) {
2036
- return config.logFunction;
2037
- }
2038
- return (text, overwrite) => {
2039
- if (overwrite === true && process.stdout.isTTY) {
2040
- clearLine(process.stdout, 0);
2041
- cursorTo(process.stdout, 0);
2042
- } else {
2043
- process.stdout.write("\n");
2044
- }
2045
- process.stdout.write(text);
2046
- };
2047
- }
2048
- function logWarning(config) {
2049
- if (config.logFunction) {
2050
- return config.logFunction;
2051
- }
2052
- return (text) => {
2053
- process.stdout.write(`
2054
- ${formatWarning(text)}
2055
- `);
2056
- };
2057
- }
2058
- function logError(config) {
2059
- if (config.logFunction) {
2060
- return config.logFunction;
2061
- }
2062
- return (text) => {
2063
- process.stdout.write(`
2064
- ${formatError(text)}
2065
- `);
2066
- };
2067
- }
2068
- function formatWarning(text) {
2069
- const warningMessage = `${colors.underline("Warning")}: ${text}`;
2070
- return colors.yellow(warningMessage);
2071
- }
2072
- function formatError(error) {
2073
- const messageText = error instanceof Error ? error.message : error;
2074
- const errorMessage = `${colors.underline("Error")}: ${messageText.replace(
2075
- "Error: ",
2076
- ""
2077
- )}`;
2078
- return colors.red(errorMessage);
2079
- }
2080
- function logStats(stats, config) {
2081
- if (config.logFunction) {
2082
- return;
2083
- }
2084
- const table = new Table({
2085
- colWidths: [40, 20],
2086
- head: ["Item", "Count"]
2087
- });
2088
- table.push(
2089
- ["\u{1F4C4} Timetable Pages", stats.timetablePages],
2090
- ["\u{1F551} Timetables", stats.timetables],
2091
- ["\u{1F4C5} Calendar Service IDs", stats.calendars],
2092
- ["\u{1F504} Routes", stats.routes],
2093
- ["\u{1F68D} Trips", stats.trips],
2094
- ["\u{1F6D1} Stops", stats.stops],
2095
- ["\u26D4\uFE0F Warnings", stats.warnings.length]
2096
- );
2097
- config.log(table.toString());
2098
- }
2099
- var generateProgressBarString = (barTotal, barProgress, size2 = 40) => {
2100
- const line = "-";
2101
- const slider = "=";
2102
- if (!barTotal) {
2103
- throw new Error("Total value is either not provided or invalid");
2104
- }
2105
- if (!barProgress && barProgress !== 0) {
2106
- throw new Error("Current value is either not provided or invalid");
2107
- }
2108
- if (isNaN(barTotal)) {
2109
- throw new Error("Total value is not an integer");
2110
- }
2111
- if (isNaN(barProgress)) {
2112
- throw new Error("Current value is not an integer");
2113
- }
2114
- if (isNaN(size2)) {
2115
- throw new Error("Size is not an integer");
2116
- }
2117
- if (barProgress > barTotal) {
2118
- return slider.repeat(size2 + 2);
2119
- }
2120
- const percentage = barProgress / barTotal;
2121
- const progress = Math.round(size2 * percentage);
2122
- const emptyProgress = size2 - progress;
2123
- const progressText = slider.repeat(progress);
2124
- const emptyProgressText = line.repeat(emptyProgress);
2125
- return progressText + emptyProgressText;
2126
- };
2127
- function progressBar(formatString, barTotal, config) {
2128
- let barProgress = 0;
2129
- if (config.verbose === false) {
2130
- return {
2131
- increment: noop,
2132
- interrupt: noop
2133
- };
2134
- }
2135
- if (barTotal === 0) {
2136
- return null;
2137
- }
2138
- const renderProgressString = () => formatString.replace("{value}", barProgress).replace("{total}", barTotal).replace("{bar}", generateProgressBarString(barTotal, barProgress));
2139
- config.log(renderProgressString(), true);
2140
- return {
2141
- interrupt(text) {
2142
- config.logWarning(text);
2143
- config.log("");
2144
- },
2145
- increment() {
2146
- barProgress += 1;
2147
- config.log(renderProgressString(), true);
2148
- }
2149
- };
2150
- }
2151
-
2152
2177
  // src/lib/gtfs-to-html.ts
2153
2178
  import path from "node:path";
2154
2179
  import { mkdir as mkdir2, writeFile } from "node:fs/promises";
@@ -2160,9 +2185,6 @@ import untildify2 from "untildify";
2160
2185
  var gtfsToHtml = async (initialConfig) => {
2161
2186
  const config = setDefaultConfig(initialConfig);
2162
2187
  const timer = new Timer();
2163
- config.log = log(config);
2164
- config.logWarning = logWarning(config);
2165
- config.logError = logError(config);
2166
2188
  const agencyKey = config.agencies.map(
2167
2189
  (agency) => agency.agencyKey ?? agency.agency_key ?? "unknown"
2168
2190
  ).join("-");
@@ -2173,7 +2195,7 @@ var gtfsToHtml = async (initialConfig) => {
2173
2195
  openDb2(config);
2174
2196
  } catch (error) {
2175
2197
  if (error?.code === "SQLITE_CANTOPEN") {
2176
- config.logError(
2198
+ logError(config)(
2177
2199
  `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
2178
2200
  );
2179
2201
  }
@@ -2281,12 +2303,12 @@ var gtfsToHtml = async (initialConfig) => {
2281
2303
  outputPath,
2282
2304
  config.zipOutput ? "/timetables.zip" : ""
2283
2305
  );
2284
- config.log(
2306
+ log(config)(
2285
2307
  `${agencyKey}: ${config.outputFormat.toUpperCase()} timetables created at ${fullOutputPath}`
2286
2308
  );
2287
- logStats(stats, config);
2309
+ logStats(config)(stats);
2288
2310
  const seconds = Math.round(timer.time() / 1e3);
2289
- config.log(
2311
+ log(config)(
2290
2312
  `${agencyKey}: ${config.outputFormat.toUpperCase()} timetable generation required ${seconds} seconds`
2291
2313
  );
2292
2314
  timer.stop();