gtfs-to-html 2.2.0 → 2.3.2
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/.eslintrc.json +15 -20
- package/.husky/pre-commit +4 -0
- package/CHANGELOG.md +275 -2
- package/README.md +59 -41
- package/app/index.js +46 -24
- package/bin/gtfs-to-html.js +5 -7
- package/lib/file-utils.js +52 -15
- package/lib/formatters.js +123 -28
- package/lib/geojson-utils.js +32 -17
- package/lib/gtfs-to-html.js +96 -34
- package/lib/log-utils.js +23 -15
- package/lib/template-functions.js +80 -17
- package/lib/time-utils.js +10 -2
- package/lib/utils.js +762 -371
- package/package.json +29 -11
- package/public/css/timetable_styles.css +55 -49
- package/public/js/system-map.js +73 -60
- package/public/js/timetable-map.js +103 -96
- package/public/js/timetable-menu.js +32 -8
- 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 +2160 -3398
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
const maps = {};
|
|
5
5
|
|
|
6
6
|
function formatRoute(route) {
|
|
7
|
-
const html = route.route_url
|
|
7
|
+
const html = route.route_url
|
|
8
|
+
? $('<a>').attr('href', route.route_url)
|
|
9
|
+
: $('<div>');
|
|
8
10
|
|
|
9
11
|
html.addClass('route-item text-sm mb-2');
|
|
10
12
|
|
|
@@ -32,21 +34,14 @@ function formatStopPopup(feature) {
|
|
|
32
34
|
.appendTo(html);
|
|
33
35
|
|
|
34
36
|
if (feature.properties.stop_code ?? false) {
|
|
35
|
-
$('<label>')
|
|
36
|
-
.addClass('mr-1')
|
|
37
|
-
.text('Stop Code:')
|
|
38
|
-
.appendTo(html);
|
|
37
|
+
$('<label>').addClass('mr-1').text('Stop Code:').appendTo(html);
|
|
39
38
|
|
|
40
|
-
$('<strong>')
|
|
41
|
-
.text(feature.properties.stop_code)
|
|
42
|
-
.appendTo(html);
|
|
39
|
+
$('<strong>').text(feature.properties.stop_code).appendTo(html);
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
$('<div>')
|
|
46
|
-
.text('Routes Served:')
|
|
47
|
-
.appendTo(html);
|
|
42
|
+
$('<div>').text('Routes Served:').appendTo(html);
|
|
48
43
|
|
|
49
|
-
$(html).append(routes.map(route => formatRoute(route)));
|
|
44
|
+
$(html).append(routes.map((route) => formatRoute(route)));
|
|
50
45
|
|
|
51
46
|
return html.prop('outerHTML');
|
|
52
47
|
}
|
|
@@ -80,7 +75,7 @@ function createMap(id, geojson) {
|
|
|
80
75
|
style: 'mapbox://styles/mapbox/light-v10',
|
|
81
76
|
center: bounds.getCenter(),
|
|
82
77
|
zoom: 12,
|
|
83
|
-
preserveDrawingBuffer: true
|
|
78
|
+
preserveDrawingBuffer: true,
|
|
84
79
|
});
|
|
85
80
|
|
|
86
81
|
map.scrollZoom.disable();
|
|
@@ -92,9 +87,9 @@ function createMap(id, geojson) {
|
|
|
92
87
|
top: 40,
|
|
93
88
|
bottom: 40,
|
|
94
89
|
left: 20,
|
|
95
|
-
right: 40
|
|
90
|
+
right: 40,
|
|
96
91
|
},
|
|
97
|
-
duration: 0
|
|
92
|
+
duration: 0,
|
|
98
93
|
});
|
|
99
94
|
|
|
100
95
|
// Find the index of the first symbol layer in the map style
|
|
@@ -107,91 +102,103 @@ function createMap(id, geojson) {
|
|
|
107
102
|
}
|
|
108
103
|
|
|
109
104
|
// Add route line outline first
|
|
110
|
-
map.addLayer(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
map.addLayer({
|
|
130
|
-
id: 'route-line',
|
|
131
|
-
type: 'line',
|
|
132
|
-
source: {
|
|
133
|
-
type: 'geojson',
|
|
134
|
-
data: geojson
|
|
135
|
-
},
|
|
136
|
-
paint: {
|
|
137
|
-
'line-color': ['to-color', ['get', 'route_color'], defaultRouteColor],
|
|
138
|
-
'line-opacity': 1,
|
|
139
|
-
'line-width': 2
|
|
140
|
-
},
|
|
141
|
-
layout: {
|
|
142
|
-
'line-join': 'round',
|
|
143
|
-
'line-cap': 'round'
|
|
144
|
-
},
|
|
145
|
-
filter: ['!has', 'stop_id']
|
|
146
|
-
}, firstSymbolId);
|
|
147
|
-
|
|
148
|
-
map.addLayer({
|
|
149
|
-
id: 'stops',
|
|
150
|
-
type: 'circle',
|
|
151
|
-
source: {
|
|
152
|
-
type: 'geojson',
|
|
153
|
-
data: geojson
|
|
105
|
+
map.addLayer(
|
|
106
|
+
{
|
|
107
|
+
id: 'route-line-outline',
|
|
108
|
+
type: 'line',
|
|
109
|
+
source: {
|
|
110
|
+
type: 'geojson',
|
|
111
|
+
data: geojson,
|
|
112
|
+
},
|
|
113
|
+
paint: {
|
|
114
|
+
'line-color': '#FFFFFF',
|
|
115
|
+
'line-opacity': 1,
|
|
116
|
+
'line-width': 6,
|
|
117
|
+
},
|
|
118
|
+
layout: {
|
|
119
|
+
'line-join': 'round',
|
|
120
|
+
'line-cap': 'round',
|
|
121
|
+
},
|
|
122
|
+
filter: ['!has', 'stop_id'],
|
|
154
123
|
},
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
124
|
+
firstSymbolId
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
map.addLayer(
|
|
128
|
+
{
|
|
129
|
+
id: 'route-line',
|
|
130
|
+
type: 'line',
|
|
131
|
+
source: {
|
|
132
|
+
type: 'geojson',
|
|
133
|
+
data: geojson,
|
|
134
|
+
},
|
|
135
|
+
paint: {
|
|
136
|
+
'line-color': ['to-color', ['get', 'route_color'], defaultRouteColor],
|
|
137
|
+
'line-opacity': 1,
|
|
138
|
+
'line-width': 2,
|
|
162
139
|
},
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
140
|
+
layout: {
|
|
141
|
+
'line-join': 'round',
|
|
142
|
+
'line-cap': 'round',
|
|
143
|
+
},
|
|
144
|
+
filter: ['!has', 'stop_id'],
|
|
166
145
|
},
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
map.addLayer(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
146
|
+
firstSymbolId
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
map.addLayer(
|
|
150
|
+
{
|
|
151
|
+
id: 'stops',
|
|
152
|
+
type: 'circle',
|
|
153
|
+
source: {
|
|
154
|
+
type: 'geojson',
|
|
155
|
+
data: geojson,
|
|
156
|
+
},
|
|
157
|
+
paint: {
|
|
158
|
+
'circle-radius': {
|
|
159
|
+
stops: [
|
|
160
|
+
[9, 2],
|
|
161
|
+
[13, 4],
|
|
162
|
+
[15, 6],
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
'circle-stroke-width': 1,
|
|
166
|
+
'circle-stroke-color': '#363636',
|
|
167
|
+
'circle-color': '#363636',
|
|
168
|
+
},
|
|
169
|
+
filter: ['has', 'stop_id'],
|
|
176
170
|
},
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
171
|
+
firstSymbolId
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
map.addLayer(
|
|
175
|
+
{
|
|
176
|
+
id: 'stops-highlighted',
|
|
177
|
+
type: 'circle',
|
|
178
|
+
source: {
|
|
179
|
+
type: 'geojson',
|
|
180
|
+
data: geojson,
|
|
181
|
+
},
|
|
182
|
+
paint: {
|
|
183
|
+
'circle-radius': {
|
|
184
|
+
stops: [
|
|
185
|
+
[9, 3],
|
|
186
|
+
[13, 4],
|
|
187
|
+
[15, 7],
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
'circle-stroke-width': 2,
|
|
191
|
+
'circle-stroke-color': '#666666',
|
|
192
|
+
'circle-color': '#888888',
|
|
184
193
|
},
|
|
185
|
-
'
|
|
186
|
-
'circle-stroke-color': '#666666',
|
|
187
|
-
'circle-color': '#888888'
|
|
194
|
+
filter: ['==', 'stop_id', ''],
|
|
188
195
|
},
|
|
189
|
-
|
|
190
|
-
|
|
196
|
+
firstSymbolId
|
|
197
|
+
);
|
|
191
198
|
|
|
192
|
-
map.on('mousemove', event => {
|
|
199
|
+
map.on('mousemove', (event) => {
|
|
193
200
|
const features = map.queryRenderedFeatures(event.point, {
|
|
194
|
-
layers: ['stops']
|
|
201
|
+
layers: ['stops'],
|
|
195
202
|
});
|
|
196
203
|
if (features.length > 0) {
|
|
197
204
|
map.getCanvas().style.cursor = 'pointer';
|
|
@@ -202,14 +209,14 @@ function createMap(id, geojson) {
|
|
|
202
209
|
}
|
|
203
210
|
});
|
|
204
211
|
|
|
205
|
-
map.on('click', event => {
|
|
212
|
+
map.on('click', (event) => {
|
|
206
213
|
// Set bbox as 5px rectangle area around clicked point
|
|
207
214
|
const bbox = [
|
|
208
215
|
[event.point.x - 5, event.point.y - 5],
|
|
209
|
-
[event.point.x + 5, event.point.y + 5]
|
|
216
|
+
[event.point.x + 5, event.point.y + 5],
|
|
210
217
|
];
|
|
211
218
|
const features = map.queryRenderedFeatures(bbox, {
|
|
212
|
-
layers: ['stops']
|
|
219
|
+
layers: ['stops'],
|
|
213
220
|
});
|
|
214
221
|
|
|
215
222
|
if (!features || features.length === 0) {
|
|
@@ -238,7 +245,7 @@ function createMap(id, geojson) {
|
|
|
238
245
|
}
|
|
239
246
|
|
|
240
247
|
// On table hover, highlight stop on map
|
|
241
|
-
$('th, td', $('#' + id + ' table')).hover(event => {
|
|
248
|
+
$('th, td', $('#' + id + ' table')).hover((event) => {
|
|
242
249
|
let stopId;
|
|
243
250
|
const table = $(event.target).parents('table');
|
|
244
251
|
if (table.data('orientation') === 'vertical') {
|
|
@@ -19,20 +19,44 @@ $(() => {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
$('#day_list_selector input[name="dayList"]').each((index, element) => {
|
|
22
|
-
$(element)
|
|
23
|
-
|
|
22
|
+
$(element)
|
|
23
|
+
.parents('label')
|
|
24
|
+
.toggleClass('text-white bg-blue-600', $(element).is(':checked'));
|
|
25
|
+
$(element)
|
|
26
|
+
.parents('label')
|
|
27
|
+
.toggleClass(
|
|
28
|
+
'text-gray-600 bg-gray-300',
|
|
29
|
+
$(element).is(':not(:checked)')
|
|
30
|
+
);
|
|
24
31
|
});
|
|
25
32
|
|
|
26
|
-
$('#direction_name_selector input[name="directionName"]').each(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
$('#direction_name_selector input[name="directionName"]').each(
|
|
34
|
+
(index, element) => {
|
|
35
|
+
$(element)
|
|
36
|
+
.parents('label')
|
|
37
|
+
.toggleClass('text-white bg-blue-600', $(element).is(':checked'));
|
|
38
|
+
$(element)
|
|
39
|
+
.parents('label')
|
|
40
|
+
.toggleClass(
|
|
41
|
+
'text-gray-600 bg-gray-300',
|
|
42
|
+
$(element).is(':not(:checked)')
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
);
|
|
30
46
|
|
|
31
47
|
const dayList = $('#day_list_selector input[name="dayList"]:checked').val();
|
|
32
|
-
const directionName = $(
|
|
48
|
+
const directionName = $(
|
|
49
|
+
'#direction_name_selector input[name="directionName"]:checked'
|
|
50
|
+
).val();
|
|
33
51
|
|
|
34
52
|
$('.timetable').hide();
|
|
35
|
-
const id = $(
|
|
53
|
+
const id = $(
|
|
54
|
+
'.timetable[data-day-list="' +
|
|
55
|
+
dayList +
|
|
56
|
+
'"][data-direction-name="' +
|
|
57
|
+
directionName +
|
|
58
|
+
'"]'
|
|
59
|
+
).attr('id');
|
|
36
60
|
showTimetable(id);
|
|
37
61
|
}
|
|
38
62
|
|
|
@@ -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
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
slug: timetables_as_csv
|
|
3
|
+
title: New Feature - GTFS timetables as CSV
|
|
4
|
+
author: Brendan Nee
|
|
5
|
+
author_url: https://github.com/brendannee
|
|
6
|
+
author_image_url: https://avatars3.githubusercontent.com/u/96217?s=400&v=4
|
|
7
|
+
tags: [csv]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
GTFS-to-HTML Version 2.3.0 adds support for exporting timetables as CSV. Setting the [outputFormat](https://gtfstohtml.com/docs/configuration#outputformat) configuration to `csv` will generate CSV files instead of HTML. One CSV file per timetable will be generated.
|
|
11
|
+
|
|
12
|
+
An example of a CSV timetable:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
,San Francisco Ferry Building,Vallejo Ferry Terminal,Mare Island Ferry Terminal
|
|
16
|
+
Run #1,10:30am,11:30am,
|
|
17
|
+
Run #2,11:30am,12:30pm,
|
|
18
|
+
Run #3,1:50pm,2:50pm,
|
|
19
|
+
Run #4,2:50pm,3:50pm,
|
|
20
|
+
Run #5,4:10pm,5:10pm,5:25pm
|
|
21
|
+
Run #6,5:10pm,6:10pm,6:25pm
|
|
22
|
+
Run #7,6:30pm,7:30pm,
|
|
23
|
+
Run #8,8:50pm,9:50pm,10:05pm
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Timetables in CSV respect the `orientation` set in `timetables.txt` or `defaultOrientation` in `config.json`, they can be either `horizontal` or `vertical`.
|