gtfs-to-html 2.9.14 → 2.10.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/README.md +3 -1
- package/config-sample.json +0 -1
- package/dist/app/index.js +104 -55
- package/dist/app/index.js.map +1 -1
- package/dist/bin/gtfs-to-html.js +29 -17
- package/dist/bin/gtfs-to-html.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +29 -17
- package/dist/index.js.map +1 -1
- package/package.json +10 -11
- package/views/default/css/overview_styles.css +11 -9
- package/views/default/css/timetable_styles.css +17 -15
- package/views/default/formatting_functions.pug +2 -1
- package/views/default/js/system-map.js +492 -400
- package/views/default/js/timetable-map.js +390 -286
- package/views/default/overview.pug +3 -4
- package/views/default/overview_full.pug +4 -4
- package/views/default/timetablepage.pug +1 -1
- package/views/default/timetablepage_full.pug +2 -4
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
/* global
|
|
1
|
+
/* global document, jQuery, _, maplibregl, geojson, mapStyleUrl */
|
|
2
2
|
/* eslint prefer-arrow-callback: "off", no-unused-vars: "off" */
|
|
3
3
|
|
|
4
|
-
const maps = {};
|
|
5
|
-
|
|
6
4
|
function formatRouteColor(route) {
|
|
7
5
|
return route.route_color || '#000000';
|
|
8
6
|
}
|
|
@@ -18,7 +16,6 @@ function formatRoute(route) {
|
|
|
18
16
|
|
|
19
17
|
html.addClass('map-route-item');
|
|
20
18
|
|
|
21
|
-
// Only add color swatch if route has a color
|
|
22
19
|
const routeItemDivs = [];
|
|
23
20
|
|
|
24
21
|
if (route.route_color) {
|
|
@@ -67,13 +64,13 @@ function formatStopPopup(feature) {
|
|
|
67
64
|
if (feature.properties.stop_code ?? false) {
|
|
68
65
|
jQuery('<div>')
|
|
69
66
|
.html([
|
|
70
|
-
jQuery('<
|
|
67
|
+
jQuery('<div>').addClass('popup-label').text('Stop Code:'),
|
|
71
68
|
jQuery('<strong>').text(feature.properties.stop_code),
|
|
72
69
|
])
|
|
73
70
|
.appendTo(html);
|
|
74
71
|
}
|
|
75
72
|
|
|
76
|
-
jQuery('<
|
|
73
|
+
jQuery('<div>').addClass('popup-label').text('Routes Served:').appendTo(html);
|
|
77
74
|
|
|
78
75
|
jQuery(html).append(
|
|
79
76
|
jQuery('<div>')
|
|
@@ -96,7 +93,7 @@ function formatStopPopup(feature) {
|
|
|
96
93
|
}
|
|
97
94
|
|
|
98
95
|
function getBounds(geojson) {
|
|
99
|
-
const bounds = new
|
|
96
|
+
const bounds = new maplibregl.LngLatBounds();
|
|
100
97
|
for (const feature of geojson.features) {
|
|
101
98
|
if (feature.geometry.type.toLowerCase() === 'point') {
|
|
102
99
|
bounds.extend(feature.geometry.coordinates);
|
|
@@ -116,7 +113,7 @@ function getBounds(geojson) {
|
|
|
116
113
|
return bounds;
|
|
117
114
|
}
|
|
118
115
|
|
|
119
|
-
function createSystemMap(
|
|
116
|
+
function createSystemMap() {
|
|
120
117
|
const defaultRouteColor = '#000000';
|
|
121
118
|
const lineLayout = {
|
|
122
119
|
'line-join': 'round',
|
|
@@ -129,9 +126,9 @@ function createSystemMap(id, geojson) {
|
|
|
129
126
|
}
|
|
130
127
|
|
|
131
128
|
const bounds = getBounds(geojson);
|
|
132
|
-
const map = new
|
|
133
|
-
container:
|
|
134
|
-
style:
|
|
129
|
+
const map = new maplibregl.Map({
|
|
130
|
+
container: 'system_map',
|
|
131
|
+
style: mapStyleUrl,
|
|
135
132
|
center: bounds.getCenter(),
|
|
136
133
|
zoom: 12,
|
|
137
134
|
});
|
|
@@ -142,399 +139,493 @@ function createSystemMap(id, geojson) {
|
|
|
142
139
|
}
|
|
143
140
|
|
|
144
141
|
map.scrollZoom.disable();
|
|
145
|
-
map.addControl(new
|
|
146
|
-
|
|
147
|
-
map.on('load', () => {
|
|
148
|
-
map.fitBounds(bounds, {
|
|
149
|
-
padding: 20,
|
|
150
|
-
duration: 0,
|
|
151
|
-
});
|
|
142
|
+
map.addControl(new maplibregl.NavigationControl());
|
|
143
|
+
map.addControl(new maplibregl.FullscreenControl());
|
|
152
144
|
|
|
153
|
-
|
|
154
|
-
map.setLayoutProperty('poi-label', 'visibility', 'none');
|
|
145
|
+
addGeocoder(map, bounds);
|
|
155
146
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
147
|
+
map.on('load', () => {
|
|
148
|
+
fitMapToBounds(map, bounds);
|
|
149
|
+
disablePointsOfInterest(map);
|
|
150
|
+
addMapLayers(map, geojson, defaultRouteColor, lineLayout);
|
|
151
|
+
setupEventListeners(map, routes);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
164
154
|
|
|
165
|
-
|
|
166
|
-
|
|
155
|
+
function addGeocoder(map, bounds) {
|
|
156
|
+
map.addControl(
|
|
157
|
+
new MaplibreGeocoder(
|
|
167
158
|
{
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
159
|
+
forwardGeocode: async (config) => {
|
|
160
|
+
const features = [];
|
|
161
|
+
try {
|
|
162
|
+
const request = `https://nominatim.openstreetmap.org/search?q=${
|
|
163
|
+
config.query
|
|
164
|
+
}&format=geojson&polygon_geojson=1&addressdetails=1&viewbox=${bounds.getWest()},${bounds.getSouth()},${bounds.getEast()},${bounds.getNorth()}&bounded=1`;
|
|
165
|
+
const response = await fetch(request);
|
|
166
|
+
const geojson = await response.json();
|
|
167
|
+
for (const feature of geojson.features) {
|
|
168
|
+
const center = [
|
|
169
|
+
feature.bbox[0] + (feature.bbox[2] - feature.bbox[0]) / 2,
|
|
170
|
+
feature.bbox[1] + (feature.bbox[3] - feature.bbox[1]) / 2,
|
|
171
|
+
];
|
|
172
|
+
const point = {
|
|
173
|
+
type: 'Feature',
|
|
174
|
+
geometry: {
|
|
175
|
+
type: 'Point',
|
|
176
|
+
coordinates: center,
|
|
177
|
+
},
|
|
178
|
+
place_name: feature.properties.display_name,
|
|
179
|
+
properties: feature.properties,
|
|
180
|
+
text: feature.properties.display_name,
|
|
181
|
+
place_type: ['place'],
|
|
182
|
+
center,
|
|
183
|
+
};
|
|
184
|
+
features.push(point);
|
|
185
|
+
}
|
|
186
|
+
} catch (e) {
|
|
187
|
+
console.error(`Failed to forwardGeocode with error: ${e}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
features,
|
|
192
|
+
type: 'FeatureCollection',
|
|
193
|
+
};
|
|
191
194
|
},
|
|
192
|
-
layout: lineLayout,
|
|
193
|
-
filter: ['!has', 'stop_id'],
|
|
194
195
|
},
|
|
195
|
-
firstSymbolId,
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
// Add highlighted route drop shadow outlines next
|
|
199
|
-
map.addLayer(
|
|
200
196
|
{
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
source: {
|
|
204
|
-
type: 'geojson',
|
|
205
|
-
data: geojson,
|
|
206
|
-
},
|
|
207
|
-
paint: {
|
|
208
|
-
'line-color': '#000000',
|
|
209
|
-
'line-opacity': 0.3,
|
|
210
|
-
'line-width': {
|
|
211
|
-
base: 16,
|
|
212
|
-
stops: [
|
|
213
|
-
[14, 24],
|
|
214
|
-
[18, 50],
|
|
215
|
-
],
|
|
216
|
-
},
|
|
217
|
-
'line-blur': {
|
|
218
|
-
base: 16,
|
|
219
|
-
stops: [
|
|
220
|
-
[14, 24],
|
|
221
|
-
[18, 50],
|
|
222
|
-
],
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
layout: lineLayout,
|
|
226
|
-
filter: ['==', ['get', 'route_id'], 'none'],
|
|
197
|
+
maplibregl,
|
|
198
|
+
zoom: 12,
|
|
227
199
|
},
|
|
228
|
-
|
|
229
|
-
|
|
200
|
+
),
|
|
201
|
+
'top-left',
|
|
202
|
+
);
|
|
203
|
+
}
|
|
230
204
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
205
|
+
function fitMapToBounds(map, bounds) {
|
|
206
|
+
map.fitBounds(bounds, {
|
|
207
|
+
padding: 20,
|
|
208
|
+
duration: 0,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function disablePointsOfInterest(map) {
|
|
213
|
+
const layers = map.getStyle().layers;
|
|
214
|
+
const poiLayerIds = layers
|
|
215
|
+
.filter((layer) => layer.id.startsWith('poi'))
|
|
216
|
+
?.map((layer) => layer.id);
|
|
217
|
+
poiLayerIds.forEach((layerId) => {
|
|
218
|
+
map.setLayoutProperty(layerId, 'visibility', 'none');
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function addMapLayers(map, geojson, defaultRouteColor, lineLayout) {
|
|
223
|
+
const layers = map.getStyle().layers;
|
|
224
|
+
const firstLabelLayerId = layers.find(
|
|
225
|
+
(layer) => layer.type === 'symbol' && layer.id.includes('label'),
|
|
226
|
+
)?.id;
|
|
227
|
+
|
|
228
|
+
addRouteLineShadow(map, geojson, lineLayout, firstLabelLayerId);
|
|
229
|
+
addHighlightedRouteLineShadow(map, geojson, lineLayout, firstLabelLayerId);
|
|
230
|
+
addRouteLineOutline(map, geojson, lineLayout, firstLabelLayerId);
|
|
231
|
+
addHighlightedRouteLineOutline(map, geojson, lineLayout, firstLabelLayerId);
|
|
232
|
+
addRouteLine(map, geojson, defaultRouteColor, lineLayout, firstLabelLayerId);
|
|
233
|
+
addHighlightedRouteLine(
|
|
234
|
+
map,
|
|
235
|
+
geojson,
|
|
236
|
+
defaultRouteColor,
|
|
237
|
+
lineLayout,
|
|
238
|
+
firstLabelLayerId,
|
|
239
|
+
);
|
|
240
|
+
addStops(map, geojson);
|
|
241
|
+
addHighlightedStops(map, geojson);
|
|
242
|
+
addRouteLabels(map, geojson);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function getFirstSymbolLayerId(map) {
|
|
246
|
+
const layers = map.getStyle().layers;
|
|
247
|
+
return layers.find((layer) => layer.type === 'symbol').id;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function addRouteLineShadow(map, geojson, lineLayout, firstSymbolId) {
|
|
251
|
+
map.addLayer(
|
|
252
|
+
{
|
|
253
|
+
id: 'route-line-shadows',
|
|
254
|
+
type: 'line',
|
|
255
|
+
source: { type: 'geojson', data: geojson },
|
|
256
|
+
paint: {
|
|
257
|
+
'line-color': '#000000',
|
|
258
|
+
'line-opacity': 0.3,
|
|
259
|
+
'line-width': {
|
|
260
|
+
base: 12,
|
|
261
|
+
stops: [
|
|
262
|
+
[14, 20],
|
|
263
|
+
[18, 42],
|
|
264
|
+
],
|
|
239
265
|
},
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
[14, 12],
|
|
247
|
-
[18, 32],
|
|
248
|
-
],
|
|
249
|
-
},
|
|
266
|
+
'line-blur': {
|
|
267
|
+
base: 12,
|
|
268
|
+
stops: [
|
|
269
|
+
[14, 20],
|
|
270
|
+
[18, 42],
|
|
271
|
+
],
|
|
250
272
|
},
|
|
251
|
-
layout: lineLayout,
|
|
252
|
-
filter: ['has', 'route_id'],
|
|
253
273
|
},
|
|
254
|
-
|
|
255
|
-
|
|
274
|
+
layout: lineLayout,
|
|
275
|
+
filter: ['!has', 'stop_id'],
|
|
276
|
+
},
|
|
277
|
+
firstSymbolId,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
256
280
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
281
|
+
function addHighlightedRouteLineShadow(
|
|
282
|
+
map,
|
|
283
|
+
geojson,
|
|
284
|
+
lineLayout,
|
|
285
|
+
firstSymbolId,
|
|
286
|
+
) {
|
|
287
|
+
map.addLayer(
|
|
288
|
+
{
|
|
289
|
+
id: 'highlighted-route-line-shadows',
|
|
290
|
+
type: 'line',
|
|
291
|
+
source: { type: 'geojson', data: geojson },
|
|
292
|
+
paint: {
|
|
293
|
+
'line-color': '#000000',
|
|
294
|
+
'line-opacity': 0.3,
|
|
295
|
+
'line-width': {
|
|
296
|
+
base: 16,
|
|
297
|
+
stops: [
|
|
298
|
+
[14, 24],
|
|
299
|
+
[18, 50],
|
|
300
|
+
],
|
|
265
301
|
},
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
[14, 6],
|
|
273
|
-
[18, 16],
|
|
274
|
-
],
|
|
275
|
-
},
|
|
302
|
+
'line-blur': {
|
|
303
|
+
base: 16,
|
|
304
|
+
stops: [
|
|
305
|
+
[14, 24],
|
|
306
|
+
[18, 50],
|
|
307
|
+
],
|
|
276
308
|
},
|
|
277
|
-
layout: lineLayout,
|
|
278
|
-
filter: ['has', 'route_id'],
|
|
279
309
|
},
|
|
280
|
-
|
|
281
|
-
|
|
310
|
+
layout: lineLayout,
|
|
311
|
+
filter: ['==', ['get', 'route_id'], 'none'],
|
|
312
|
+
},
|
|
313
|
+
firstSymbolId,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
282
316
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
[14, 16],
|
|
299
|
-
[18, 40],
|
|
300
|
-
],
|
|
301
|
-
},
|
|
317
|
+
function addRouteLineOutline(map, geojson, lineLayout, firstSymbolId) {
|
|
318
|
+
map.addLayer(
|
|
319
|
+
{
|
|
320
|
+
id: 'route-outlines',
|
|
321
|
+
type: 'line',
|
|
322
|
+
source: { type: 'geojson', data: geojson },
|
|
323
|
+
paint: {
|
|
324
|
+
'line-color': '#FFFFFF',
|
|
325
|
+
'line-opacity': 1,
|
|
326
|
+
'line-width': {
|
|
327
|
+
base: 8,
|
|
328
|
+
stops: [
|
|
329
|
+
[14, 12],
|
|
330
|
+
[18, 32],
|
|
331
|
+
],
|
|
302
332
|
},
|
|
303
|
-
layout: lineLayout,
|
|
304
|
-
filter: ['==', ['get', 'route_id'], 'none'],
|
|
305
333
|
},
|
|
306
|
-
|
|
307
|
-
|
|
334
|
+
layout: lineLayout,
|
|
335
|
+
filter: ['has', 'route_id'],
|
|
336
|
+
},
|
|
337
|
+
firstSymbolId,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
308
340
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
],
|
|
327
|
-
|
|
341
|
+
function addHighlightedRouteLineOutline(
|
|
342
|
+
map,
|
|
343
|
+
geojson,
|
|
344
|
+
lineLayout,
|
|
345
|
+
firstSymbolId,
|
|
346
|
+
) {
|
|
347
|
+
map.addLayer(
|
|
348
|
+
{
|
|
349
|
+
id: 'highlighted-route-outlines',
|
|
350
|
+
type: 'line',
|
|
351
|
+
source: { type: 'geojson', data: geojson },
|
|
352
|
+
paint: {
|
|
353
|
+
'line-color': '#FFFFFF',
|
|
354
|
+
'line-opacity': 1,
|
|
355
|
+
'line-width': {
|
|
356
|
+
base: 10,
|
|
357
|
+
stops: [
|
|
358
|
+
[14, 16],
|
|
359
|
+
[18, 40],
|
|
360
|
+
],
|
|
328
361
|
},
|
|
329
|
-
layout: lineLayout,
|
|
330
|
-
filter: ['==', ['get', 'route_id'], 'none'],
|
|
331
362
|
},
|
|
332
|
-
|
|
333
|
-
|
|
363
|
+
layout: lineLayout,
|
|
364
|
+
filter: ['==', ['get', 'route_id'], 'none'],
|
|
365
|
+
},
|
|
366
|
+
firstSymbolId,
|
|
367
|
+
);
|
|
368
|
+
}
|
|
334
369
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
370
|
+
function addRouteLine(
|
|
371
|
+
map,
|
|
372
|
+
geojson,
|
|
373
|
+
defaultRouteColor,
|
|
374
|
+
lineLayout,
|
|
375
|
+
firstSymbolId,
|
|
376
|
+
) {
|
|
377
|
+
map.addLayer(
|
|
378
|
+
{
|
|
379
|
+
id: 'routes',
|
|
380
|
+
type: 'line',
|
|
381
|
+
source: { type: 'geojson', data: geojson },
|
|
343
382
|
paint: {
|
|
344
|
-
'
|
|
345
|
-
'
|
|
346
|
-
|
|
383
|
+
'line-color': ['coalesce', ['get', 'route_color'], defaultRouteColor],
|
|
384
|
+
'line-opacity': 1,
|
|
385
|
+
'line-width': {
|
|
386
|
+
base: 4,
|
|
347
387
|
stops: [
|
|
348
|
-
[
|
|
349
|
-
[
|
|
388
|
+
[14, 6],
|
|
389
|
+
[18, 16],
|
|
350
390
|
],
|
|
351
391
|
},
|
|
352
|
-
'circle-stroke-color': '#3F4A5C',
|
|
353
|
-
'circle-stroke-width': 2,
|
|
354
|
-
'circle-opacity': ['interpolate', ['linear'], ['zoom'], 13, 0, 13.5, 1],
|
|
355
|
-
'circle-stroke-opacity': [
|
|
356
|
-
'interpolate',
|
|
357
|
-
['linear'],
|
|
358
|
-
['zoom'],
|
|
359
|
-
13,
|
|
360
|
-
0,
|
|
361
|
-
13.5,
|
|
362
|
-
1,
|
|
363
|
-
],
|
|
364
392
|
},
|
|
365
|
-
|
|
366
|
-
|
|
393
|
+
layout: lineLayout,
|
|
394
|
+
filter: ['has', 'route_id'],
|
|
395
|
+
},
|
|
396
|
+
firstSymbolId,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
367
399
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
400
|
+
function addHighlightedRouteLine(
|
|
401
|
+
map,
|
|
402
|
+
geojson,
|
|
403
|
+
defaultRouteColor,
|
|
404
|
+
lineLayout,
|
|
405
|
+
firstSymbolId,
|
|
406
|
+
) {
|
|
407
|
+
map.addLayer(
|
|
408
|
+
{
|
|
409
|
+
id: 'highlighted-routes',
|
|
410
|
+
type: 'line',
|
|
411
|
+
source: { type: 'geojson', data: geojson },
|
|
376
412
|
paint: {
|
|
377
|
-
'
|
|
378
|
-
'
|
|
379
|
-
|
|
413
|
+
'line-color': ['coalesce', ['get', 'route_color'], defaultRouteColor],
|
|
414
|
+
'line-opacity': 1,
|
|
415
|
+
'line-width': {
|
|
416
|
+
base: 6,
|
|
380
417
|
stops: [
|
|
381
|
-
[
|
|
382
|
-
[
|
|
418
|
+
[14, 8],
|
|
419
|
+
[18, 20],
|
|
383
420
|
],
|
|
384
421
|
},
|
|
385
|
-
'circle-stroke-width': 2,
|
|
386
|
-
'circle-stroke-color': '#3f4a5c',
|
|
387
|
-
'circle-opacity': ['interpolate', ['linear'], ['zoom'], 13, 0, 13.5, 1],
|
|
388
|
-
'circle-stroke-opacity': [
|
|
389
|
-
'interpolate',
|
|
390
|
-
['linear'],
|
|
391
|
-
['zoom'],
|
|
392
|
-
13,
|
|
393
|
-
0,
|
|
394
|
-
13.5,
|
|
395
|
-
1,
|
|
396
|
-
],
|
|
397
422
|
},
|
|
398
|
-
|
|
399
|
-
|
|
423
|
+
layout: lineLayout,
|
|
424
|
+
filter: ['==', ['get', 'route_id'], 'none'],
|
|
425
|
+
},
|
|
426
|
+
firstSymbolId,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
400
429
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
430
|
+
function addStops(map, geojson) {
|
|
431
|
+
map.addLayer({
|
|
432
|
+
id: 'stops',
|
|
433
|
+
type: 'circle',
|
|
434
|
+
source: { type: 'geojson', data: geojson },
|
|
435
|
+
paint: {
|
|
436
|
+
'circle-color': '#fff',
|
|
437
|
+
'circle-radius': {
|
|
438
|
+
base: 1.75,
|
|
439
|
+
stops: [
|
|
440
|
+
[12, 4],
|
|
441
|
+
[22, 100],
|
|
442
|
+
],
|
|
413
443
|
},
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
444
|
+
'circle-stroke-color': '#3F4A5C',
|
|
445
|
+
'circle-stroke-width': 2,
|
|
446
|
+
'circle-opacity': ['interpolate', ['linear'], ['zoom'], 13, 0, 13.5, 1],
|
|
447
|
+
'circle-stroke-opacity': [
|
|
448
|
+
'interpolate',
|
|
449
|
+
['linear'],
|
|
450
|
+
['zoom'],
|
|
451
|
+
13,
|
|
452
|
+
0,
|
|
453
|
+
13.5,
|
|
454
|
+
1,
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
filter: ['has', 'stop_id'],
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function addHighlightedStops(map, geojson) {
|
|
462
|
+
map.addLayer({
|
|
463
|
+
id: 'stops-highlighted',
|
|
464
|
+
type: 'circle',
|
|
465
|
+
source: { type: 'geojson', data: geojson },
|
|
466
|
+
paint: {
|
|
467
|
+
'circle-color': '#fff',
|
|
468
|
+
'circle-radius': {
|
|
469
|
+
base: 1.75,
|
|
470
|
+
stops: [
|
|
471
|
+
[12, 5],
|
|
472
|
+
[22, 125],
|
|
473
|
+
],
|
|
418
474
|
},
|
|
419
|
-
|
|
420
|
-
|
|
475
|
+
'circle-stroke-width': 2,
|
|
476
|
+
'circle-stroke-color': '#3f4a5c',
|
|
477
|
+
'circle-opacity': ['interpolate', ['linear'], ['zoom'], 13, 0, 13.5, 1],
|
|
478
|
+
'circle-stroke-opacity': [
|
|
479
|
+
'interpolate',
|
|
480
|
+
['linear'],
|
|
481
|
+
['zoom'],
|
|
482
|
+
13,
|
|
483
|
+
0,
|
|
484
|
+
13.5,
|
|
485
|
+
1,
|
|
486
|
+
],
|
|
487
|
+
},
|
|
488
|
+
filter: ['==', 'stop_id', ''],
|
|
489
|
+
});
|
|
490
|
+
}
|
|
421
491
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
} else {
|
|
441
|
-
map.getCanvas().style.cursor = '';
|
|
442
|
-
unHighlightRoutes();
|
|
443
|
-
unHighlightStop();
|
|
444
|
-
}
|
|
445
|
-
});
|
|
492
|
+
function addRouteLabels(map, geojson) {
|
|
493
|
+
map.addLayer({
|
|
494
|
+
id: 'route-labels',
|
|
495
|
+
type: 'symbol',
|
|
496
|
+
source: { type: 'geojson', data: geojson },
|
|
497
|
+
layout: {
|
|
498
|
+
'symbol-placement': 'line',
|
|
499
|
+
'text-field': ['get', 'route_short_name'],
|
|
500
|
+
'text-size': 14,
|
|
501
|
+
},
|
|
502
|
+
paint: {
|
|
503
|
+
'text-color': '#000000',
|
|
504
|
+
'text-halo-width': 2,
|
|
505
|
+
'text-halo-color': '#ffffff',
|
|
506
|
+
},
|
|
507
|
+
filter: ['has', 'route_short_name'],
|
|
508
|
+
});
|
|
509
|
+
}
|
|
446
510
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
];
|
|
453
|
-
|
|
454
|
-
const stopFeatures = map.queryRenderedFeatures(bbox, {
|
|
455
|
-
layers: ['stops-highlighted', 'stops'],
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
if (stopFeatures && stopFeatures.length > 0) {
|
|
459
|
-
// Get the stop feature and show popup
|
|
460
|
-
const stopFeature = stopFeatures[0];
|
|
461
|
-
|
|
462
|
-
new mapboxgl.Popup()
|
|
463
|
-
.setLngLat(stopFeature.geometry.coordinates)
|
|
464
|
-
.setHTML(formatStopPopup(stopFeature))
|
|
465
|
-
.addTo(map);
|
|
466
|
-
} else {
|
|
467
|
-
const routeFeatures = map.queryRenderedFeatures(bbox, {
|
|
468
|
-
layers: ['routes', 'route-outlines'],
|
|
469
|
-
});
|
|
511
|
+
function setupEventListeners(map, routes) {
|
|
512
|
+
map.on('mousemove', (event) => handleMouseMove(event, map, routes));
|
|
513
|
+
map.on('click', (event) => handleClick(event, map));
|
|
514
|
+
setupTableHoverListeners(map);
|
|
515
|
+
}
|
|
470
516
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
new mapboxgl.Popup()
|
|
482
|
-
.setLngLat(event.lngLat)
|
|
483
|
-
.setHTML(formatRoutePopup(routes))
|
|
484
|
-
.addTo(map);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
});
|
|
517
|
+
function handleMouseMove(event, map, routes) {
|
|
518
|
+
const features = map.queryRenderedFeatures(event.point, {
|
|
519
|
+
layers: ['routes', 'route-outlines', 'stops-highlighted', 'stops'],
|
|
520
|
+
});
|
|
521
|
+
if (features.length > 0) {
|
|
522
|
+
map.getCanvas().style.cursor = 'pointer';
|
|
523
|
+
highlightRoutes(
|
|
524
|
+
map,
|
|
525
|
+
_.compact(_.uniq(features.map((feature) => feature.properties.route_id))),
|
|
526
|
+
);
|
|
488
527
|
|
|
489
|
-
|
|
490
|
-
|
|
528
|
+
if (features.some((feature) => feature.layer.id === 'stops')) {
|
|
529
|
+
highlightStop(
|
|
530
|
+
map,
|
|
531
|
+
features.find((feature) => feature.layer.id === 'stops').properties
|
|
532
|
+
.stop_id,
|
|
533
|
+
);
|
|
491
534
|
}
|
|
535
|
+
} else {
|
|
536
|
+
map.getCanvas().style.cursor = '';
|
|
537
|
+
unHighlightRoutes(map);
|
|
538
|
+
unHighlightStop(map);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function handleClick(event, map) {
|
|
543
|
+
const bbox = [
|
|
544
|
+
[event.point.x - 5, event.point.y - 5],
|
|
545
|
+
[event.point.x + 5, event.point.y + 5],
|
|
546
|
+
];
|
|
547
|
+
const stopFeatures = map.queryRenderedFeatures(bbox, {
|
|
548
|
+
layers: ['stops-highlighted', 'stops'],
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
if (stopFeatures && stopFeatures.length > 0) {
|
|
552
|
+
showStopPopup(map, stopFeatures[0]);
|
|
553
|
+
} else {
|
|
554
|
+
const routeFeatures = map.queryRenderedFeatures(bbox, {
|
|
555
|
+
layers: ['routes', 'route-outlines'],
|
|
556
|
+
});
|
|
492
557
|
|
|
493
|
-
|
|
494
|
-
map
|
|
558
|
+
if (routeFeatures && routeFeatures.length > 0) {
|
|
559
|
+
showRoutePopup(map, routeFeatures, event.lngLat);
|
|
495
560
|
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
496
563
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
]);
|
|
520
|
-
|
|
521
|
-
const routeLineOpacity = 0.4;
|
|
522
|
-
|
|
523
|
-
// De-emphasize other routes
|
|
524
|
-
map.setPaintProperty('routes', 'line-opacity', routeLineOpacity);
|
|
525
|
-
map.setPaintProperty('route-outlines', 'line-opacity', routeLineOpacity);
|
|
526
|
-
map.setPaintProperty(
|
|
527
|
-
'route-line-shadows',
|
|
528
|
-
'line-opacity',
|
|
529
|
-
routeLineOpacity,
|
|
530
|
-
);
|
|
564
|
+
function showStopPopup(map, feature) {
|
|
565
|
+
new maplibregl.Popup()
|
|
566
|
+
.setLngLat(feature.geometry.coordinates)
|
|
567
|
+
.setHTML(formatStopPopup(feature))
|
|
568
|
+
.addTo(map);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function showRoutePopup(map, features, lngLat) {
|
|
572
|
+
const routes = _.orderBy(
|
|
573
|
+
_.uniqBy(features, (feature) => feature.properties.route_short_name),
|
|
574
|
+
(feature) => Number.parseInt(feature.properties.route_short_name, 10),
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
new maplibregl.Popup()
|
|
578
|
+
.setLngLat(lngLat)
|
|
579
|
+
.setHTML(formatRoutePopup(routes))
|
|
580
|
+
.addTo(map);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function highlightStop(map, stopId) {
|
|
584
|
+
map.setFilter('stops-highlighted', ['==', 'stop_id', stopId]);
|
|
585
|
+
}
|
|
531
586
|
|
|
532
|
-
|
|
587
|
+
function unHighlightStop(map) {
|
|
588
|
+
map.setFilter('stops-highlighted', ['==', 'stop_id', '']);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function highlightRoutes(map, routeIds, zoom) {
|
|
592
|
+
map.setFilter('highlighted-routes', [
|
|
593
|
+
'all',
|
|
594
|
+
['has', 'route_short_name'],
|
|
595
|
+
['in', ['get', 'route_id'], ['literal', routeIds]],
|
|
596
|
+
]);
|
|
597
|
+
map.setFilter('highlighted-route-outlines', [
|
|
598
|
+
'all',
|
|
599
|
+
['has', 'route_short_name'],
|
|
600
|
+
['in', ['get', 'route_id'], ['literal', routeIds]],
|
|
601
|
+
]);
|
|
602
|
+
map.setFilter('highlighted-route-line-shadows', [
|
|
603
|
+
'all',
|
|
604
|
+
['has', 'route_short_name'],
|
|
605
|
+
['in', ['get', 'route_id'], ['literal', routeIds]],
|
|
606
|
+
]);
|
|
607
|
+
|
|
608
|
+
map.setFilter('route-labels', [
|
|
609
|
+
'in',
|
|
610
|
+
['get', 'route_id'],
|
|
611
|
+
['literal', routeIds],
|
|
612
|
+
]);
|
|
613
|
+
|
|
614
|
+
const routeLineOpacity = 0.4;
|
|
615
|
+
|
|
616
|
+
map.setPaintProperty('routes', 'line-opacity', routeLineOpacity);
|
|
617
|
+
map.setPaintProperty('route-outlines', 'line-opacity', routeLineOpacity);
|
|
618
|
+
map.setPaintProperty('route-line-shadows', 'line-opacity', routeLineOpacity);
|
|
619
|
+
|
|
620
|
+
if (zoom) {
|
|
621
|
+
const data = map.querySourceFeatures('routes');
|
|
622
|
+
if (data) {
|
|
623
|
+
const highlightedFeatures = data.filter((feature) =>
|
|
533
624
|
routeIds.includes(feature.properties.route_id),
|
|
534
625
|
);
|
|
535
|
-
|
|
536
|
-
if (highlightedFeatures.length > 0 && zoom) {
|
|
626
|
+
if (highlightedFeatures.length > 0) {
|
|
537
627
|
const zoomBounds = getBounds({
|
|
628
|
+
type: 'FeatureCollection',
|
|
538
629
|
features: highlightedFeatures,
|
|
539
630
|
});
|
|
540
631
|
map.fitBounds(zoomBounds, {
|
|
@@ -542,55 +633,56 @@ function createSystemMap(id, geojson) {
|
|
|
542
633
|
});
|
|
543
634
|
}
|
|
544
635
|
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
545
638
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
639
|
+
function unHighlightRoutes(map, zoom) {
|
|
640
|
+
map.setFilter('highlighted-routes', ['==', ['get', 'route_id'], 'none']);
|
|
641
|
+
map.setFilter('highlighted-route-outlines', [
|
|
642
|
+
'==',
|
|
643
|
+
['get', 'route_id'],
|
|
644
|
+
'none',
|
|
645
|
+
]);
|
|
646
|
+
map.setFilter('highlighted-route-line-shadows', [
|
|
647
|
+
'==',
|
|
648
|
+
['get', 'route_id'],
|
|
649
|
+
'none',
|
|
650
|
+
]);
|
|
651
|
+
|
|
652
|
+
map.setFilter('route-labels', ['has', 'route_short_name']);
|
|
653
|
+
|
|
654
|
+
const routeLineOpacity = 1;
|
|
655
|
+
|
|
656
|
+
map.setPaintProperty('routes', 'line-opacity', routeLineOpacity);
|
|
657
|
+
map.setPaintProperty('route-outlines', 'line-opacity', routeLineOpacity);
|
|
658
|
+
map.setPaintProperty('route-line-shadows', 'line-opacity', routeLineOpacity);
|
|
659
|
+
|
|
660
|
+
if (zoom) {
|
|
661
|
+
const data = map.querySourceFeatures('routes');
|
|
662
|
+
if (data) {
|
|
663
|
+
map.fitBounds(
|
|
664
|
+
getBounds({
|
|
665
|
+
type: 'FeatureCollection',
|
|
666
|
+
features: data,
|
|
667
|
+
}),
|
|
571
668
|
);
|
|
572
|
-
|
|
573
|
-
if (zoom) {
|
|
574
|
-
map.fitBounds(bounds);
|
|
575
|
-
}
|
|
576
669
|
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
577
672
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
jQuery('.overview-list').hover(
|
|
589
|
-
() => {},
|
|
590
|
-
() => unHighlightRoutes(true),
|
|
591
|
-
);
|
|
673
|
+
function setupTableHoverListeners(map) {
|
|
674
|
+
jQuery(() => {
|
|
675
|
+
jQuery('.overview-list a').hover((event) => {
|
|
676
|
+
const routeIdString = jQuery(event.target).data('route-ids');
|
|
677
|
+
if (routeIdString) {
|
|
678
|
+
const routeIds = routeIdString.toString().split(',');
|
|
679
|
+
highlightRoutes(map, routeIds, true);
|
|
680
|
+
}
|
|
592
681
|
});
|
|
593
|
-
});
|
|
594
682
|
|
|
595
|
-
|
|
683
|
+
jQuery('.overview-list').hover(
|
|
684
|
+
() => {},
|
|
685
|
+
() => unHighlightRoutes(map, true),
|
|
686
|
+
);
|
|
687
|
+
});
|
|
596
688
|
}
|