gtfs-to-html 2.7.2 → 2.8.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/package.json +21 -8
- package/.eslintrc.json +0 -28
- package/.husky/pre-commit +0 -4
- package/CHANGELOG.md +0 -1018
- package/app/index.js +0 -138
- package/bin/gtfs-to-html.js +0 -48
- package/config-sample.json +0 -59
- package/docker/Dockerfile +0 -14
- package/docker/README.md +0 -5
- package/docker/docker-compose.yml +0 -10
- package/examples/stop_attributes.txt +0 -6
- package/examples/timetable_notes.txt +0 -8
- package/examples/timetable_notes_references.txt +0 -8
- package/examples/timetable_pages.txt +0 -3
- package/examples/timetable_stop_order.txt +0 -16
- package/examples/timetables.txt +0 -9
- package/index.js +0 -1
- package/lib/file-utils.js +0 -202
- package/lib/formatters.js +0 -518
- package/lib/geojson-utils.js +0 -96
- package/lib/gtfs-to-html.js +0 -214
- package/lib/log-utils.js +0 -215
- package/lib/template-functions.js +0 -192
- package/lib/time-utils.js +0 -90
- package/lib/utils.js +0 -1702
- package/views/default/css/overview_styles.css +0 -197
- package/views/default/css/timetable_pdf_styles.css +0 -7
- package/views/default/css/timetable_styles.css +0 -447
- package/views/default/formatting_functions.pug +0 -113
- package/views/default/js/system-map.js +0 -594
- package/views/default/js/timetable-map.js +0 -358
- package/views/default/js/timetable-menu.js +0 -63
- package/views/default/layout.pug +0 -11
- package/views/default/overview.pug +0 -27
- package/views/default/overview_full.pug +0 -16
- package/views/default/timetable_continuation_as.pug +0 -7
- package/views/default/timetable_continuation_from.pug +0 -7
- package/views/default/timetable_horizontal.pug +0 -42
- package/views/default/timetable_hourly.pug +0 -30
- package/views/default/timetable_menu.pug +0 -48
- package/views/default/timetable_note_symbol.pug +0 -5
- package/views/default/timetable_stop_name.pug +0 -13
- package/views/default/timetable_stoptime.pug +0 -17
- package/views/default/timetable_vertical.pug +0 -67
- package/views/default/timetablepage.pug +0 -66
- package/views/default/timetablepage_full.pug +0 -22
- package/www/README.md +0 -33
- package/www/babel.config.js +0 -3
- package/www/blog/2020-07-07-New-Documentation.md +0 -12
- package/www/blog/2020-08-20-Version-1.0.0.md +0 -29
- package/www/blog/2021-11-06-CSV-Export.md +0 -26
- package/www/docs/additional-files.md +0 -24
- package/www/docs/configuration.md +0 -568
- package/www/docs/current-usage.md +0 -48
- package/www/docs/custom-templates.md +0 -13
- package/www/docs/introduction.md +0 -39
- package/www/docs/logging-sql-queries.md +0 -12
- package/www/docs/previewing-html-output.md +0 -24
- package/www/docs/processing-large-gtfs.md +0 -10
- package/www/docs/quick-start.md +0 -136
- package/www/docs/related-libraries.md +0 -54
- package/www/docs/reviewing-changes.md +0 -29
- package/www/docs/stop-attributes.md +0 -30
- package/www/docs/support.md +0 -12
- package/www/docs/timetable-notes-references.md +0 -44
- package/www/docs/timetable-notes.md +0 -33
- package/www/docs/timetable-pages.md +0 -37
- package/www/docs/timetable-stop-order.md +0 -63
- package/www/docs/timetables.md +0 -64
- package/www/docusaurus.config.js +0 -104
- package/www/package.json +0 -21
- package/www/sidebars.js +0 -10
- package/www/src/css/custom.css +0 -25
- package/www/src/pages/index.js +0 -270
- package/www/src/pages/styles.module.css +0 -53
- package/www/static/.nojekyll +0 -0
- package/www/static/img/favicon.ico +0 -0
- package/www/static/img/gtfs-to-html-logo.svg +0 -18
- package/www/static/img/overview-example.jpg +0 -0
- package/www/static/img/timetable-example.jpg +0 -0
- package/www/static/img/undraw_happy_music.svg +0 -1
- package/www/static/img/undraw_proud_coder.svg +0 -1
- package/www/static/img/undraw_spreadsheets.svg +0 -1
- package/www/yarn.lock +0 -8351
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
-
|
|
2
|
-
function getTimetableSummary(timetable) {
|
|
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 === 'vertical') {
|
|
5
|
-
summary += ' Stops and their schedule times are listed in the columns.';
|
|
6
|
-
} else if (timetable.orientation === 'horizontal') {
|
|
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 === 'hourly') {
|
|
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
|
-
}
|
|
11
|
-
return summary;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function isNullOrEmpty(value) {
|
|
15
|
-
return value === null || value === '';
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function formatFrequencyWarning(frequencies) {
|
|
19
|
-
let warning = 'Trip times shown below are an example only. ';
|
|
20
|
-
frequencies.forEach((frequency, idx) => {
|
|
21
|
-
if (idx === 0) {
|
|
22
|
-
warning += 'This route runs every ';
|
|
23
|
-
} else {
|
|
24
|
-
warning += ' and ';
|
|
25
|
-
}
|
|
26
|
-
warning += `${frequency.headway_min} minutes between ${frequency.start_formatted_time} and ${frequency.end_formatted_time}`;
|
|
27
|
-
});
|
|
28
|
-
warning += '.';
|
|
29
|
-
return warning;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function formatAgencyName(agency) {
|
|
33
|
-
if (!agency || !agency.agency_name) {
|
|
34
|
-
return '';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return agency.agency_name;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function getAgencyTimetableGroups(timetablePages, agencies) {
|
|
41
|
-
const agencyIds = [];
|
|
42
|
-
for (const timetablePage of timetablePages) {
|
|
43
|
-
agencyIds.push(...timetablePage.agency_ids);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const uniqueAgencyIds = _.uniq(_.compact(agencyIds));
|
|
47
|
-
|
|
48
|
-
if (uniqueAgencyIds.length === 0) {
|
|
49
|
-
return [{
|
|
50
|
-
agency: _.first(agencies),
|
|
51
|
-
timetablePages
|
|
52
|
-
}];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return _.orderBy(uniqueAgencyIds.map(agencyId => {
|
|
56
|
-
return {
|
|
57
|
-
agency: agencies.find(agency => agency.agency_id === agencyId) || _.first(agencies),
|
|
58
|
-
timetablePages: timetablePages.filter(timetablePage => timetablePage.agency_ids.includes(agencyId))
|
|
59
|
-
};
|
|
60
|
-
}), timetableGroup => timetableGroup.agency.agency_name.toLowerCase());
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function prepareMapData(timetable) {
|
|
64
|
-
const routes = {}
|
|
65
|
-
const minifiedGeojson = {
|
|
66
|
-
type: 'FeatureCollection',
|
|
67
|
-
features: []
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
for (const feature of timetable.geojson.features) {
|
|
71
|
-
if (feature.geometry.type.toLowerCase() === 'point') {
|
|
72
|
-
for (const route of feature.properties.routes) {
|
|
73
|
-
routes[route.route_id] = route
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
feature.properties.routes = feature.properties.routes.map(route => route.route_id)
|
|
77
|
-
|
|
78
|
-
minifiedGeojson.features.push(_.omit(feature, ['location_type', 'tts_stop_name']))
|
|
79
|
-
} else if (feature.geometry.type.toLowerCase() === 'linestring') {
|
|
80
|
-
feature.properties = {
|
|
81
|
-
route_color: feature.properties.route_color
|
|
82
|
-
}
|
|
83
|
-
minifiedGeojson.features.push(feature)
|
|
84
|
-
} else if (feature.geometry.type.toLowerCase() === 'multilinestring') {
|
|
85
|
-
feature.properties = {
|
|
86
|
-
route_color: feature.properties.route_color
|
|
87
|
-
}
|
|
88
|
-
minifiedGeojson.features.push(feature)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
id: `timetable_id_${formatHtmlId(timetable.timetable_id)}`,
|
|
94
|
-
routes,
|
|
95
|
-
geojson: minifiedGeojson
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function getRouteColorsAsCss (route) {
|
|
100
|
-
if (route && route.route_color) {
|
|
101
|
-
return `background: #${route.route_color}; color: #${route.route_text_color ?? 'ffffff'};`
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return ''
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function formatRouteName(route) {
|
|
108
|
-
if (isNullOrEmpty(route.route_long_name)) {
|
|
109
|
-
return `Route ${route.route_short_name}`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return route.route_long_name;
|
|
113
|
-
}
|
|
@@ -1,594 +0,0 @@
|
|
|
1
|
-
/* global window, document, _, $, mapboxgl */
|
|
2
|
-
/* eslint prefer-arrow-callback: "off", no-unused-vars: "off" */
|
|
3
|
-
|
|
4
|
-
const maps = {};
|
|
5
|
-
|
|
6
|
-
function formatRouteColor(route) {
|
|
7
|
-
return route.route_color || '#000000';
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function formatRouteTextColor(route) {
|
|
11
|
-
return route.route_text_color || '#FFFFFF';
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function formatRoute(route) {
|
|
15
|
-
const html = route.route_url
|
|
16
|
-
? $('<a>').attr('href', route.route_url)
|
|
17
|
-
: $('<div>');
|
|
18
|
-
|
|
19
|
-
html.addClass('map-route-item');
|
|
20
|
-
|
|
21
|
-
// Only add color swatch if route has a color
|
|
22
|
-
const routeItemDivs = [];
|
|
23
|
-
|
|
24
|
-
if (route.route_color) {
|
|
25
|
-
routeItemDivs.push(
|
|
26
|
-
$('<div>')
|
|
27
|
-
.addClass('route-color-swatch')
|
|
28
|
-
.css('backgroundColor', formatRouteColor(route))
|
|
29
|
-
.css('color', formatRouteTextColor(route))
|
|
30
|
-
.text(route.route_short_name ?? ''),
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
routeItemDivs.push(
|
|
34
|
-
$('<div>')
|
|
35
|
-
.addClass('underline-hover')
|
|
36
|
-
.text(route.route_long_name ?? `Route ${route.route_short_name}`),
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
html.append(routeItemDivs);
|
|
40
|
-
|
|
41
|
-
return html.prop('outerHTML');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function formatRoutePopup(features) {
|
|
45
|
-
const html = $('<div>');
|
|
46
|
-
|
|
47
|
-
if (features.length > 1) {
|
|
48
|
-
$('<div>').addClass('popup-title').text('Routes').appendTo(html);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
$(html).append(features.map((feature) => formatRoute(feature.properties)));
|
|
52
|
-
|
|
53
|
-
return html.prop('outerHTML');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function formatStopPopup(feature) {
|
|
57
|
-
const routes = JSON.parse(feature.properties.routes);
|
|
58
|
-
const html = $('<div>');
|
|
59
|
-
|
|
60
|
-
$('<div>')
|
|
61
|
-
.addClass('popup-title')
|
|
62
|
-
.text(feature.properties.stop_name)
|
|
63
|
-
.appendTo(html);
|
|
64
|
-
|
|
65
|
-
if (feature.properties.stop_code ?? false) {
|
|
66
|
-
$('<div>')
|
|
67
|
-
.html([
|
|
68
|
-
$('<label>').addClass('popup-label').text('Stop Code:'),
|
|
69
|
-
$('<strong>').text(feature.properties.stop_code),
|
|
70
|
-
])
|
|
71
|
-
.appendTo(html);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
$('<label>').text('Routes Served:').appendTo(html);
|
|
75
|
-
|
|
76
|
-
$(html).append(
|
|
77
|
-
$('<div>')
|
|
78
|
-
.addClass('route-list')
|
|
79
|
-
.html(routes.map((route) => formatRoute(route))),
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
$('<a>')
|
|
83
|
-
.addClass('btn-blue btn-sm')
|
|
84
|
-
.prop(
|
|
85
|
-
'href',
|
|
86
|
-
`https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=${feature.geometry.coordinates[1]},${feature.geometry.coordinates[0]}&heading=0&pitch=0&fov=90`,
|
|
87
|
-
)
|
|
88
|
-
.prop('target', '_blank')
|
|
89
|
-
.prop('rel', 'noopener noreferrer')
|
|
90
|
-
.html('View on Streetview')
|
|
91
|
-
.appendTo(html);
|
|
92
|
-
|
|
93
|
-
return html.prop('outerHTML');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function getBounds(geojson) {
|
|
97
|
-
const bounds = new mapboxgl.LngLatBounds();
|
|
98
|
-
for (const feature of geojson.features) {
|
|
99
|
-
if (feature.geometry.type.toLowerCase() === 'point') {
|
|
100
|
-
bounds.extend(feature.geometry.coordinates);
|
|
101
|
-
} else if (feature.geometry.type.toLowerCase() === 'linestring') {
|
|
102
|
-
for (const coordinate of feature.geometry.coordinates) {
|
|
103
|
-
bounds.extend(coordinate);
|
|
104
|
-
}
|
|
105
|
-
} else if (feature.geometry.type.toLowerCase() === 'multilinestring') {
|
|
106
|
-
for (const linestring of feature.geometry.coordinates) {
|
|
107
|
-
for (const coordinate of linestring) {
|
|
108
|
-
bounds.extend(coordinate);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return bounds;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function createSystemMap(id, geojson) {
|
|
118
|
-
const defaultRouteColor = '#000000';
|
|
119
|
-
const lineLayout = {
|
|
120
|
-
'line-join': 'round',
|
|
121
|
-
'line-cap': 'round',
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
if (!geojson || geojson.features.length === 0) {
|
|
125
|
-
$('#' + id).hide();
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const bounds = getBounds(geojson);
|
|
130
|
-
const map = new mapboxgl.Map({
|
|
131
|
-
container: id,
|
|
132
|
-
style: 'mapbox://styles/mapbox/light-v10',
|
|
133
|
-
center: bounds.getCenter(),
|
|
134
|
-
zoom: 12,
|
|
135
|
-
});
|
|
136
|
-
const routes = {};
|
|
137
|
-
|
|
138
|
-
for (const feature of geojson.features) {
|
|
139
|
-
routes[feature.properties.route_id] = feature.properties;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
map.scrollZoom.disable();
|
|
143
|
-
map.addControl(new mapboxgl.NavigationControl());
|
|
144
|
-
|
|
145
|
-
map.on('load', () => {
|
|
146
|
-
map.fitBounds(bounds, {
|
|
147
|
-
padding: 20,
|
|
148
|
-
duration: 0,
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// Turn off Points of Interest labels
|
|
152
|
-
map.setLayoutProperty('poi-label', 'visibility', 'none');
|
|
153
|
-
|
|
154
|
-
// Find the index of the first symbol layer in the map style to put the route lines underneath
|
|
155
|
-
let firstSymbolId;
|
|
156
|
-
for (const layer of map.getStyle().layers) {
|
|
157
|
-
if (layer.type === 'symbol') {
|
|
158
|
-
firstSymbolId = layer.id;
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Add route drop shadow outline first
|
|
164
|
-
map.addLayer(
|
|
165
|
-
{
|
|
166
|
-
id: 'route-line-shadows',
|
|
167
|
-
type: 'line',
|
|
168
|
-
source: {
|
|
169
|
-
type: 'geojson',
|
|
170
|
-
data: geojson,
|
|
171
|
-
},
|
|
172
|
-
paint: {
|
|
173
|
-
'line-color': '#000000',
|
|
174
|
-
'line-opacity': 0.3,
|
|
175
|
-
'line-width': {
|
|
176
|
-
base: 12,
|
|
177
|
-
stops: [
|
|
178
|
-
[14, 20],
|
|
179
|
-
[18, 42],
|
|
180
|
-
],
|
|
181
|
-
},
|
|
182
|
-
'line-blur': {
|
|
183
|
-
base: 12,
|
|
184
|
-
stops: [
|
|
185
|
-
[14, 20],
|
|
186
|
-
[18, 42],
|
|
187
|
-
],
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
layout: lineLayout,
|
|
191
|
-
filter: ['!has', 'stop_id'],
|
|
192
|
-
},
|
|
193
|
-
firstSymbolId,
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
// Add highlighted route drop shadow outlines next
|
|
197
|
-
map.addLayer(
|
|
198
|
-
{
|
|
199
|
-
id: 'highlighted-route-line-shadows',
|
|
200
|
-
type: 'line',
|
|
201
|
-
source: {
|
|
202
|
-
type: 'geojson',
|
|
203
|
-
data: geojson,
|
|
204
|
-
},
|
|
205
|
-
paint: {
|
|
206
|
-
'line-color': '#000000',
|
|
207
|
-
'line-opacity': 0.3,
|
|
208
|
-
'line-width': {
|
|
209
|
-
base: 16,
|
|
210
|
-
stops: [
|
|
211
|
-
[14, 24],
|
|
212
|
-
[18, 50],
|
|
213
|
-
],
|
|
214
|
-
},
|
|
215
|
-
'line-blur': {
|
|
216
|
-
base: 16,
|
|
217
|
-
stops: [
|
|
218
|
-
[14, 24],
|
|
219
|
-
[18, 50],
|
|
220
|
-
],
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
layout: lineLayout,
|
|
224
|
-
filter: ['==', ['get', 'route_id'], 'none'],
|
|
225
|
-
},
|
|
226
|
-
firstSymbolId,
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
// Add white outlines to routes next
|
|
230
|
-
map.addLayer(
|
|
231
|
-
{
|
|
232
|
-
id: `route-outlines`,
|
|
233
|
-
type: 'line',
|
|
234
|
-
source: {
|
|
235
|
-
type: 'geojson',
|
|
236
|
-
data: geojson,
|
|
237
|
-
},
|
|
238
|
-
paint: {
|
|
239
|
-
'line-color': '#FFFFFF',
|
|
240
|
-
'line-opacity': 1,
|
|
241
|
-
'line-width': {
|
|
242
|
-
base: 8,
|
|
243
|
-
stops: [
|
|
244
|
-
[14, 12],
|
|
245
|
-
[18, 32],
|
|
246
|
-
],
|
|
247
|
-
},
|
|
248
|
-
},
|
|
249
|
-
layout: lineLayout,
|
|
250
|
-
filter: ['has', 'route_id'],
|
|
251
|
-
},
|
|
252
|
-
firstSymbolId,
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
// Add route lines next
|
|
256
|
-
map.addLayer(
|
|
257
|
-
{
|
|
258
|
-
id: 'routes',
|
|
259
|
-
type: 'line',
|
|
260
|
-
source: {
|
|
261
|
-
type: 'geojson',
|
|
262
|
-
data: geojson,
|
|
263
|
-
},
|
|
264
|
-
paint: {
|
|
265
|
-
'line-color': ['coalesce', ['get', 'route_color'], defaultRouteColor],
|
|
266
|
-
'line-opacity': 1,
|
|
267
|
-
'line-width': {
|
|
268
|
-
base: 4,
|
|
269
|
-
stops: [
|
|
270
|
-
[14, 6],
|
|
271
|
-
[18, 16],
|
|
272
|
-
],
|
|
273
|
-
},
|
|
274
|
-
},
|
|
275
|
-
layout: lineLayout,
|
|
276
|
-
filter: ['has', 'route_id'],
|
|
277
|
-
},
|
|
278
|
-
firstSymbolId,
|
|
279
|
-
);
|
|
280
|
-
|
|
281
|
-
// Add highlighted route white outlines next
|
|
282
|
-
map.addLayer(
|
|
283
|
-
{
|
|
284
|
-
id: `highlighted-route-outlines`,
|
|
285
|
-
type: 'line',
|
|
286
|
-
source: {
|
|
287
|
-
type: 'geojson',
|
|
288
|
-
data: geojson,
|
|
289
|
-
},
|
|
290
|
-
paint: {
|
|
291
|
-
'line-color': '#FFFFFF',
|
|
292
|
-
'line-opacity': 1,
|
|
293
|
-
'line-width': {
|
|
294
|
-
base: 10,
|
|
295
|
-
stops: [
|
|
296
|
-
[14, 16],
|
|
297
|
-
[18, 40],
|
|
298
|
-
],
|
|
299
|
-
},
|
|
300
|
-
},
|
|
301
|
-
layout: lineLayout,
|
|
302
|
-
filter: ['==', ['get', 'route_id'], 'none'],
|
|
303
|
-
},
|
|
304
|
-
firstSymbolId,
|
|
305
|
-
);
|
|
306
|
-
|
|
307
|
-
// Add highlighted route lines next
|
|
308
|
-
map.addLayer(
|
|
309
|
-
{
|
|
310
|
-
id: 'highlighted-routes',
|
|
311
|
-
type: 'line',
|
|
312
|
-
source: {
|
|
313
|
-
type: 'geojson',
|
|
314
|
-
data: geojson,
|
|
315
|
-
},
|
|
316
|
-
paint: {
|
|
317
|
-
'line-color': ['coalesce', ['get', 'route_color'], defaultRouteColor],
|
|
318
|
-
'line-opacity': 1,
|
|
319
|
-
'line-width': {
|
|
320
|
-
base: 6,
|
|
321
|
-
stops: [
|
|
322
|
-
[14, 8],
|
|
323
|
-
[18, 20],
|
|
324
|
-
],
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
layout: lineLayout,
|
|
328
|
-
filter: ['==', ['get', 'route_id'], 'none'],
|
|
329
|
-
},
|
|
330
|
-
firstSymbolId,
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
// Add stops when zoomed in
|
|
334
|
-
map.addLayer({
|
|
335
|
-
id: 'stops',
|
|
336
|
-
type: 'circle',
|
|
337
|
-
source: {
|
|
338
|
-
type: 'geojson',
|
|
339
|
-
data: geojson,
|
|
340
|
-
},
|
|
341
|
-
paint: {
|
|
342
|
-
'circle-color': '#fff',
|
|
343
|
-
'circle-radius': {
|
|
344
|
-
base: 1.75,
|
|
345
|
-
stops: [
|
|
346
|
-
[12, 4],
|
|
347
|
-
[22, 100],
|
|
348
|
-
],
|
|
349
|
-
},
|
|
350
|
-
'circle-stroke-color': '#3F4A5C',
|
|
351
|
-
'circle-stroke-width': 2,
|
|
352
|
-
'circle-opacity': ['interpolate', ['linear'], ['zoom'], 13, 0, 13.5, 1],
|
|
353
|
-
'circle-stroke-opacity': [
|
|
354
|
-
'interpolate',
|
|
355
|
-
['linear'],
|
|
356
|
-
['zoom'],
|
|
357
|
-
13,
|
|
358
|
-
0,
|
|
359
|
-
13.5,
|
|
360
|
-
1,
|
|
361
|
-
],
|
|
362
|
-
},
|
|
363
|
-
filter: ['has', 'stop_id'],
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
// Layer for highlighted stops
|
|
367
|
-
map.addLayer({
|
|
368
|
-
id: 'stops-highlighted',
|
|
369
|
-
type: 'circle',
|
|
370
|
-
source: {
|
|
371
|
-
type: 'geojson',
|
|
372
|
-
data: geojson,
|
|
373
|
-
},
|
|
374
|
-
paint: {
|
|
375
|
-
'circle-color': '#fff',
|
|
376
|
-
'circle-radius': {
|
|
377
|
-
base: 1.75,
|
|
378
|
-
stops: [
|
|
379
|
-
[12, 5],
|
|
380
|
-
[22, 125],
|
|
381
|
-
],
|
|
382
|
-
},
|
|
383
|
-
'circle-stroke-width': 2,
|
|
384
|
-
'circle-stroke-color': '#3f4a5c',
|
|
385
|
-
'circle-opacity': ['interpolate', ['linear'], ['zoom'], 13, 0, 13.5, 1],
|
|
386
|
-
'circle-stroke-opacity': [
|
|
387
|
-
'interpolate',
|
|
388
|
-
['linear'],
|
|
389
|
-
['zoom'],
|
|
390
|
-
13,
|
|
391
|
-
0,
|
|
392
|
-
13.5,
|
|
393
|
-
1,
|
|
394
|
-
],
|
|
395
|
-
},
|
|
396
|
-
filter: ['==', 'stop_id', ''],
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
// Add labels
|
|
400
|
-
map.addLayer({
|
|
401
|
-
id: 'route-labels',
|
|
402
|
-
type: 'symbol',
|
|
403
|
-
source: {
|
|
404
|
-
type: 'geojson',
|
|
405
|
-
data: geojson,
|
|
406
|
-
},
|
|
407
|
-
layout: {
|
|
408
|
-
'symbol-placement': 'line',
|
|
409
|
-
'text-field': ['get', 'route_short_name'],
|
|
410
|
-
'text-size': 14,
|
|
411
|
-
},
|
|
412
|
-
paint: {
|
|
413
|
-
'text-color': '#000000',
|
|
414
|
-
'text-halo-width': 2,
|
|
415
|
-
'text-halo-color': '#ffffff',
|
|
416
|
-
},
|
|
417
|
-
filter: ['has', 'route_short_name'],
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
map.on('mousemove', (event) => {
|
|
421
|
-
const features = map.queryRenderedFeatures(event.point, {
|
|
422
|
-
layers: ['routes', 'route-outlines', 'stops-highlighted', 'stops'],
|
|
423
|
-
});
|
|
424
|
-
if (features.length > 0) {
|
|
425
|
-
map.getCanvas().style.cursor = 'pointer';
|
|
426
|
-
highlightRoutes(
|
|
427
|
-
_.compact(
|
|
428
|
-
_.uniq(features.map((feature) => feature.properties.route_id)),
|
|
429
|
-
),
|
|
430
|
-
);
|
|
431
|
-
|
|
432
|
-
if (features.some((feature) => feature.layer.id === 'stops')) {
|
|
433
|
-
highlightStop(
|
|
434
|
-
features.find((feature) => feature.layer.id === 'stops').properties
|
|
435
|
-
.stop_id,
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
} else {
|
|
439
|
-
map.getCanvas().style.cursor = '';
|
|
440
|
-
unHighlightRoutes();
|
|
441
|
-
unHighlightStop();
|
|
442
|
-
}
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
map.on('click', (event) => {
|
|
446
|
-
// Set bbox as 5px rectangle area around clicked point
|
|
447
|
-
const bbox = [
|
|
448
|
-
[event.point.x - 5, event.point.y - 5],
|
|
449
|
-
[event.point.x + 5, event.point.y + 5],
|
|
450
|
-
];
|
|
451
|
-
|
|
452
|
-
const stopFeatures = map.queryRenderedFeatures(bbox, {
|
|
453
|
-
layers: ['stops-highlighted', 'stops'],
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
if (stopFeatures && stopFeatures.length > 0) {
|
|
457
|
-
// Get the stop feature and show popup
|
|
458
|
-
const stopFeature = stopFeatures[0];
|
|
459
|
-
|
|
460
|
-
new mapboxgl.Popup()
|
|
461
|
-
.setLngLat(stopFeature.geometry.coordinates)
|
|
462
|
-
.setHTML(formatStopPopup(stopFeature))
|
|
463
|
-
.addTo(map);
|
|
464
|
-
} else {
|
|
465
|
-
const routeFeatures = map.queryRenderedFeatures(bbox, {
|
|
466
|
-
layers: ['routes', 'route-outlines'],
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
if (routeFeatures && routeFeatures.length > 0) {
|
|
470
|
-
const routes = _.orderBy(
|
|
471
|
-
_.uniqBy(
|
|
472
|
-
routeFeatures,
|
|
473
|
-
(feature) => feature.properties.route_short_name,
|
|
474
|
-
),
|
|
475
|
-
(feature) =>
|
|
476
|
-
Number.parseInt(feature.properties.route_short_name, 10),
|
|
477
|
-
);
|
|
478
|
-
|
|
479
|
-
new mapboxgl.Popup()
|
|
480
|
-
.setLngLat(event.lngLat)
|
|
481
|
-
.setHTML(formatRoutePopup(routes))
|
|
482
|
-
.addTo(map);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
function highlightStop(stopId) {
|
|
488
|
-
map.setFilter('stops-highlighted', ['==', 'stop_id', stopId]);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
function unHighlightStop() {
|
|
492
|
-
map.setFilter('stops-highlighted', ['==', 'stop_id', '']);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function highlightRoutes(routeIds, zoom) {
|
|
496
|
-
map.setFilter('highlighted-routes', [
|
|
497
|
-
'all',
|
|
498
|
-
['has', 'route_short_name'],
|
|
499
|
-
['in', ['get', 'route_id'], ['literal', routeIds]],
|
|
500
|
-
]);
|
|
501
|
-
map.setFilter('highlighted-route-outlines', [
|
|
502
|
-
'all',
|
|
503
|
-
['has', 'route_short_name'],
|
|
504
|
-
['in', ['get', 'route_id'], ['literal', routeIds]],
|
|
505
|
-
]);
|
|
506
|
-
map.setFilter('highlighted-route-line-shadows', [
|
|
507
|
-
'all',
|
|
508
|
-
['has', 'route_short_name'],
|
|
509
|
-
['in', ['get', 'route_id'], ['literal', routeIds]],
|
|
510
|
-
]);
|
|
511
|
-
|
|
512
|
-
// Show labels only for highlighted route
|
|
513
|
-
map.setFilter('route-labels', [
|
|
514
|
-
'in',
|
|
515
|
-
['get', 'route_id'],
|
|
516
|
-
['literal', routeIds],
|
|
517
|
-
]);
|
|
518
|
-
|
|
519
|
-
const routeLineOpacity = 0.4;
|
|
520
|
-
|
|
521
|
-
// De-emphasize other routes
|
|
522
|
-
map.setPaintProperty('routes', 'line-opacity', routeLineOpacity);
|
|
523
|
-
map.setPaintProperty('route-outlines', 'line-opacity', routeLineOpacity);
|
|
524
|
-
map.setPaintProperty(
|
|
525
|
-
'route-line-shadows',
|
|
526
|
-
'line-opacity',
|
|
527
|
-
routeLineOpacity,
|
|
528
|
-
);
|
|
529
|
-
|
|
530
|
-
const highlightedFeatures = geojson.features.filter((feature) =>
|
|
531
|
-
routeIds.includes(feature.properties.route_id),
|
|
532
|
-
);
|
|
533
|
-
|
|
534
|
-
if (highlightedFeatures.length > 0 && zoom) {
|
|
535
|
-
const zoomBounds = getBounds({
|
|
536
|
-
features: highlightedFeatures,
|
|
537
|
-
});
|
|
538
|
-
map.fitBounds(zoomBounds, {
|
|
539
|
-
padding: 20,
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
function unHighlightRoutes(zoom) {
|
|
545
|
-
map.setFilter('highlighted-routes', ['==', ['get', 'route_id'], 'none']);
|
|
546
|
-
map.setFilter('highlighted-route-outlines', [
|
|
547
|
-
'==',
|
|
548
|
-
['get', 'route_id'],
|
|
549
|
-
'none',
|
|
550
|
-
]);
|
|
551
|
-
map.setFilter('highlighted-route-line-shadows', [
|
|
552
|
-
'==',
|
|
553
|
-
['get', 'route_id'],
|
|
554
|
-
'none',
|
|
555
|
-
]);
|
|
556
|
-
|
|
557
|
-
// Show labels for all routes
|
|
558
|
-
map.setFilter('route-labels', ['has', 'route_short_name']);
|
|
559
|
-
|
|
560
|
-
const routeLineOpacity = 1;
|
|
561
|
-
|
|
562
|
-
// Re-emphasize other routes
|
|
563
|
-
map.setPaintProperty('routes', 'line-opacity', routeLineOpacity);
|
|
564
|
-
map.setPaintProperty('route-outlines', 'line-opacity', routeLineOpacity);
|
|
565
|
-
map.setPaintProperty(
|
|
566
|
-
'route-line-shadows',
|
|
567
|
-
'line-opacity',
|
|
568
|
-
routeLineOpacity,
|
|
569
|
-
);
|
|
570
|
-
|
|
571
|
-
if (zoom) {
|
|
572
|
-
map.fitBounds(bounds);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// On table hover, highlight route on map
|
|
577
|
-
$(() => {
|
|
578
|
-
$('.overview-list a').hover((event) => {
|
|
579
|
-
const routeIdString = $(event.target).data('route-ids');
|
|
580
|
-
if (routeIdString) {
|
|
581
|
-
const routeIds = routeIdString.toString().split(',');
|
|
582
|
-
highlightRoutes(routeIds, true);
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
|
|
586
|
-
$('.overview-list').hover(
|
|
587
|
-
() => {},
|
|
588
|
-
() => unHighlightRoutes(true),
|
|
589
|
-
);
|
|
590
|
-
});
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
maps[id] = map;
|
|
594
|
-
}
|