gtfs-to-html 2.2.1 → 2.3.3
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 +32 -1
- package/README.md +59 -41
- package/app/index.js +3 -3
- package/lib/file-utils.js +13 -0
- package/lib/formatters.js +31 -0
- package/lib/gtfs-to-html.js +39 -22
- package/lib/template-functions.js +42 -0
- package/lib/utils.js +51 -8
- package/package.json +14 -13
- package/public/css/timetable_styles.css +7 -5
- package/views/default/formatting_functions.pug +0 -17
- package/views/default/overview_full.pug +2 -2
- package/views/default/timetablepage_full.pug +2 -2
- package/www/blog/2021-11-06-CSV-Export.md +26 -0
- package/www/docs/configuration.md +87 -85
- package/www/docs/current-usage.md +31 -30
- package/www/docs/introduction.md +8 -5
- package/www/docs/timetables.md +35 -27
- package/www/package.json +2 -5
- package/www/static/img/gtfs-to-html-logo.svg +15 -61
- package/www/yarn.lock +2238 -3515
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,37 @@ 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.3.3] - 2022-01-21
|
|
9
|
+
|
|
10
|
+
### Updated
|
|
11
|
+
|
|
12
|
+
- Dependency updates
|
|
13
|
+
|
|
14
|
+
## [2.3.2] - 2021-12-28
|
|
15
|
+
|
|
16
|
+
### Updated
|
|
17
|
+
|
|
18
|
+
- Dependency updates
|
|
19
|
+
- Updated docs info on multi-route timetables
|
|
20
|
+
|
|
21
|
+
## [2.3.1] - 2021-11-26
|
|
22
|
+
|
|
23
|
+
### Updated
|
|
24
|
+
|
|
25
|
+
- Dependency updates
|
|
26
|
+
- Better trip names for CSV export
|
|
27
|
+
|
|
28
|
+
## [2.3.0] - 2021-11-05
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- Support for exporting timetables in CSV format
|
|
33
|
+
|
|
34
|
+
### Updated
|
|
35
|
+
|
|
36
|
+
- Update route color swatch styles to support longer names
|
|
37
|
+
- Dependency updates
|
|
38
|
+
|
|
8
39
|
## [2.2.1] - 2021-10-17
|
|
9
40
|
|
|
10
41
|
### Added
|
|
@@ -102,7 +133,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
102
133
|
|
|
103
134
|
- Fix for showRouteTitle config
|
|
104
135
|
|
|
105
|
-
## [
|
|
136
|
+
## [2.3.1] - 2021-11-26
|
|
106
137
|
|
|
107
138
|
### Added
|
|
108
139
|
|
package/README.md
CHANGED
|
@@ -1,35 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
➡️
|
|
3
|
+
<a href="https://gtfstohtml.com/docs/">Documentation</a> |
|
|
4
|
+
<a href="https://gtfstohtml.com/docs/quick-start">Quick Start</a> |
|
|
5
|
+
<a href="https://gtfstohtml.com/docs/configuration">Configuration</a> |
|
|
6
|
+
<a href="https://gtfstohtml.com/docs/contact">Questions and Support</a>
|
|
7
|
+
⬅️
|
|
8
|
+
<br /><br />
|
|
9
|
+
<img src="www/static/img/gtfs-to-html-logo.svg" alt="GTFS-to-HTML" />
|
|
10
|
+
<br /><br />
|
|
11
|
+
<a href="https://www.npmjs.com/package/gtfs-to-html" rel="nofollow"><img src="https://img.shields.io/npm/v/gtfs-to-html.svg?style=flat" style="max-width: 100%;"></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/gtfs-to-html" rel="nofollow"><img src="https://img.shields.io/npm/dm/gtfs-to-html.svg?style=flat" style="max-width: 100%;"></a>
|
|
13
|
+
<img src="https://img.shields.io/badge/License-MIT-yellow.svg">
|
|
14
|
+
<br /><br />
|
|
15
|
+
Create human-readable, user-friendly transit timetables in HTML, PDF or CSV format directly from GTFS.
|
|
16
|
+
<br /><br />
|
|
17
|
+
<a href="https://nodei.co/npm/gtfs-to-html/" rel="nofollow"><img src="https://nodei.co/npm/gtfs-to-html.png?downloads=true" alt="NPM" style="max-width: 100%;"></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<hr>
|
|
21
|
+
|
|
22
|
+
See [gtfstohtml.com](https://gtfstohtml.com) for full documentation.
|
|
23
|
+
|
|
24
|
+
Most transit agencies have schedule data in [GTFS ](https://developers.google.com/transit/gtfs/) format but need to show each route's schedule to users on a website. GTFS-to-HTML automates the process of creating nicely formatted HTML timetables for inclusion on a transit agency website. This makes it easy to keep timetables up to date and accurate when schedule changes happen and reduces the likelihood of errors.
|
|
14
25
|
|
|
15
26
|
<img width="1265" src="https://user-images.githubusercontent.com/96217/28296063-aed45568-6b1a-11e7-9794-94b3d915d668.png">
|
|
16
27
|
|
|
17
28
|
## Features
|
|
18
29
|
|
|
19
30
|
### Configurable and customizable
|
|
20
|
-
|
|
31
|
+
|
|
32
|
+
`gtfs-to-html` has many options that configure how timetables are presented. It also allows using a completely custom template which makes it easy to build chunks of HTML that will fit perfectly into any website using any HTML structure and classes that you'd like. Or, create printable PDF versions or CSV exports of timetables using the `outputFormat` config option.
|
|
21
33
|
|
|
22
34
|
### Accessibility for all
|
|
35
|
+
|
|
23
36
|
`gtfs-to-html` properly formats timetables to ensure they are screen-reader accessible and WCAG 2.0 compliant.
|
|
24
37
|
|
|
25
38
|
### Mobile responsiveness built in
|
|
39
|
+
|
|
26
40
|
Built-in styling makes `gtfs-to-html` timetables ready to size and scroll easily on mobile phones and tablets.
|
|
27
41
|
|
|
28
42
|
### Schedule changes? A cinch.
|
|
43
|
+
|
|
29
44
|
By generating future timetables and including dates in table metadata, your timetables can appear in advance of a schedule change, and you can validate that your new timetables and GTFS are correct.
|
|
30
45
|
|
|
31
46
|
### Auto-generated maps
|
|
32
|
-
|
|
47
|
+
|
|
48
|
+
`gtfs-to-html` can also generate a map for each route that can be included with the schedule page. The map shows all stops for the route and lists all routes that serve each stop. See the `showMap` configuration option below.
|
|
33
49
|
|
|
34
50
|
Note: If you only want maps of GTFS data, use the [gtfs-to-geojson](https://github.com/blinktaginc/gtfs-to-geojson) package instead and skip making timetables entirely. If offers many different formats of GeoJSON for routes and stops.
|
|
35
51
|
|
|
@@ -40,34 +56,36 @@ Note: If you only want maps of GTFS data, use the [gtfs-to-geojson](https://gith
|
|
|
40
56
|
You can now use `gtfs-to-html` without actually downloading any code or doing any configuration. [run.gtfstohtml.com](https://run.gtfstohtml.com) provides a web based interface for finding GTFS feeds for agencies, setting configuration and then generates a previewable and downloadable set of timetables.
|
|
41
57
|
|
|
42
58
|
## Current Usage
|
|
59
|
+
|
|
43
60
|
Many transit agencies use `gtfs-to-html` to generate the schedule pages used on their websites, including:
|
|
44
61
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
- [Advance Transit](https://advancetransit.com)
|
|
63
|
+
- [Brockton Area Transit Authority](https://ridebat.com)
|
|
64
|
+
- [Capital Transit (Helena, Montana)](http://www.ridethecapitalt.org)
|
|
65
|
+
- [Capital Transit (Juneau, Alaska)](https://juneaucapitaltransit.org)
|
|
66
|
+
- [Central Transit (Ellensburg, Washington)](https://centraltransit.org)
|
|
67
|
+
- [County Connection (Contra Costa County, California)](https://countyconnection.com)
|
|
68
|
+
- [El Dorado Transit](http://eldoradotransit.com)
|
|
69
|
+
- [Greater Attleboro-Taunton Regional Transit Authority](https://www.gatra.org)
|
|
70
|
+
- [Humboldt Transit Authority](http://hta.org)
|
|
71
|
+
- [Kings Area Rural Transit (KART)](https://www.kartbus.org)
|
|
72
|
+
- [Madera County Connection](http://mcctransit.com)
|
|
73
|
+
- [Marin Transit](https://marintransit.org)
|
|
74
|
+
- [Morongo Basin Transit Authority](https://mbtabus.com)
|
|
75
|
+
- [Mountain Transit](http://mountaintransit.org)
|
|
76
|
+
- [MVgo (Mountain View, CA)](https://mvgo.org)
|
|
77
|
+
- [NW Connector (Oregon)](http://www.nworegontransit.org)
|
|
78
|
+
- [Palo Verde Valley Transit Agency](http://pvvta.com)
|
|
79
|
+
- [Petaluma Transit](http://transit.cityofpetaluma.net)
|
|
80
|
+
- [RTC Washoe (Reno, NV)](https://www.rtcwashoe.com)
|
|
81
|
+
- [Santa Barbara Metropolitan Transit District](https://sbmtd.gov)
|
|
82
|
+
- [Sonoma County Transit](http://sctransit.com)
|
|
83
|
+
- [Tahoe Transportation District](https://www.tahoetransportation.org)
|
|
84
|
+
- [Tahoe Truckee Area Regional Transit](https://tahoetruckeetransit.com)
|
|
85
|
+
- [Transcollines](https://transcollines.ca)
|
|
86
|
+
- [Tulare County Area Transit](https://ridetcat.org)
|
|
87
|
+
- [Victor Valley Transit](https://vvta.org)
|
|
88
|
+
- [Worcester Regional Transit Authority](https://therta.com)
|
|
71
89
|
|
|
72
90
|
Are you using `gtfs-to-html`? Let us know via email (brendan@blinktag.com) or via opening a github issue or pull request if your agency is using this library.
|
|
73
91
|
|
package/app/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
getTimetablePagesForAgency,
|
|
15
15
|
getFormattedTimetablePage,
|
|
16
16
|
generateOverviewHTML,
|
|
17
|
-
|
|
17
|
+
generateTimetableHTML,
|
|
18
18
|
} from '../lib/utils.js';
|
|
19
19
|
|
|
20
20
|
const { argv } = yargs(process.argv).option('c', {
|
|
@@ -107,8 +107,8 @@ router.get('/timetables/:timetablePageId', async (request, response, next) => {
|
|
|
107
107
|
config
|
|
108
108
|
);
|
|
109
109
|
|
|
110
|
-
const
|
|
111
|
-
response.send(
|
|
110
|
+
const html = await generateTimetableHTML(timetablePage, config);
|
|
111
|
+
response.send(html);
|
|
112
112
|
} catch (error) {
|
|
113
113
|
next(error);
|
|
114
114
|
}
|
package/lib/file-utils.js
CHANGED
|
@@ -145,6 +145,19 @@ export function generateFileName(timetable, config) {
|
|
|
145
145
|
return sanitize(filename).toLowerCase();
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/*
|
|
149
|
+
* Generate the filename for a CSV timetable.
|
|
150
|
+
*/
|
|
151
|
+
export function generateCSVFileName(timetable, timetablePage) {
|
|
152
|
+
let filename = timetablePage.filename.replace(/.html$/, '');
|
|
153
|
+
|
|
154
|
+
if (timetablePage.timetables.length > 1) {
|
|
155
|
+
filename += `_${timetable.direction_id}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return sanitize(`${filename}.csv`);
|
|
159
|
+
}
|
|
160
|
+
|
|
148
161
|
/*
|
|
149
162
|
* Generates the folder name for a timetable page based on the date.
|
|
150
163
|
*/
|
package/lib/formatters.js
CHANGED
|
@@ -356,6 +356,37 @@ export function formatStops(timetable, config) {
|
|
|
356
356
|
return timetable.stops;
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
+
/*
|
|
360
|
+
* Formats a stop name.
|
|
361
|
+
*/
|
|
362
|
+
export function formatStopName(stop) {
|
|
363
|
+
return `${stop.stop_name}${
|
|
364
|
+
stop.type === 'arrival'
|
|
365
|
+
? ' (Arrival)'
|
|
366
|
+
: stop.type === 'departure'
|
|
367
|
+
? ' (Departure)'
|
|
368
|
+
: ''
|
|
369
|
+
}`;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/*
|
|
373
|
+
* Formats trip "Contines from".
|
|
374
|
+
*/
|
|
375
|
+
export function formatTripContinuesFrom(trip) {
|
|
376
|
+
return trip.continues_from_route
|
|
377
|
+
? trip.continues_from_route.route.route_short_name
|
|
378
|
+
: '';
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/*
|
|
382
|
+
* Formats trip "Contines as".
|
|
383
|
+
*/
|
|
384
|
+
export function formatTripContinuesAs(trip) {
|
|
385
|
+
return trip.continues_as_route
|
|
386
|
+
? trip.continues_as_route.route.route_short_name
|
|
387
|
+
: '';
|
|
388
|
+
}
|
|
389
|
+
|
|
359
390
|
/*
|
|
360
391
|
* Change all stoptimes of a trip so the first trip starts at midnight. Useful
|
|
361
392
|
* for hourly schedules.
|
package/lib/gtfs-to-html.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { map } from 'lodash-es';
|
|
5
5
|
import { openDb, getDb, importGtfs } from 'gtfs';
|
|
6
6
|
import sanitize from 'sanitize-filename';
|
|
7
7
|
import Timer from 'timer-machine';
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
generateFolderName,
|
|
13
13
|
renderPdf,
|
|
14
14
|
zipFolder,
|
|
15
|
+
generateCSVFileName,
|
|
15
16
|
} from './file-utils.js';
|
|
16
17
|
import {
|
|
17
18
|
log,
|
|
@@ -25,8 +26,10 @@ import {
|
|
|
25
26
|
setDefaultConfig,
|
|
26
27
|
getTimetablePagesForAgency,
|
|
27
28
|
getFormattedTimetablePage,
|
|
28
|
-
|
|
29
|
+
generateTimetableHTML,
|
|
30
|
+
generateTimetableCSV,
|
|
29
31
|
generateOverviewHTML,
|
|
32
|
+
generateStats,
|
|
30
33
|
} from './utils.js';
|
|
31
34
|
|
|
32
35
|
/*
|
|
@@ -93,7 +96,7 @@ const gtfsToHtml = async (initialConfig) => {
|
|
|
93
96
|
);
|
|
94
97
|
await prepDirectory(exportPath);
|
|
95
98
|
|
|
96
|
-
if (config.noHead !== true) {
|
|
99
|
+
if (config.noHead !== true && ['html', 'pdf'].includes(config.outputFormat)) {
|
|
97
100
|
copyStaticAssets(exportPath);
|
|
98
101
|
}
|
|
99
102
|
|
|
@@ -138,26 +141,38 @@ const gtfsToHtml = async (initialConfig) => {
|
|
|
138
141
|
sanitize(timetablePage.filename)
|
|
139
142
|
);
|
|
140
143
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
if (config.outputFormat === 'csv') {
|
|
145
|
+
for (const timetable of timetablePage.timetables) {
|
|
146
|
+
const csv = await generateTimetableCSV(timetable);
|
|
147
|
+
const csvPath = path.join(
|
|
148
|
+
exportPath,
|
|
149
|
+
datePath,
|
|
150
|
+
generateCSVFileName(timetable, timetablePage)
|
|
151
|
+
);
|
|
152
|
+
await writeFile(csvPath, csv);
|
|
146
153
|
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
} else {
|
|
155
|
+
const html = await generateTimetableHTML(timetablePage, config);
|
|
156
|
+
const htmlPath = path.join(
|
|
157
|
+
exportPath,
|
|
158
|
+
datePath,
|
|
159
|
+
sanitize(timetablePage.filename)
|
|
160
|
+
);
|
|
161
|
+
await writeFile(htmlPath, html);
|
|
155
162
|
|
|
156
|
-
|
|
157
|
-
|
|
163
|
+
if (config.outputFormat === 'pdf') {
|
|
164
|
+
await renderPdf(htmlPath);
|
|
165
|
+
}
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
timetablePages.push(timetablePage);
|
|
169
|
+
const timetableStats = generateStats(timetablePage);
|
|
170
|
+
|
|
171
|
+
for (const key of Object.keys(outputStats)) {
|
|
172
|
+
if (timetableStats[key]) {
|
|
173
|
+
outputStats[key] += timetableStats[key];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
161
176
|
} catch (error) {
|
|
162
177
|
outputStats.warnings.push(error.message);
|
|
163
178
|
bar.interrupt(error.message);
|
|
@@ -167,10 +182,12 @@ const gtfsToHtml = async (initialConfig) => {
|
|
|
167
182
|
}
|
|
168
183
|
/* eslint-enable no-await-in-loop */
|
|
169
184
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
185
|
+
if (config.outputFormat === 'html') {
|
|
186
|
+
// Generate route summary index.html
|
|
187
|
+
config.assetPath = '';
|
|
188
|
+
const html = await generateOverviewHTML(timetablePages, config);
|
|
189
|
+
await writeFile(path.join(exportPath, 'index.html'), html);
|
|
190
|
+
}
|
|
174
191
|
|
|
175
192
|
// Generate output log.txt
|
|
176
193
|
const logText = await generateLogText(outputStats, config);
|
|
@@ -141,3 +141,45 @@ export function getNotesForStoptime(notes, stoptime) {
|
|
|
141
141
|
);
|
|
142
142
|
});
|
|
143
143
|
}
|
|
144
|
+
|
|
145
|
+
/*
|
|
146
|
+
* Formats a trip name.
|
|
147
|
+
*/
|
|
148
|
+
export function formatTripName(trip, index, timetable) {
|
|
149
|
+
let tripName = '';
|
|
150
|
+
if (timetable.routes.length > 1) {
|
|
151
|
+
tripName = trip.route_short_name;
|
|
152
|
+
} else if (trip.trip_short_name) {
|
|
153
|
+
tripName += trip.trip_short_name;
|
|
154
|
+
} else {
|
|
155
|
+
tripName += `Run #${index + 1}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (timetableHasDifferentDays(timetable)) {
|
|
159
|
+
tripName += ` ${trip.dayList}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return tripName;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/*
|
|
166
|
+
* Formats a trip name.
|
|
167
|
+
*/
|
|
168
|
+
export function formatTripNameForCSV(trip, timetable) {
|
|
169
|
+
let tripName = '';
|
|
170
|
+
if (timetable.routes.length > 1) {
|
|
171
|
+
tripName += `${trip.route_short_name} - `;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (trip.trip_short_name) {
|
|
175
|
+
tripName += trip.trip_short_name;
|
|
176
|
+
} else {
|
|
177
|
+
tripName += trip.trip_id;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (timetableHasDifferentDays(timetable)) {
|
|
181
|
+
tripName += ` - ${trip.dayList}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return tripName;
|
|
185
|
+
}
|
package/lib/utils.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
sortBy,
|
|
20
20
|
uniq,
|
|
21
21
|
uniqBy,
|
|
22
|
+
zip,
|
|
22
23
|
} from 'lodash-es';
|
|
23
24
|
import {
|
|
24
25
|
getCalendarDates,
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
getTimetablePages,
|
|
38
39
|
getAgencies,
|
|
39
40
|
} from 'gtfs';
|
|
41
|
+
import { stringify } from 'csv-stringify';
|
|
40
42
|
import moment from 'moment';
|
|
41
43
|
import sqlString from 'sqlstring';
|
|
42
44
|
import toposort from 'toposort';
|
|
@@ -47,11 +49,14 @@ import {
|
|
|
47
49
|
formatDays,
|
|
48
50
|
formatDaysLong,
|
|
49
51
|
formatFrequency,
|
|
52
|
+
formatStopName,
|
|
50
53
|
formatStops,
|
|
51
54
|
formatTimetableId,
|
|
52
55
|
formatTimetableLabel,
|
|
53
56
|
formatTimetablePageLabel,
|
|
54
57
|
formatTrip,
|
|
58
|
+
formatTripContinuesAs,
|
|
59
|
+
formatTripContinuesFrom,
|
|
55
60
|
isNullOrEmpty,
|
|
56
61
|
mergeTimetablesWithSameId,
|
|
57
62
|
resetStoptimesToMidnight,
|
|
@@ -67,6 +72,7 @@ import {
|
|
|
67
72
|
fromGTFSTime,
|
|
68
73
|
calendarCodeToCalendar,
|
|
69
74
|
} from './time-utils.js';
|
|
75
|
+
import { formatTripNameForCSV } from './template-functions.js';
|
|
70
76
|
|
|
71
77
|
const { version } = JSON.parse(
|
|
72
78
|
readFileSync(new URL('../package.json', import.meta.url))
|
|
@@ -1498,7 +1504,7 @@ export async function getFormattedTimetablePage(timetablePageId, config) {
|
|
|
1498
1504
|
/*
|
|
1499
1505
|
* Generate stats about timetable page.
|
|
1500
1506
|
*/
|
|
1501
|
-
const generateStats = (timetablePage) => {
|
|
1507
|
+
export const generateStats = (timetablePage) => {
|
|
1502
1508
|
const stats = {
|
|
1503
1509
|
stops: 0,
|
|
1504
1510
|
trips: 0,
|
|
@@ -1527,17 +1533,54 @@ const generateStats = (timetablePage) => {
|
|
|
1527
1533
|
/*
|
|
1528
1534
|
* Generate the HTML timetable for a timetable page.
|
|
1529
1535
|
*/
|
|
1530
|
-
export
|
|
1536
|
+
export function generateTimetableHTML(timetablePage, config) {
|
|
1531
1537
|
const templateVars = {
|
|
1532
1538
|
timetablePage,
|
|
1533
1539
|
config,
|
|
1534
1540
|
};
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
+
return renderTemplate('timetablepage', templateVars, config);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
/*
|
|
1545
|
+
* Generate the CSV timetable for a timetable page.
|
|
1546
|
+
*/
|
|
1547
|
+
export async function generateTimetableCSV(timetable) {
|
|
1548
|
+
// Generate horizontal orientation, then transpose if vertical is needed.
|
|
1549
|
+
const lines = [];
|
|
1550
|
+
|
|
1551
|
+
lines.push([
|
|
1552
|
+
'',
|
|
1553
|
+
...timetable.orderedTrips.map((trip) =>
|
|
1554
|
+
formatTripNameForCSV(trip, timetable)
|
|
1555
|
+
),
|
|
1556
|
+
]);
|
|
1557
|
+
|
|
1558
|
+
if (timetable.has_continues_from_route) {
|
|
1559
|
+
lines.push([
|
|
1560
|
+
'Continues from route',
|
|
1561
|
+
...timetable.orderedTrips.map((trip) => formatTripContinuesFrom(trip)),
|
|
1562
|
+
]);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
for (const stop of timetable.stops) {
|
|
1566
|
+
lines.push([
|
|
1567
|
+
formatStopName(stop),
|
|
1568
|
+
...stop.trips.map((stoptime) => stoptime.formatted_time),
|
|
1569
|
+
]);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
if (timetable.has_continues_as_route) {
|
|
1573
|
+
lines.push([
|
|
1574
|
+
'Continues as route',
|
|
1575
|
+
...timetable.orderedTrips.map((trip) => formatTripContinuesAs(trip)),
|
|
1576
|
+
]);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
if (timetable.orientation === 'vertical') {
|
|
1580
|
+
return stringify(zip(...lines));
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
return stringify(lines);
|
|
1541
1584
|
}
|
|
1542
1585
|
|
|
1543
1586
|
/*
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gtfs-to-html",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "Build human readable transit timetables as HTML or
|
|
5
|
+
"description": "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"transit",
|
|
8
8
|
"gtfs",
|
|
@@ -38,32 +38,33 @@
|
|
|
38
38
|
"@turf/helpers": "^6.5.0",
|
|
39
39
|
"@turf/simplify": "^6.5.0",
|
|
40
40
|
"archiver": "^5.3.0",
|
|
41
|
-
"chalk": "^
|
|
42
|
-
"cli-table": "^0.3.
|
|
41
|
+
"chalk": "^5.0.0",
|
|
42
|
+
"cli-table": "^0.3.11",
|
|
43
43
|
"copy-dir": "^1.3.0",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
44
|
+
"csv-stringify": "^6.0.5",
|
|
45
|
+
"express": "^4.17.2",
|
|
46
|
+
"gtfs": "^3.2.4",
|
|
46
47
|
"js-beautify": "^1.14.0",
|
|
47
48
|
"lodash-es": "^4.17.21",
|
|
48
49
|
"moment": "^2.29.1",
|
|
49
50
|
"morgan": "^1.10.0",
|
|
50
|
-
"pretty-error": "^
|
|
51
|
+
"pretty-error": "^4.0.0",
|
|
51
52
|
"pug": "^3.0.2",
|
|
52
|
-
"puppeteer": "^
|
|
53
|
+
"puppeteer": "^13.1.1",
|
|
53
54
|
"sanitize-filename": "^1.6.3",
|
|
54
55
|
"sqlstring": "^2.3.2",
|
|
55
56
|
"timer-machine": "^1.1.0",
|
|
56
57
|
"toposort": "^2.0.2",
|
|
57
58
|
"untildify": "^4.0.0",
|
|
58
|
-
"yargs": "^17.
|
|
59
|
+
"yargs": "^17.3.1"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
|
-
"eslint": "^8.0
|
|
62
|
+
"eslint": "^8.7.0",
|
|
62
63
|
"eslint-config-prettier": "^8.3.0",
|
|
63
64
|
"eslint-config-xo": "^0.39.0",
|
|
64
|
-
"husky": "^7.0.
|
|
65
|
-
"prettier": "^2.
|
|
66
|
-
"pretty-quick": "^3.1.
|
|
65
|
+
"husky": "^7.0.4",
|
|
66
|
+
"prettier": "^2.5.1",
|
|
67
|
+
"pretty-quick": "^3.1.3"
|
|
67
68
|
},
|
|
68
69
|
"engines": {
|
|
69
70
|
"node": ">= 12.14.0"
|
|
@@ -162,24 +162,26 @@ a:hover {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
.route-color-swatch {
|
|
165
|
-
width: 26px;
|
|
165
|
+
min-width: 26px;
|
|
166
166
|
height: 26px;
|
|
167
|
-
border-radius:
|
|
167
|
+
border-radius: 13px;
|
|
168
168
|
text-align: center;
|
|
169
169
|
line-height: 26px;
|
|
170
170
|
font-size: 14px;
|
|
171
|
-
letter-spacing: -
|
|
171
|
+
letter-spacing: -0.5px;
|
|
172
|
+
padding: 0 5px;
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
.route-color-swatch-large {
|
|
175
|
-
width: 40px;
|
|
176
|
+
min-width: 40px;
|
|
176
177
|
height: 40px;
|
|
177
|
-
border-radius:
|
|
178
|
+
border-radius: 20px;
|
|
178
179
|
text-align: center;
|
|
179
180
|
line-height: 40px;
|
|
180
181
|
font-size: 20px;
|
|
181
182
|
font-weight: bold;
|
|
182
183
|
letter-spacing: -1px;
|
|
184
|
+
padding: 0 8px;
|
|
183
185
|
}
|
|
184
186
|
|
|
185
187
|
@media screen and (max-width: 767px) {
|
|
@@ -11,23 +11,6 @@
|
|
|
11
11
|
return summary;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function formatTripName(trip, idx, timetable) {
|
|
15
|
-
let tripName = '';
|
|
16
|
-
if (timetable.routes.length > 1) {
|
|
17
|
-
tripName = trip.route_short_name;
|
|
18
|
-
} else if (trip.trip_short_name) {
|
|
19
|
-
tripName += trip.trip_short_name;
|
|
20
|
-
} else {
|
|
21
|
-
tripName += `Run #${idx + 1}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (timetableHasDifferentDays(timetable)) {
|
|
25
|
-
tripName += ` ${trip.dayList}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return tripName;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
14
|
function formatRouteName(route) {
|
|
32
15
|
const hasLongName = route.route_long_name !== '' && route.route_long_name !== null;
|
|
33
16
|
|
|
@@ -6,9 +6,9 @@ block extraHeader
|
|
|
6
6
|
if config.showMap
|
|
7
7
|
script(src="https://unpkg.com/jquery@3.6.0/dist/jquery.min.js" crossorigin="anonymous")
|
|
8
8
|
script(src="https://unpkg.com/lodash@4.17.21/lodash.min.js" crossorigin="anonymous")
|
|
9
|
-
script(src="https://api.mapbox.com/mapbox-gl-js/v2.5.
|
|
9
|
+
script(src="https://api.mapbox.com/mapbox-gl-js/v2.5.1/mapbox-gl.js")
|
|
10
10
|
script.
|
|
11
11
|
mapboxgl.accessToken = '#{config.mapboxAccessToken}';
|
|
12
12
|
script(src=`${config.assetPath}js/system-map.js`)
|
|
13
13
|
|
|
14
|
-
link(href="https://api.mapbox.com/mapbox-gl-js/v2.5.
|
|
14
|
+
link(href="https://api.mapbox.com/mapbox-gl-js/v2.5.1/mapbox-gl.css" rel="stylesheet")
|
|
@@ -9,10 +9,10 @@ block extraHeader
|
|
|
9
9
|
script(src=`${config.assetPath}js/timetable-menu.js`)
|
|
10
10
|
|
|
11
11
|
if config.showMap
|
|
12
|
-
script(src="https://api.mapbox.com/mapbox-gl-js/v2.5.
|
|
12
|
+
script(src="https://api.mapbox.com/mapbox-gl-js/v2.5.1/mapbox-gl.js")
|
|
13
13
|
script.
|
|
14
14
|
mapboxgl.accessToken = '#{config.mapboxAccessToken}';
|
|
15
15
|
script(src=`${config.assetPath}js/timetable-map.js`)
|
|
16
16
|
|
|
17
|
-
link(href="https://api.mapbox.com/mapbox-gl-js/v2.5.
|
|
17
|
+
link(href="https://api.mapbox.com/mapbox-gl-js/v2.5.1/mapbox-gl.css" rel="stylesheet")
|
|
18
18
|
|