gtfs-to-html 2.5.7 → 2.5.9

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/CHANGELOG.md CHANGED
@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.5.9] - 2024-01-24
9
+
10
+ ### Updated
11
+
12
+ - Move stop markers above map labels
13
+ - Update to Mapbox GL JS v3
14
+ - Dependency updates
15
+
16
+ ### Fixed
17
+
18
+ - Handle routes with no route_short_name in map marker popups
19
+
20
+ ## [2.5.8] - 2024-01-02
21
+
22
+ ### Fixed
23
+
24
+ - Improved warning logging
25
+
26
+ ### Added
27
+
28
+ - Handle case where a calendar_date is both included and excluded
29
+
30
+ ### Updated
31
+
32
+ - Dependency updates
33
+
8
34
  ## [2.5.7] - 2023-11-07
9
35
 
10
36
  ### Updated
@@ -51,7 +51,7 @@ const gtfsToHtml = async (initialConfig) => {
51
51
  } catch (error) {
52
52
  if (error instanceof Error && error.code === 'SQLITE_CANTOPEN') {
53
53
  config.logError(
54
- `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
54
+ `Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`,
55
55
  );
56
56
  }
57
57
 
@@ -84,7 +84,7 @@ const gtfsToHtml = async (initialConfig) => {
84
84
  const timetablePages = [];
85
85
  const timetablePageIds = map(
86
86
  getTimetablePagesForAgency(config),
87
- 'timetable_page_id'
87
+ 'timetable_page_id',
88
88
  );
89
89
  await prepDirectory(exportPath);
90
90
 
@@ -95,7 +95,7 @@ const gtfsToHtml = async (initialConfig) => {
95
95
  const bar = progressBar(
96
96
  `${agencyKey}: Generating ${config.outputFormat.toUpperCase()} timetables {bar} {value}/{total}`,
97
97
  timetablePageIds.length,
98
- config
98
+ config,
99
99
  );
100
100
 
101
101
  /* eslint-disable no-await-in-loop */
@@ -103,15 +103,9 @@ const gtfsToHtml = async (initialConfig) => {
103
103
  try {
104
104
  const timetablePage = await getFormattedTimetablePage(
105
105
  timetablePageId,
106
- config
106
+ config,
107
107
  );
108
108
 
109
- if (timetablePage.consolidatedTimetables.length === 0) {
110
- throw new Error(
111
- `No timetables found for timetable_page_id=${timetablePage.timetable_page_id}`
112
- );
113
- }
114
-
115
109
  for (const timetable of timetablePage.timetables) {
116
110
  for (const warning of timetable.warnings) {
117
111
  outputStats.warnings.push(warning);
@@ -119,6 +113,12 @@ const gtfsToHtml = async (initialConfig) => {
119
113
  }
120
114
  }
121
115
 
116
+ if (timetablePage.consolidatedTimetables.length === 0) {
117
+ throw new Error(
118
+ `No timetables found for timetable_page_id=${timetablePage.timetable_page_id}`,
119
+ );
120
+ }
121
+
122
122
  outputStats.timetables += timetablePage.consolidatedTimetables.length;
123
123
  outputStats.timetablePages += 1;
124
124
 
@@ -130,7 +130,7 @@ const gtfsToHtml = async (initialConfig) => {
130
130
 
131
131
  timetablePage.relativePath = path.join(
132
132
  datePath,
133
- sanitize(timetablePage.filename)
133
+ sanitize(timetablePage.filename),
134
134
  );
135
135
 
136
136
  if (config.outputFormat === 'csv') {
@@ -139,7 +139,7 @@ const gtfsToHtml = async (initialConfig) => {
139
139
  const csvPath = path.join(
140
140
  exportPath,
141
141
  datePath,
142
- generateCSVFileName(timetable, timetablePage)
142
+ generateCSVFileName(timetable, timetablePage),
143
143
  );
144
144
  await writeFile(csvPath, csv);
145
145
  }
@@ -148,7 +148,7 @@ const gtfsToHtml = async (initialConfig) => {
148
148
  const htmlPath = path.join(
149
149
  exportPath,
150
150
  datePath,
151
- sanitize(timetablePage.filename)
151
+ sanitize(timetablePage.filename),
152
152
  );
153
153
  await writeFile(htmlPath, html);
154
154
 
@@ -192,19 +192,19 @@ const gtfsToHtml = async (initialConfig) => {
192
192
 
193
193
  const fullExportPath = path.join(
194
194
  exportPath,
195
- config.zipOutput ? '/timetables.zip' : ''
195
+ config.zipOutput ? '/timetables.zip' : '',
196
196
  );
197
197
 
198
198
  // Print stats
199
199
  config.log(
200
- `${agencyKey}: ${config.outputFormat.toUpperCase()} timetables created at ${fullExportPath}`
200
+ `${agencyKey}: ${config.outputFormat.toUpperCase()} timetables created at ${fullExportPath}`,
201
201
  );
202
202
 
203
203
  logStats(outputStats, config);
204
204
 
205
205
  const seconds = Math.round(timer.time() / 1000);
206
206
  config.log(
207
- `${agencyKey}: ${config.outputFormat.toUpperCase()} timetable generation required ${seconds} seconds`
207
+ `${agencyKey}: ${config.outputFormat.toUpperCase()} timetable generation required ${seconds} seconds`,
208
208
  );
209
209
 
210
210
  timer.stop();
package/lib/log-utils.js CHANGED
@@ -110,7 +110,7 @@ export function formatError(error) {
110
110
  const messageText = error instanceof Error ? error.message : error;
111
111
  const errorMessage = `${colors.underline('Error')}: ${messageText.replace(
112
112
  'Error: ',
113
- ''
113
+ '',
114
114
  )}`;
115
115
  return colors.red(errorMessage);
116
116
  }
@@ -136,7 +136,7 @@ export function logStats(stats, config) {
136
136
  ['🔄 Routes', stats.routes],
137
137
  ['🚍 Trips', stats.trips],
138
138
  ['🛑 Stops', stats.stops],
139
- ['⛔️ Warnings', stats.warnings.length]
139
+ ['⛔️ Warnings', stats.warnings.length],
140
140
  );
141
141
 
142
142
  config.log(table.toString());
@@ -209,7 +209,7 @@ export function progressBar(formatString, barTotal, config) {
209
209
  interrupt(text) {
210
210
  // Log two lines to avoid overwrite by progress bar
211
211
  config.logWarning(text);
212
- config.logWarning('');
212
+ config.log('');
213
213
  },
214
214
  increment() {
215
215
  barProgress += 1;
package/lib/utils.js CHANGED
@@ -292,26 +292,32 @@ const getCalendarDatesForTimetable = (timetable, config) => {
292
292
  );
293
293
  const start = fromGTFSDate(timetable.start_date);
294
294
  const end = fromGTFSDate(timetable.end_date);
295
- const filteredCalendarDates = {
296
- excludedDates: [],
297
- includedDates: [],
298
- };
295
+ const excludedDates = [];
296
+ const includedDates = [];
299
297
 
300
298
  for (const calendarDate of calendarDates) {
301
299
  if (moment(calendarDate.date, 'YYYYMMDD').isBetween(start, end)) {
302
300
  if (calendarDate.exception_type === 1) {
303
- filteredCalendarDates.includedDates.push(
304
- formatDate(calendarDate, config.dateFormat),
305
- );
301
+ includedDates.push(formatDate(calendarDate, config.dateFormat));
306
302
  } else if (calendarDate.exception_type === 2) {
307
- filteredCalendarDates.excludedDates.push(
308
- formatDate(calendarDate, config.dateFormat),
309
- );
303
+ excludedDates.push(formatDate(calendarDate, config.dateFormat));
310
304
  }
311
305
  }
312
306
  }
313
307
 
314
- return filteredCalendarDates;
308
+ // Remove dates that are both included and excluded from both lists
309
+ const includedAndExcludedDates = excludedDates.filter((date) =>
310
+ includedDates.includes(date),
311
+ );
312
+
313
+ return {
314
+ excludedDates: excludedDates.filter(
315
+ (date) => !includedAndExcludedDates.includes(date),
316
+ ),
317
+ includedDates: includedDates.filter(
318
+ (date) => !includedAndExcludedDates.includes(date),
319
+ ),
320
+ };
315
321
  };
316
322
 
317
323
  /*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtfs-to-html",
3
- "version": "2.5.7",
3
+ "version": "2.5.9",
4
4
  "private": false,
5
5
  "description": "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
6
6
  "keywords": [
@@ -40,16 +40,16 @@
40
40
  "archiver": "^6.0.1",
41
41
  "cli-table": "^0.3.11",
42
42
  "copy-dir": "^1.3.0",
43
- "csv-stringify": "^6.4.4",
43
+ "csv-stringify": "^6.4.5",
44
44
  "express": "^4.18.2",
45
- "gtfs": "^4.5.0",
45
+ "gtfs": "^4.6.0",
46
46
  "js-beautify": "^1.14.11",
47
47
  "lodash-es": "^4.17.21",
48
- "moment": "^2.29.4",
48
+ "moment": "^2.30.1",
49
49
  "morgan": "^1.10.0",
50
50
  "pretty-error": "^4.0.0",
51
51
  "pug": "^3.0.2",
52
- "puppeteer": "^21.5.0",
52
+ "puppeteer": "^21.9.0",
53
53
  "sanitize-filename": "^1.6.3",
54
54
  "sqlstring": "^2.3.3",
55
55
  "timer-machine": "^1.1.0",
@@ -60,8 +60,8 @@
60
60
  },
61
61
  "devDependencies": {
62
62
  "husky": "^8.0.3",
63
- "lint-staged": "^15.0.2",
64
- "prettier": "^3.0.3"
63
+ "lint-staged": "^15.2.0",
64
+ "prettier": "^3.2.4"
65
65
  },
66
66
  "engines": {
67
67
  "node": ">= 18.0.0"
@@ -3,6 +3,17 @@
3
3
 
4
4
  const maps = {};
5
5
 
6
+ function formatRouteName(route) {
7
+ let routeName = '';
8
+ if (route.route_short_name !== undefined) {
9
+ routeName += route.route_short_name;
10
+ } else if (route.route_long_name !== undefined) {
11
+ routeName += route.route_long_name;
12
+ }
13
+
14
+ return routeName;
15
+ }
16
+
6
17
  function formatRoute(route) {
7
18
  const html = route.route_url
8
19
  ? $('<a>').attr('href', route.route_url)
@@ -17,9 +28,7 @@ function formatRoute(route) {
17
28
  .appendTo(html);
18
29
  }
19
30
 
20
- $('<span>')
21
- .text(`${route.route_short_name} ${route.route_long_name}`)
22
- .appendTo(html);
31
+ $('<span>').text(formatRouteName(route)).appendTo(html);
23
32
 
24
33
  return html.prop('outerHTML');
25
34
  }
@@ -107,10 +116,10 @@ function createSystemMap(id, geojson) {
107
116
  duration: 0,
108
117
  });
109
118
 
110
- // Turn of Points of Interest labels
119
+ // Turn off Points of Interest labels
111
120
  map.setLayoutProperty('poi-label', 'visibility', 'none');
112
121
 
113
- // Find the index of the first symbol layer in the map style
122
+ // Find the index of the first symbol layer in the map style to put the route lines underneath
114
123
  let firstSymbolId;
115
124
  for (const layer of map.getStyle().layers) {
116
125
  if (layer.type === 'symbol') {
@@ -149,7 +158,7 @@ function createSystemMap(id, geojson) {
149
158
  layout: lineLayout,
150
159
  filter: ['!has', 'stop_id'],
151
160
  },
152
- firstSymbolId
161
+ firstSymbolId,
153
162
  );
154
163
 
155
164
  // Add highlighted route drop shadow outlines next
@@ -182,7 +191,7 @@ function createSystemMap(id, geojson) {
182
191
  layout: lineLayout,
183
192
  filter: ['==', ['get', 'route_id'], 'none'],
184
193
  },
185
- firstSymbolId
194
+ firstSymbolId,
186
195
  );
187
196
 
188
197
  // Add white outlines to routes next
@@ -208,7 +217,7 @@ function createSystemMap(id, geojson) {
208
217
  layout: lineLayout,
209
218
  filter: ['has', 'route_id'],
210
219
  },
211
- firstSymbolId
220
+ firstSymbolId,
212
221
  );
213
222
 
214
223
  // Add highlighted route white outlines next
@@ -234,7 +243,7 @@ function createSystemMap(id, geojson) {
234
243
  layout: lineLayout,
235
244
  filter: ['==', ['get', 'route_id'], 'none'],
236
245
  },
237
- firstSymbolId
246
+ firstSymbolId,
238
247
  );
239
248
 
240
249
  // Add route lines next
@@ -260,7 +269,7 @@ function createSystemMap(id, geojson) {
260
269
  layout: lineLayout,
261
270
  filter: ['has', 'route_id'],
262
271
  },
263
- firstSymbolId
272
+ firstSymbolId,
264
273
  );
265
274
 
266
275
  // Add highlighted route lines next
@@ -286,96 +295,74 @@ function createSystemMap(id, geojson) {
286
295
  layout: lineLayout,
287
296
  filter: ['==', ['get', 'route_id'], 'none'],
288
297
  },
289
- firstSymbolId
298
+ firstSymbolId,
290
299
  );
291
300
 
292
301
  // Add stops when zoomed in
293
- map.addLayer(
294
- {
295
- id: 'stops',
296
- type: 'circle',
297
- source: {
298
- type: 'geojson',
299
- data: geojson,
300
- },
301
- paint: {
302
- 'circle-color': '#fff',
303
- 'circle-radius': {
304
- base: 1.75,
305
- stops: [
306
- [12, 4],
307
- [22, 100],
308
- ],
309
- },
310
- 'circle-stroke-color': '#3F4A5C',
311
- 'circle-stroke-width': 2,
312
- 'circle-opacity': [
313
- 'interpolate',
314
- ['linear'],
315
- ['zoom'],
316
- 13,
317
- 0,
318
- 13.5,
319
- 1,
320
- ],
321
- 'circle-stroke-opacity': [
322
- 'interpolate',
323
- ['linear'],
324
- ['zoom'],
325
- 13,
326
- 0,
327
- 13.5,
328
- 1,
302
+ map.addLayer({
303
+ id: 'stops',
304
+ type: 'circle',
305
+ source: {
306
+ type: 'geojson',
307
+ data: geojson,
308
+ },
309
+ paint: {
310
+ 'circle-color': '#fff',
311
+ 'circle-radius': {
312
+ base: 1.75,
313
+ stops: [
314
+ [12, 4],
315
+ [22, 100],
329
316
  ],
330
317
  },
331
- filter: ['has', 'stop_id'],
318
+ 'circle-stroke-color': '#3F4A5C',
319
+ 'circle-stroke-width': 2,
320
+ 'circle-opacity': ['interpolate', ['linear'], ['zoom'], 13, 0, 13.5, 1],
321
+ 'circle-stroke-opacity': [
322
+ 'interpolate',
323
+ ['linear'],
324
+ ['zoom'],
325
+ 13,
326
+ 0,
327
+ 13.5,
328
+ 1,
329
+ ],
332
330
  },
333
- firstSymbolId
334
- );
331
+ filter: ['has', 'stop_id'],
332
+ });
335
333
 
336
334
  // Layer for highlighted stops
337
- map.addLayer(
338
- {
339
- id: 'stops-highlighted',
340
- type: 'circle',
341
- source: {
342
- type: 'geojson',
343
- data: geojson,
344
- },
345
- paint: {
346
- 'circle-color': '#fff',
347
- 'circle-radius': {
348
- base: 1.75,
349
- stops: [
350
- [12, 5],
351
- [22, 125],
352
- ],
353
- },
354
- 'circle-stroke-width': 2,
355
- 'circle-stroke-color': '#3f4a5c',
356
- 'circle-opacity': [
357
- 'interpolate',
358
- ['linear'],
359
- ['zoom'],
360
- 13,
361
- 0,
362
- 13.5,
363
- 1,
364
- ],
365
- 'circle-stroke-opacity': [
366
- 'interpolate',
367
- ['linear'],
368
- ['zoom'],
369
- 13,
370
- 0,
371
- 13.5,
372
- 1,
335
+ map.addLayer({
336
+ id: 'stops-highlighted',
337
+ type: 'circle',
338
+ source: {
339
+ type: 'geojson',
340
+ data: geojson,
341
+ },
342
+ paint: {
343
+ 'circle-color': '#fff',
344
+ 'circle-radius': {
345
+ base: 1.75,
346
+ stops: [
347
+ [12, 5],
348
+ [22, 125],
373
349
  ],
374
350
  },
375
- filter: ['==', 'stop_id', ''],
351
+ 'circle-stroke-width': 2,
352
+ 'circle-stroke-color': '#3f4a5c',
353
+ 'circle-opacity': ['interpolate', ['linear'], ['zoom'], 13, 0, 13.5, 1],
354
+ 'circle-stroke-opacity': [
355
+ 'interpolate',
356
+ ['linear'],
357
+ ['zoom'],
358
+ 13,
359
+ 0,
360
+ 13.5,
361
+ 1,
362
+ ],
376
363
  },
377
- firstSymbolId
378
- );
364
+ filter: ['==', 'stop_id', ''],
365
+ });
379
366
 
380
367
  // Add labels
381
368
  map.addLayer({
@@ -406,14 +393,14 @@ function createSystemMap(id, geojson) {
406
393
  map.getCanvas().style.cursor = 'pointer';
407
394
  highlightRoutes(
408
395
  _.compact(
409
- _.uniq(features.map((feature) => feature.properties.route_id))
410
- )
396
+ _.uniq(features.map((feature) => feature.properties.route_id)),
397
+ ),
411
398
  );
412
399
 
413
400
  if (features.some((feature) => feature.layer.id === 'stops')) {
414
401
  highlightStop(
415
402
  features.find((feature) => feature.layer.id === 'stops').properties
416
- .stop_id
403
+ .stop_id,
417
404
  );
418
405
  }
419
406
  } else {
@@ -451,10 +438,10 @@ function createSystemMap(id, geojson) {
451
438
  const routes = _.orderBy(
452
439
  _.uniqBy(
453
440
  routeFeatures,
454
- (feature) => feature.properties.route_short_name
441
+ (feature) => feature.properties.route_short_name,
455
442
  ),
456
443
  (feature) =>
457
- Number.parseInt(feature.properties.route_short_name, 10)
444
+ Number.parseInt(feature.properties.route_short_name, 10),
458
445
  );
459
446
 
460
447
  new mapboxgl.Popup()
@@ -505,11 +492,11 @@ function createSystemMap(id, geojson) {
505
492
  map.setPaintProperty(
506
493
  'route-line-shadows',
507
494
  'line-opacity',
508
- routeLineOpacity
495
+ routeLineOpacity,
509
496
  );
510
497
 
511
498
  const highlightedFeatures = geojson.features.filter((feature) =>
512
- routeIds.includes(feature.properties.route_id)
499
+ routeIds.includes(feature.properties.route_id),
513
500
  );
514
501
 
515
502
  if (highlightedFeatures.length > 0 && zoom) {
@@ -546,7 +533,7 @@ function createSystemMap(id, geojson) {
546
533
  map.setPaintProperty(
547
534
  'route-line-shadows',
548
535
  'line-opacity',
549
- routeLineOpacity
536
+ routeLineOpacity,
550
537
  );
551
538
 
552
539
  if (zoom) {
@@ -566,7 +553,7 @@ function createSystemMap(id, geojson) {
566
553
 
567
554
  $('.overview-list').hover(
568
555
  () => {},
569
- () => unHighlightRoutes(true)
556
+ () => unHighlightRoutes(true),
570
557
  );
571
558
  });
572
559
  });