gtfs-to-html 2.6.12 → 2.7.0
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 +14 -0
- package/app/index.js +14 -9
- package/lib/file-utils.js +11 -16
- package/lib/formatters.js +2 -43
- package/lib/gtfs-to-html.js +2 -2
- package/lib/utils.js +0 -2
- package/package.json +3 -3
- package/{public → views/default}/css/overview_styles.css +1 -1
- package/{public → views/default}/css/timetable_styles.css +16 -10
- package/views/default/formatting_functions.pug +11 -3
- package/views/default/overview.pug +3 -2
- package/views/default/timetablepage.pug +3 -2
- package/www/docs/configuration.md +2 -2
- package/www/docs/timetables.md +2 -2
- /package/{public → views/default}/css/timetable_pdf_styles.css +0 -0
- /package/{public → views/default}/js/system-map.js +0 -0
- /package/{public → views/default}/js/timetable-map.js +0 -0
- /package/{public → views/default}/js/timetable-menu.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ 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.7.0] - 2024-07-27
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Fixes for horizontal orientation labels
|
|
12
|
+
|
|
13
|
+
### Updated
|
|
14
|
+
- Better date format documentation
|
|
15
|
+
- Better default for timetable_page_label
|
|
16
|
+
- Larger map on desktop
|
|
17
|
+
- Improved button styles
|
|
18
|
+
- Serve static assets from templatePath
|
|
19
|
+
- Move static js and css to views/default
|
|
20
|
+
- Dependency updates
|
|
21
|
+
|
|
8
22
|
## [2.6.12] - 2024-07-26
|
|
9
23
|
|
|
10
24
|
### Updated
|
package/app/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import { readFileSync } from 'node:fs';
|
|
|
4
4
|
import { map } from 'lodash-es';
|
|
5
5
|
import yargs from 'yargs';
|
|
6
6
|
import { openDb } from 'gtfs';
|
|
7
|
-
|
|
8
7
|
import express from 'express';
|
|
9
8
|
import logger from 'morgan';
|
|
9
|
+
import untildify from 'untildify';
|
|
10
10
|
|
|
11
11
|
import { formatTimetableLabel } from '../lib/formatters.js';
|
|
12
12
|
import {
|
|
@@ -44,7 +44,7 @@ try {
|
|
|
44
44
|
} catch (error) {
|
|
45
45
|
if (error instanceof Error && error.code === 'SQLITE_CANTOPEN') {
|
|
46
46
|
config.logError(
|
|
47
|
-
`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json
|
|
47
|
+
`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`,
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -59,14 +59,14 @@ router.get('/', async (request, response, next) => {
|
|
|
59
59
|
const timetablePages = [];
|
|
60
60
|
const timetablePageIds = map(
|
|
61
61
|
getTimetablePagesForAgency(config),
|
|
62
|
-
'timetable_page_id'
|
|
62
|
+
'timetable_page_id',
|
|
63
63
|
);
|
|
64
64
|
|
|
65
65
|
for (const timetablePageId of timetablePageIds) {
|
|
66
66
|
// eslint-disable-next-line no-await-in-loop
|
|
67
67
|
const timetablePage = await getFormattedTimetablePage(
|
|
68
68
|
timetablePageId,
|
|
69
|
-
config
|
|
69
|
+
config,
|
|
70
70
|
);
|
|
71
71
|
|
|
72
72
|
if (
|
|
@@ -74,7 +74,7 @@ router.get('/', async (request, response, next) => {
|
|
|
74
74
|
timetablePage.consolidatedTimetables.length === 0
|
|
75
75
|
) {
|
|
76
76
|
console.error(
|
|
77
|
-
`No timetables found for timetable_page_id=${timetablePage.timetable_page_id}
|
|
77
|
+
`No timetables found for timetable_page_id=${timetablePage.timetable_page_id}`,
|
|
78
78
|
);
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -107,7 +107,7 @@ router.get('/timetables/:timetablePageId', async (request, response, next) => {
|
|
|
107
107
|
try {
|
|
108
108
|
const timetablePage = await getFormattedTimetablePage(
|
|
109
109
|
timetablePageId,
|
|
110
|
-
config
|
|
110
|
+
config,
|
|
111
111
|
);
|
|
112
112
|
|
|
113
113
|
const html = await generateTimetableHTML(timetablePage, config);
|
|
@@ -121,9 +121,14 @@ app.set('views', path.join(fileURLToPath(import.meta.url), '../../views'));
|
|
|
121
121
|
app.set('view engine', 'pug');
|
|
122
122
|
|
|
123
123
|
app.use(logger('dev'));
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
|
|
125
|
+
// Serve static assets
|
|
126
|
+
const staticAssetPath =
|
|
127
|
+
config.templatePath === undefined
|
|
128
|
+
? path.join(fileURLToPath(import.meta.url), '../../views/default')
|
|
129
|
+
: untildify(config.templatePath);
|
|
130
|
+
|
|
131
|
+
app.use(express.static(staticAssetPath));
|
|
127
132
|
|
|
128
133
|
app.use('/', router);
|
|
129
134
|
app.set('port', process.env.PORT || 3000);
|
package/lib/file-utils.js
CHANGED
|
@@ -66,18 +66,12 @@ function getTemplatePath(templateFileName, config) {
|
|
|
66
66
|
fullTemplateFileName += '_full';
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
);
|
|
74
|
-
}
|
|
69
|
+
const templatePath =
|
|
70
|
+
config.templatePath === undefined
|
|
71
|
+
? path.join(fileURLToPath(import.meta.url), '../../views/default')
|
|
72
|
+
: untildify(config.templatePath);
|
|
75
73
|
|
|
76
|
-
return path.join(
|
|
77
|
-
fileURLToPath(import.meta.url),
|
|
78
|
-
'../../views/default',
|
|
79
|
-
`${fullTemplateFileName}.pug`,
|
|
80
|
-
);
|
|
74
|
+
return path.join(templatePath, `${fullTemplateFileName}.pug`);
|
|
81
75
|
}
|
|
82
76
|
|
|
83
77
|
/*
|
|
@@ -101,11 +95,12 @@ export async function prepDirectory(exportPath) {
|
|
|
101
95
|
/*
|
|
102
96
|
* Copy needed CSS and JS to export path.
|
|
103
97
|
*/
|
|
104
|
-
export function copyStaticAssets(exportPath) {
|
|
105
|
-
const staticAssetPath =
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
export function copyStaticAssets(config, exportPath) {
|
|
99
|
+
const staticAssetPath =
|
|
100
|
+
config.templatePath === undefined
|
|
101
|
+
? path.join(fileURLToPath(import.meta.url), '../../views/default')
|
|
102
|
+
: untildify(config.templatePath);
|
|
103
|
+
|
|
109
104
|
copydir.sync(path.join(staticAssetPath, 'css'), path.join(exportPath, 'css'));
|
|
110
105
|
copydir.sync(path.join(staticAssetPath, 'js'), path.join(exportPath, 'js'));
|
|
111
106
|
}
|
package/lib/formatters.js
CHANGED
|
@@ -247,20 +247,6 @@ export function formatTrip(trip, timetable, calendars, config) {
|
|
|
247
247
|
return trip;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
/*
|
|
251
|
-
* Format a route name.
|
|
252
|
-
*/
|
|
253
|
-
export function formatRouteName(route) {
|
|
254
|
-
let routeName = 'Route ';
|
|
255
|
-
if (!isNullOrEmpty(route.route_short_name)) {
|
|
256
|
-
routeName += route.route_short_name;
|
|
257
|
-
} else if (!isNullOrEmpty(route.route_long_name)) {
|
|
258
|
-
routeName += route.route_long_name;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return routeName;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
250
|
/*
|
|
265
251
|
* Format a frequency.
|
|
266
252
|
*/
|
|
@@ -384,7 +370,7 @@ export function formatStopName(stop) {
|
|
|
384
370
|
}
|
|
385
371
|
|
|
386
372
|
/*
|
|
387
|
-
* Formats trip "
|
|
373
|
+
* Formats trip "Continues from".
|
|
388
374
|
*/
|
|
389
375
|
export function formatTripContinuesFrom(trip) {
|
|
390
376
|
return trip.continues_from_route
|
|
@@ -393,7 +379,7 @@ export function formatTripContinuesFrom(trip) {
|
|
|
393
379
|
}
|
|
394
380
|
|
|
395
381
|
/*
|
|
396
|
-
* Formats trip "
|
|
382
|
+
* Formats trip "Continues as".
|
|
397
383
|
*/
|
|
398
384
|
export function formatTripContinuesAs(trip) {
|
|
399
385
|
return trip.continues_as_route
|
|
@@ -500,33 +486,6 @@ export function formatTimetableLabel(timetable) {
|
|
|
500
486
|
return timetableLabel;
|
|
501
487
|
}
|
|
502
488
|
|
|
503
|
-
/*
|
|
504
|
-
* Format a label for a timetable page.
|
|
505
|
-
*/
|
|
506
|
-
export function formatTimetablePageLabel(timetablePage) {
|
|
507
|
-
if (!isNullOrEmpty(timetablePage.timetable_page_label)) {
|
|
508
|
-
return timetablePage.timetable_page_label;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (
|
|
512
|
-
timetablePage.consolidatedTimetables &&
|
|
513
|
-
timetablePage.consolidatedTimetables.length > 0
|
|
514
|
-
) {
|
|
515
|
-
// Use route names from all timetables
|
|
516
|
-
const routes = uniqBy(
|
|
517
|
-
flatMap(
|
|
518
|
-
timetablePage.consolidatedTimetables,
|
|
519
|
-
(timetable) => timetable.routes,
|
|
520
|
-
),
|
|
521
|
-
'route_id',
|
|
522
|
-
);
|
|
523
|
-
|
|
524
|
-
return routes.map((route) => formatRouteName(route)).join(' and ');
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return 'Unknown';
|
|
528
|
-
}
|
|
529
|
-
|
|
530
489
|
/*
|
|
531
490
|
* Merge timetables with same `timetable_id`.
|
|
532
491
|
*/
|
package/lib/gtfs-to-html.js
CHANGED
|
@@ -89,7 +89,7 @@ const gtfsToHtml = async (initialConfig) => {
|
|
|
89
89
|
await prepDirectory(exportPath);
|
|
90
90
|
|
|
91
91
|
if (config.noHead !== true && ['html', 'pdf'].includes(config.outputFormat)) {
|
|
92
|
-
copyStaticAssets(exportPath);
|
|
92
|
+
copyStaticAssets(config, exportPath);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
const bar = progressBar(
|
|
@@ -134,7 +134,7 @@ const gtfsToHtml = async (initialConfig) => {
|
|
|
134
134
|
);
|
|
135
135
|
|
|
136
136
|
if (config.outputFormat === 'csv') {
|
|
137
|
-
for (const timetable of timetablePage.
|
|
137
|
+
for (const timetable of timetablePage.consolidatedTimetables) {
|
|
138
138
|
const csv = await generateTimetableCSV(timetable);
|
|
139
139
|
const csvPath = path.join(
|
|
140
140
|
exportPath,
|
package/lib/utils.js
CHANGED
|
@@ -58,7 +58,6 @@ import {
|
|
|
58
58
|
formatStops,
|
|
59
59
|
formatTimetableId,
|
|
60
60
|
formatTimetableLabel,
|
|
61
|
-
formatTimetablePageLabel,
|
|
62
61
|
formatTrip,
|
|
63
62
|
formatTripContinuesAs,
|
|
64
63
|
formatTripContinuesFrom,
|
|
@@ -1505,7 +1504,6 @@ export function getFormattedTimetablePage(timetablePageId, config) {
|
|
|
1505
1504
|
timetablePage.timetables,
|
|
1506
1505
|
config,
|
|
1507
1506
|
);
|
|
1508
|
-
timetablePage.timetable_page_label = formatTimetablePageLabel(timetablePage);
|
|
1509
1507
|
timetablePage.dayList = formatDays(
|
|
1510
1508
|
getDaysFromCalendars(timetablePage.consolidatedTimetables),
|
|
1511
1509
|
config,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gtfs-to-html",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
|
|
6
6
|
"keywords": [
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"archiver": "^7.0.1",
|
|
41
41
|
"cli-table": "^0.3.11",
|
|
42
42
|
"copy-dir": "^1.3.0",
|
|
43
|
-
"csv-stringify": "^6.5.
|
|
43
|
+
"csv-stringify": "^6.5.1",
|
|
44
44
|
"express": "^4.19.2",
|
|
45
|
-
"gtfs": "^4.13.
|
|
45
|
+
"gtfs": "^4.13.2",
|
|
46
46
|
"insane": "^2.6.2",
|
|
47
47
|
"js-beautify": "^1.15.1",
|
|
48
48
|
"lodash-es": "^4.17.21",
|
|
@@ -180,7 +180,7 @@ a:hover {
|
|
|
180
180
|
|
|
181
181
|
.timetable-page .btn-blue {
|
|
182
182
|
color: rgb(255 255 255);
|
|
183
|
-
padding: 0.75rem
|
|
183
|
+
padding: 0.75rem 1.5rem;
|
|
184
184
|
background-color: rgb(37 99 235);
|
|
185
185
|
border-radius: 0.375rem;
|
|
186
186
|
justify-content: center;
|
|
@@ -197,7 +197,7 @@ a:hover {
|
|
|
197
197
|
|
|
198
198
|
.timetable-page .btn-gray {
|
|
199
199
|
color: rgb(75 85 99);
|
|
200
|
-
padding: 0.75rem
|
|
200
|
+
padding: 0.75rem 1.5rem;
|
|
201
201
|
background-color: rgb(209 213 219);
|
|
202
202
|
border-radius: 0.375rem;
|
|
203
203
|
justify-content: center;
|
|
@@ -221,7 +221,7 @@ a:hover {
|
|
|
221
221
|
margin-bottom: 2.5rem;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
.timetable-page .timetable .stop-header {
|
|
224
|
+
.timetable-page .timetable .table-vertical .stop-header {
|
|
225
225
|
text-align: center;
|
|
226
226
|
}
|
|
227
227
|
|
|
@@ -354,7 +354,13 @@ a:hover {
|
|
|
354
354
|
}
|
|
355
355
|
|
|
356
356
|
.timetable-page .table-horizontal tbody tr th.stop-name-container {
|
|
357
|
-
min-width:
|
|
357
|
+
min-width: 175px;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
@media screen and (min-width: 768px) {
|
|
361
|
+
.timetable-page .table-horizontal tbody tr th.stop-name-container {
|
|
362
|
+
min-width: 250px;
|
|
363
|
+
}
|
|
358
364
|
}
|
|
359
365
|
|
|
360
366
|
.timetable-page .table-hourly {
|
|
@@ -392,18 +398,18 @@ a:hover {
|
|
|
392
398
|
flex-shrink: 0;
|
|
393
399
|
}
|
|
394
400
|
|
|
395
|
-
@media screen and (max-width: 767px) {
|
|
396
|
-
.timetable-page .table-horizontal tbody tr th.stop-name-container {
|
|
397
|
-
min-width: 175px;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
401
|
/* Map Styles */
|
|
402
402
|
|
|
403
403
|
.timetable-page .map {
|
|
404
404
|
min-height: 350px;
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
+
@media screen and (min-width: 768px) {
|
|
408
|
+
.timetable-page .map {
|
|
409
|
+
min-height: 450px;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
407
413
|
.timetable-page .map .mapboxgl-popup-content .popup-title {
|
|
408
414
|
margin: 0 20px 5px 0;
|
|
409
415
|
font-size: 1rem;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
-
|
|
2
2
|
function getTimetableSummary(timetable) {
|
|
3
3
|
let summary = `This table shows schedules for a selection of key stops on the route for ${timetable.timetable_label} ${timetable.dayList}.`;
|
|
4
|
-
if (timetable.orientation
|
|
4
|
+
if (timetable.orientation === 'vertical') {
|
|
5
5
|
summary += ' Stops and their schedule times are listed in the columns.';
|
|
6
|
-
} else if (timetable.orientation
|
|
6
|
+
} else if (timetable.orientation === 'horizontal') {
|
|
7
7
|
summary += ' Schedule times are listed in rows, starting with the stop name in the first cell of the row.';
|
|
8
|
-
} else if (timetable.orientation
|
|
8
|
+
} else if (timetable.orientation === 'hourly') {
|
|
9
9
|
summary += ' Schedule times are listed in rows, starting with the stop name in the first cell of the row and the minutes after the hour in the second row.';
|
|
10
10
|
}
|
|
11
11
|
return summary;
|
|
@@ -98,3 +98,11 @@
|
|
|
98
98
|
|
|
99
99
|
return ''
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
function formatRouteName(route) {
|
|
103
|
+
if (isNullOrEmpty(route.route_long_name)) {
|
|
104
|
+
return `Route ${route.route_short_name}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return route.route_long_name;
|
|
108
|
+
}
|
|
@@ -10,10 +10,11 @@ include formatting_functions.pug
|
|
|
10
10
|
each timetablePage in timetablePageGroup.timetablePages
|
|
11
11
|
if config.allowEmptyTimetables || timetablePage.consolidatedTimetables.length > 0
|
|
12
12
|
a.timetable-page-link(href=`${timetablePage.relativePath}` data-route-ids=`${timetablePage.route_ids ? timetablePage.route_ids.join(',') : ''}`)
|
|
13
|
-
|
|
13
|
+
- const timetableRouteList = _.uniqBy(_.flatMap(timetablePage.consolidatedTimetables, timetable => timetable.routes), 'route_id')
|
|
14
|
+
each route in timetableRouteList
|
|
14
15
|
.route-color-swatch-large(style=`background-color: ${formatRouteColor(route)}; color: ${formatRouteTextColor(route)};`)= route.route_short_name || ''
|
|
15
16
|
div
|
|
16
|
-
.timetable-page-label= timetablePage.timetable_page_label
|
|
17
|
+
.timetable-page-label= timetablePage.timetable_page_label || timetableRouteList.map(route => formatRouteName(route)).join(' and ')
|
|
17
18
|
.badge-gray= timetablePage.dayList
|
|
18
19
|
if config.showMap
|
|
19
20
|
#system_map.overview-map
|
|
@@ -3,10 +3,11 @@ include formatting_functions.pug
|
|
|
3
3
|
<!-- Timetable generated on #{new Date().toISOString()} using GTFS-to-HTML version #{config.gtfsToHtmlVersion} -->
|
|
4
4
|
.timetable-page(class=`menu-type-${config.menuType}`)
|
|
5
5
|
if config.showRouteTitle
|
|
6
|
+
- const timetableRouteList = _.uniqBy(_.flatMap(timetablePage.consolidatedTimetables, timetable => timetable.routes), 'route_id')
|
|
6
7
|
h1
|
|
7
|
-
each route in
|
|
8
|
+
each route in timetableRouteList
|
|
8
9
|
.route-color-swatch-large(style=`background-color: ${formatRouteColor(route)}; color: ${formatRouteTextColor(route)};`)= route.route_short_name || ''
|
|
9
|
-
div= timetablePage.timetable_page_label
|
|
10
|
+
div= timetablePage.timetable_page_label || timetableRouteList.map(route => formatRouteName(route)).join(' and ')
|
|
10
11
|
if config.effectiveDate
|
|
11
12
|
.effective-date= `Effective ${config.effectiveDate}`
|
|
12
13
|
|
|
@@ -235,7 +235,7 @@ API along with your API token.
|
|
|
235
235
|
|
|
236
236
|
### endDate
|
|
237
237
|
|
|
238
|
-
\{String\} A date in
|
|
238
|
+
\{String\} A date in `YYYYMMDD` format to use to control which calendars are used for the timetables. Can be used with [startDate](#startdate) configuration options.
|
|
239
239
|
|
|
240
240
|
Optional, defaults to using all available calendars if not defined. Overridden by `start_date` and `end_date` defined in `timetables.txt`.
|
|
241
241
|
|
|
@@ -519,7 +519,7 @@ The default trip-sorting algorithm is `common`.
|
|
|
519
519
|
|
|
520
520
|
### startDate
|
|
521
521
|
|
|
522
|
-
\{String\} A date in
|
|
522
|
+
\{String\} A date in `YYYYMMDD` format to use to control which calendars are used for the timetables. Can be used with [endDate](#enddate) configuration options.
|
|
523
523
|
|
|
524
524
|
Optional, defaults to using all available calendars if not defined. Overridden by `start_date` and `end_date` defined in `timetables.txt`.
|
|
525
525
|
|
package/www/docs/timetables.md
CHANGED
|
@@ -12,8 +12,8 @@ This is an optional, non-standard file called `timetables.txt` which can be incl
|
|
|
12
12
|
| `timetable_id` | A unique ID for the timetable |
|
|
13
13
|
| `route_id` | The ID of the route the timetable is for from `routes.txt`. For timetables that should include more than one route, see [Multi-route timetables](#multi-route-timetables) |
|
|
14
14
|
| `direction_id` | The `direction_id` from `trips.txt` for the timetable. This can be blank. |
|
|
15
|
-
| `start_date` | The start date for this timetable in `
|
|
16
|
-
| `end_date` | The end date for this timetable in `
|
|
15
|
+
| `start_date` | The start date for this timetable in `YYYYMMDD` format. |
|
|
16
|
+
| `end_date` | The end date for this timetable in `YYYYMMDD` format. |
|
|
17
17
|
| `monday` | A binary value that indicates whether this timetable should include service on Mondays. Valid options are `0` and `1`. |
|
|
18
18
|
| `tuesday` | A binary value that indicates whether this timetable should include service on Tuesdays. Valid options are `0` and `1`. |
|
|
19
19
|
| `wednesday` | A binary value that indicates whether this timetable should include service on Wednesdays. Valid options are `0` and `1`. |
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|