gtfs-to-html 2.9.12 → 2.9.14
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 +1 -0
- package/dist/app/index.js +3 -2
- package/dist/app/index.js.map +1 -1
- package/dist/bin/gtfs-to-html.js +15 -10
- package/dist/bin/gtfs-to-html.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -10
- package/dist/index.js.map +1 -1
- package/package.json +13 -6
- package/views/default/css/timetable_styles.css +44 -6
- package/views/default/formatting_functions.pug +8 -2
- package/views/default/js/timetable-map.js +169 -95
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gtfs-to-html",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
|
|
6
6
|
"keywords": [
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"archiver": "^7.0.1",
|
|
53
53
|
"cli-table": "^0.3.11",
|
|
54
54
|
"csv-stringify": "^6.5.1",
|
|
55
|
-
"express": "^4.21.
|
|
56
|
-
"gtfs": "^4.
|
|
55
|
+
"express": "^4.21.1",
|
|
56
|
+
"gtfs": "^4.15.1",
|
|
57
57
|
"gtfs-realtime-pbf-js-module": "^1.0.0",
|
|
58
58
|
"insane": "^2.6.2",
|
|
59
59
|
"js-beautify": "^1.15.1",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"pbf": "^4.0.1",
|
|
65
65
|
"pretty-error": "^4.0.0",
|
|
66
66
|
"pug": "^3.0.3",
|
|
67
|
-
"puppeteer": "^23.
|
|
67
|
+
"puppeteer": "^23.5.3",
|
|
68
68
|
"sanitize-filename": "^1.6.3",
|
|
69
69
|
"sqlstring": "^2.3.3",
|
|
70
70
|
"timer-machine": "^1.1.0",
|
|
@@ -74,17 +74,24 @@
|
|
|
74
74
|
"yoctocolors": "^2.1.1"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
+
"@types/archiver": "^6.0.2",
|
|
77
78
|
"@types/express": "^4.17.21",
|
|
79
|
+
"@types/insane": "^1.0.0",
|
|
80
|
+
"@types/js-beautify": "^1.14.3",
|
|
78
81
|
"@types/lodash-es": "^4.17.12",
|
|
79
82
|
"@types/morgan": "^1.9.9",
|
|
80
|
-
"@types/node": "^20.16.
|
|
83
|
+
"@types/node": "^20.16.11",
|
|
84
|
+
"@types/pug": "^2.0.10",
|
|
85
|
+
"@types/puppeteer": "^5.4.7",
|
|
86
|
+
"@types/sanitize-filename": "^1.1.28",
|
|
81
87
|
"@types/timer-machine": "^1.1.3",
|
|
88
|
+
"@types/untildify": "^3.0.0",
|
|
82
89
|
"@types/yargs": "^17.0.33",
|
|
83
90
|
"husky": "^9.1.6",
|
|
84
91
|
"lint-staged": "^15.2.10",
|
|
85
92
|
"prettier": "^3.3.3",
|
|
86
93
|
"tsup": "^8.3.0",
|
|
87
|
-
"typescript": "^5.6.
|
|
94
|
+
"typescript": "^5.6.3"
|
|
88
95
|
},
|
|
89
96
|
"engines": {
|
|
90
97
|
"node": ">= 20.11.0"
|
|
@@ -247,7 +247,7 @@ a:hover {
|
|
|
247
247
|
.timetable-page .timetable .table-vertical .stop-header {
|
|
248
248
|
text-align: center;
|
|
249
249
|
line-height: 1.15;
|
|
250
|
-
font-size:
|
|
250
|
+
font-size: 0.875rem;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
.timetable-page .timetable .run-header {
|
|
@@ -288,12 +288,12 @@ a:hover {
|
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
.timetable-page .timetable .city-row {
|
|
291
|
-
font-size: 1.
|
|
291
|
+
font-size: 1.5rem;
|
|
292
292
|
color: #415d86;
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
.timetable-page .timetable th.city-column {
|
|
296
|
-
font-size: 1.
|
|
296
|
+
font-size: 1.5rem;
|
|
297
297
|
text-align: center;
|
|
298
298
|
}
|
|
299
299
|
|
|
@@ -413,7 +413,7 @@ a:hover {
|
|
|
413
413
|
display: flex;
|
|
414
414
|
align-items: center;
|
|
415
415
|
justify-content: center;
|
|
416
|
-
font-size:
|
|
416
|
+
font-size: 0.75rem;
|
|
417
417
|
letter-spacing: -0.5px;
|
|
418
418
|
padding: 0 2px;
|
|
419
419
|
flex-shrink: 0;
|
|
@@ -428,7 +428,7 @@ a:hover {
|
|
|
428
428
|
display: flex;
|
|
429
429
|
align-items: center;
|
|
430
430
|
justify-content: center;
|
|
431
|
-
font-size:
|
|
431
|
+
font-size: 1.25rem;
|
|
432
432
|
font-weight: bold;
|
|
433
433
|
letter-spacing: -1px;
|
|
434
434
|
padding: 0 6px;
|
|
@@ -498,6 +498,27 @@ a:hover {
|
|
|
498
498
|
grid-template-columns: auto 1fr;
|
|
499
499
|
gap: 0.5rem;
|
|
500
500
|
line-height: 1;
|
|
501
|
+
padding-top: 5px;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.timetable-page
|
|
505
|
+
.map
|
|
506
|
+
.mapboxgl-popup-content
|
|
507
|
+
.upcoming-stops div:nth-child(1) {
|
|
508
|
+
font-weight: bold;
|
|
509
|
+
border-bottom: 1px solid #dddddd;
|
|
510
|
+
padding-bottom: 3px;
|
|
511
|
+
margin-bottom: -3px;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.timetable-page
|
|
515
|
+
.map
|
|
516
|
+
.mapboxgl-popup-content
|
|
517
|
+
.upcoming-stops div:nth-child(2) {
|
|
518
|
+
font-weight: bold;
|
|
519
|
+
border-bottom: 1px solid #dddddd;
|
|
520
|
+
padding-bottom: 3px;
|
|
521
|
+
margin-bottom: -3px;
|
|
501
522
|
}
|
|
502
523
|
|
|
503
524
|
.timetable-page
|
|
@@ -509,6 +530,16 @@ a:hover {
|
|
|
509
530
|
font-weight: bold;
|
|
510
531
|
}
|
|
511
532
|
|
|
533
|
+
.timetable-page .map .mapboxgl-popup-content .vehicle-updated {
|
|
534
|
+
padding-top: 5px;
|
|
535
|
+
font-size: 10px;
|
|
536
|
+
text-align: right;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.timetable-page .map .vehicle-popup .mapboxgl-popup-content {
|
|
540
|
+
padding-bottom: 5px;
|
|
541
|
+
}
|
|
542
|
+
|
|
512
543
|
.timetable-page .map-legend {
|
|
513
544
|
max-width: 30%;
|
|
514
545
|
background-color: #fff;
|
|
@@ -560,6 +591,7 @@ a:hover {
|
|
|
560
591
|
display: flex;
|
|
561
592
|
align-items: center;
|
|
562
593
|
justify-content: center;
|
|
594
|
+
cursor: pointer;
|
|
563
595
|
}
|
|
564
596
|
|
|
565
597
|
.timetable-page .vehicle-marker .vehicle-marker-arrow {
|
|
@@ -570,6 +602,12 @@ a:hover {
|
|
|
570
602
|
height: 14px;
|
|
571
603
|
}
|
|
572
604
|
|
|
605
|
+
.timetable-page .vehicle-marker .vehicle-marker-arrow.no-bearing {
|
|
606
|
+
background-image: url('data:image/svg+xml,<%3Fxml version="1.0" encoding="utf-8"%3F><svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M7.8 10a2.2 2.2 0 0 0 4.4 0 2.2 2.2 0 0 0-4.4 0z"/></svg>');
|
|
607
|
+
width: 20px;
|
|
608
|
+
height: 20px;
|
|
609
|
+
}
|
|
610
|
+
|
|
573
611
|
.timetable-page .timetable-alerts {
|
|
574
612
|
margin-bottom: 1.5rem;
|
|
575
613
|
}
|
|
@@ -614,7 +652,7 @@ a:hover {
|
|
|
614
652
|
display: flex;
|
|
615
653
|
align-items: center;
|
|
616
654
|
justify-content: center;
|
|
617
|
-
font-size:
|
|
655
|
+
font-size: 0.75rem;
|
|
618
656
|
letter-spacing: -0.5px;
|
|
619
657
|
padding: 0 2px;
|
|
620
658
|
flex-shrink: 0;
|
|
@@ -53,8 +53,14 @@
|
|
|
53
53
|
routeData[route.route_id] = route
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
stopData[feature.properties.stop_id] = {
|
|
57
|
+
stop_id: feature.properties.stop_id,
|
|
58
|
+
stop_code: feature.properties.stop_code,
|
|
59
|
+
stop_name: feature.properties.stop_name,
|
|
60
|
+
parent_station: feature.properties.parent_station,
|
|
61
|
+
stop_lat: feature.geometry.coordinates[1],
|
|
62
|
+
stop_lon: feature.geometry.coordinates[0],
|
|
63
|
+
}
|
|
58
64
|
|
|
59
65
|
feature.properties = {
|
|
60
66
|
route_ids: feature.properties.routes.map(route => route.route_id),
|
|
@@ -187,7 +187,9 @@ function formatMovingText(vehiclePosition) {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
function getVehiclePopupHtml(vehiclePosition, vehicleTripUpdate) {
|
|
190
|
-
const html = jQuery('<div>'
|
|
190
|
+
const html = jQuery('<div>', {
|
|
191
|
+
id: `vehicle-popup-${vehiclePosition.vehicle.vehicle.id}`,
|
|
192
|
+
});
|
|
191
193
|
|
|
192
194
|
const lastUpdated = new Date(vehiclePosition.vehicle.timestamp * 1000);
|
|
193
195
|
const directionName = jQuery(
|
|
@@ -209,12 +211,7 @@ function getVehiclePopupHtml(vehiclePosition, vehicleTripUpdate) {
|
|
|
209
211
|
jQuery('<div>').text(movingText).appendTo(html);
|
|
210
212
|
}
|
|
211
213
|
|
|
212
|
-
|
|
213
|
-
.append(
|
|
214
|
-
jQuery('<small>').text(`Updated: ${lastUpdated.toLocaleTimeString()}`),
|
|
215
|
-
)
|
|
216
|
-
.appendTo(html);
|
|
217
|
-
|
|
214
|
+
const numberOfArrivalsToShow = 5;
|
|
218
215
|
const nextArrivals = [];
|
|
219
216
|
if (vehicleTripUpdate && vehicleTripUpdate.trip_update.stop_time_update) {
|
|
220
217
|
for (const stoptimeUpdate of vehicleTripUpdate.trip_update
|
|
@@ -233,7 +230,7 @@ function getVehiclePopupHtml(vehiclePosition, vehicleTripUpdate) {
|
|
|
233
230
|
});
|
|
234
231
|
}
|
|
235
232
|
|
|
236
|
-
if (nextArrivals.length >=
|
|
233
|
+
if (nextArrivals.length >= numberOfArrivalsToShow) {
|
|
237
234
|
break;
|
|
238
235
|
}
|
|
239
236
|
}
|
|
@@ -241,12 +238,12 @@ function getVehiclePopupHtml(vehiclePosition, vehicleTripUpdate) {
|
|
|
241
238
|
}
|
|
242
239
|
|
|
243
240
|
if (nextArrivals.length > 0) {
|
|
244
|
-
jQuery('<div>')
|
|
245
|
-
.append(jQuery('<small>').text('Upcoming Stops:'))
|
|
246
|
-
.appendTo(html);
|
|
247
|
-
|
|
248
241
|
jQuery('<div>')
|
|
249
242
|
.addClass('upcoming-stops')
|
|
243
|
+
.append([
|
|
244
|
+
jQuery('<div>').text('Time'),
|
|
245
|
+
jQuery('<div>').text('Upcoming Stop'),
|
|
246
|
+
])
|
|
250
247
|
.append(
|
|
251
248
|
nextArrivals.flatMap((arrival) => {
|
|
252
249
|
let delay = '';
|
|
@@ -266,9 +263,104 @@ function getVehiclePopupHtml(vehiclePosition, vehicleTripUpdate) {
|
|
|
266
263
|
.appendTo(html);
|
|
267
264
|
}
|
|
268
265
|
|
|
266
|
+
jQuery('<div>')
|
|
267
|
+
.addClass('vehicle-updated')
|
|
268
|
+
.text(`Updated: ${lastUpdated.toLocaleTimeString()}`)
|
|
269
|
+
.appendTo(html);
|
|
270
|
+
|
|
269
271
|
return html.prop('outerHTML');
|
|
270
272
|
}
|
|
271
273
|
|
|
274
|
+
function getVehicleBearing(vehiclePosition, vehicleTripUpdate) {
|
|
275
|
+
// If vehicle position includes bearing, use that
|
|
276
|
+
if (
|
|
277
|
+
vehiclePosition.vehicle.position.bearing !== undefined &&
|
|
278
|
+
vehiclePosition.vehicle.position.bearing !== 0
|
|
279
|
+
) {
|
|
280
|
+
return vehiclePosition.vehicle.position.bearing;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Else try to calculate bearing from next stop
|
|
284
|
+
if (
|
|
285
|
+
vehicleTripUpdate &&
|
|
286
|
+
vehicleTripUpdate?.trip_update?.stop_time_update?.length > 0
|
|
287
|
+
) {
|
|
288
|
+
const nextStopTimeUpdate =
|
|
289
|
+
vehicleTripUpdate.trip_update.stop_time_update[0];
|
|
290
|
+
const nextStop = stopData[nextStopTimeUpdate.stop_id];
|
|
291
|
+
|
|
292
|
+
if (nextStop && nextStop.stop_lat && nextStop.stop_lon) {
|
|
293
|
+
const vehicleLocation = vehiclePosition.vehicle.position;
|
|
294
|
+
const lat1 = vehicleLocation.latitude;
|
|
295
|
+
const lon1 = vehicleLocation.longitude;
|
|
296
|
+
const lat2 = nextStop.stop_lat;
|
|
297
|
+
const lon2 = nextStop.stop_lon;
|
|
298
|
+
|
|
299
|
+
const y = Math.sin(lon2 - lon1) * Math.cos(lat2);
|
|
300
|
+
const x =
|
|
301
|
+
Math.cos(lat1) * Math.sin(lat2) -
|
|
302
|
+
Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
|
|
303
|
+
let bearing = (Math.atan2(y, x) * 180) / Math.PI;
|
|
304
|
+
bearing = (bearing + 360) % 360;
|
|
305
|
+
|
|
306
|
+
return bearing;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function getVehicleDirectionArrow(vehiclePosition, vehicleTripUpdate) {
|
|
314
|
+
const bearing = getVehicleBearing(vehiclePosition, vehicleTripUpdate);
|
|
315
|
+
|
|
316
|
+
if (bearing !== null) {
|
|
317
|
+
return `<div class="vehicle-marker-arrow" aria-hidden="true" style="transform:rotate(${bearing}deg)"></div>`;
|
|
318
|
+
} else {
|
|
319
|
+
return `<div class="vehicle-marker-arrow no-bearing" aria-hidden="true"></div>`;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function attachVehicleMarkerClickHandler(
|
|
324
|
+
vehiclePosition,
|
|
325
|
+
vehicleTripUpdate,
|
|
326
|
+
map,
|
|
327
|
+
) {
|
|
328
|
+
const coordinates = [
|
|
329
|
+
vehiclePosition.vehicle.position.longitude,
|
|
330
|
+
vehiclePosition.vehicle.position.latitude,
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
const vehicleMarker = vehicleMarkers[vehiclePosition.vehicle.vehicle.id];
|
|
334
|
+
|
|
335
|
+
vehicleMarker
|
|
336
|
+
.getElement()
|
|
337
|
+
.removeEventListener(
|
|
338
|
+
'click',
|
|
339
|
+
vehicleMarkersEventListeners[vehiclePosition.vehicle.vehicle.id],
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
vehicleMarkersEventListeners[vehiclePosition.vehicle.vehicle.id] = (
|
|
343
|
+
event,
|
|
344
|
+
) => {
|
|
345
|
+
event.stopPropagation();
|
|
346
|
+
if (vehiclePopup.isOpen()) {
|
|
347
|
+
vehiclePopup.remove();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
vehiclePopup
|
|
351
|
+
.setLngLat(coordinates)
|
|
352
|
+
.setHTML(getVehiclePopupHtml(vehiclePosition, vehicleTripUpdate))
|
|
353
|
+
.addTo(map);
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
vehicleMarker
|
|
357
|
+
.getElement()
|
|
358
|
+
.addEventListener(
|
|
359
|
+
'click',
|
|
360
|
+
vehicleMarkersEventListeners[vehiclePosition.vehicle.vehicle.id],
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
272
364
|
function addVehicleMarker(vehiclePosition, vehicleTripUpdate) {
|
|
273
365
|
if (!vehiclePosition.vehicle || !vehiclePosition.vehicle.position) {
|
|
274
366
|
return;
|
|
@@ -276,44 +368,40 @@ function addVehicleMarker(vehiclePosition, vehicleTripUpdate) {
|
|
|
276
368
|
|
|
277
369
|
const visibleTimetableId = jQuery('.timetable:visible').data('timetable-id');
|
|
278
370
|
|
|
371
|
+
const vehicleDirectionArrow = getVehicleDirectionArrow(
|
|
372
|
+
vehiclePosition,
|
|
373
|
+
vehicleTripUpdate,
|
|
374
|
+
);
|
|
375
|
+
|
|
279
376
|
// Create a DOM element for each marker
|
|
280
377
|
const el = document.createElement('div');
|
|
281
378
|
el.className = 'vehicle-marker';
|
|
282
379
|
el.style.width = '20px';
|
|
283
380
|
el.style.height = '20px';
|
|
284
|
-
|
|
381
|
+
|
|
382
|
+
if (vehicleDirectionArrow) {
|
|
383
|
+
el.innerHTML = vehicleDirectionArrow;
|
|
384
|
+
}
|
|
285
385
|
|
|
286
386
|
const coordinates = [
|
|
287
387
|
vehiclePosition.vehicle.position.longitude,
|
|
288
388
|
vehiclePosition.vehicle.position.latitude,
|
|
289
389
|
];
|
|
290
390
|
|
|
291
|
-
vehicleMarkersEventListeners[vehiclePosition.vehicle.vehicle.id] = () => {
|
|
292
|
-
vehiclePopup
|
|
293
|
-
.setLngLat(coordinates)
|
|
294
|
-
.setHTML(getVehiclePopupHtml(vehiclePosition, vehicleTripUpdate))
|
|
295
|
-
.addTo(maps[visibleTimetableId]);
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
// Vehicle marker popups
|
|
299
|
-
el.addEventListener(
|
|
300
|
-
'mouseenter',
|
|
301
|
-
vehicleMarkersEventListeners[vehiclePosition.vehicle.vehicle.id],
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
el.addEventListener('mouseleave', () => {
|
|
305
|
-
vehiclePopup.remove();
|
|
306
|
-
});
|
|
307
|
-
|
|
308
391
|
// Add marker to map
|
|
309
|
-
const
|
|
392
|
+
const vehicleMarker = new mapboxgl.Marker(el)
|
|
310
393
|
.setLngLat(coordinates)
|
|
311
394
|
.addTo(maps[visibleTimetableId]);
|
|
312
395
|
|
|
313
|
-
|
|
396
|
+
vehicleMarkers[vehiclePosition.vehicle.vehicle.id] = vehicleMarker;
|
|
314
397
|
}
|
|
315
398
|
|
|
316
|
-
function animateVehicleMarker(vehicleMarker,
|
|
399
|
+
function animateVehicleMarker(vehicleMarker, vehiclePosition) {
|
|
400
|
+
const newCoordinates = [
|
|
401
|
+
vehiclePosition.vehicle.position.longitude,
|
|
402
|
+
vehiclePosition.vehicle.position.latitude,
|
|
403
|
+
];
|
|
404
|
+
|
|
317
405
|
let startTime;
|
|
318
406
|
const duration = 5000;
|
|
319
407
|
const previousCoordinates = vehicleMarker.getLngLat().toArray();
|
|
@@ -331,7 +419,18 @@ function animateVehicleMarker(vehicleMarker, newCoordinates) {
|
|
|
331
419
|
previousCoordinates[1] + safeProgress * latitudeDifference;
|
|
332
420
|
|
|
333
421
|
vehicleMarker.setLngLat([newLongitude, newLatitude]);
|
|
334
|
-
|
|
422
|
+
|
|
423
|
+
// Check if vehiclePopup element exists and is for this vehicle
|
|
424
|
+
const popupElement = vehiclePopup.getElement();
|
|
425
|
+
const vehiclePopupContentId = `vehicle-popup-${vehiclePosition.vehicle.vehicle.id}`;
|
|
426
|
+
const markerPopupIsOpenForThisVehicle =
|
|
427
|
+
popupElement && popupElement.querySelector(`#${vehiclePopupContentId}`);
|
|
428
|
+
|
|
429
|
+
// Check if the open vehicle popup is for this vehicle
|
|
430
|
+
if (vehiclePopup.isOpen() && markerPopupIsOpenForThisVehicle) {
|
|
431
|
+
// Animate the popup along with the vehicle marker
|
|
432
|
+
vehiclePopup.setLngLat([newLongitude, newLatitude]);
|
|
433
|
+
}
|
|
335
434
|
|
|
336
435
|
if (safeProgress != 1) {
|
|
337
436
|
requestAnimationFrame(animation);
|
|
@@ -346,36 +445,18 @@ function updateVehicleMarkerLocation(
|
|
|
346
445
|
vehiclePosition,
|
|
347
446
|
vehicleTripUpdate,
|
|
348
447
|
) {
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
vehiclePosition.vehicle.position.latitude,
|
|
354
|
-
];
|
|
355
|
-
vehicleMarker.getElement().innerHTML = `<div class="vehicle-marker-arrow" aria-hidden="true" style="transform:rotate(${vehiclePosition.vehicle.position.bearing}deg)"></div>`;
|
|
356
|
-
|
|
357
|
-
vehicleMarker
|
|
358
|
-
.getElement()
|
|
359
|
-
.removeEventListener(
|
|
360
|
-
'mouseenter',
|
|
361
|
-
vehicleMarkersEventListeners[vehiclePosition.vehicle.vehicle.id],
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
vehicleMarkersEventListeners[vehiclePosition.vehicle.vehicle.id] = () => {
|
|
365
|
-
vehiclePopup
|
|
366
|
-
.setLngLat(coordinates)
|
|
367
|
-
.setHTML(getVehiclePopupHtml(vehiclePosition, vehicleTripUpdate))
|
|
368
|
-
.addTo(maps[visibleTimetableId]);
|
|
369
|
-
};
|
|
448
|
+
const vehicleDirectionArrow = getVehicleDirectionArrow(
|
|
449
|
+
vehiclePosition,
|
|
450
|
+
vehicleTripUpdate,
|
|
451
|
+
);
|
|
370
452
|
|
|
371
|
-
|
|
372
|
-
.getElement()
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
);
|
|
453
|
+
if (vehicleDirectionArrow) {
|
|
454
|
+
vehicleMarker.getElement().innerHTML = vehicleDirectionArrow;
|
|
455
|
+
} else {
|
|
456
|
+
vehicleMarker.getElement().innerHTML = '';
|
|
457
|
+
}
|
|
377
458
|
|
|
378
|
-
animateVehicleMarker(vehicleMarker,
|
|
459
|
+
animateVehicleMarker(vehicleMarker, vehiclePosition);
|
|
379
460
|
}
|
|
380
461
|
|
|
381
462
|
async function fetchGtfsRealtime(url, headers) {
|
|
@@ -470,12 +551,9 @@ async function updateArrivals() {
|
|
|
470
551
|
|
|
471
552
|
let vehicleMarker = vehicleMarkers[vehicleId];
|
|
472
553
|
|
|
473
|
-
// If not on map, add it
|
|
474
554
|
if (vehicleMarker === undefined) {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
vehicleTripUpdate,
|
|
478
|
-
);
|
|
555
|
+
// If not on map, add it
|
|
556
|
+
addVehicleMarker(vehiclePosition, vehicleTripUpdate);
|
|
479
557
|
} else {
|
|
480
558
|
// Otherwise update location
|
|
481
559
|
updateVehicleMarkerLocation(
|
|
@@ -484,6 +562,14 @@ async function updateArrivals() {
|
|
|
484
562
|
vehicleTripUpdate,
|
|
485
563
|
);
|
|
486
564
|
}
|
|
565
|
+
|
|
566
|
+
const visibleTimetableId =
|
|
567
|
+
jQuery('.timetable:visible').data('timetable-id');
|
|
568
|
+
attachVehicleMarkerClickHandler(
|
|
569
|
+
vehiclePosition,
|
|
570
|
+
vehicleTripUpdate,
|
|
571
|
+
maps[visibleTimetableId],
|
|
572
|
+
);
|
|
487
573
|
}
|
|
488
574
|
|
|
489
575
|
// Remove vehicles not in the feed
|
|
@@ -509,39 +595,19 @@ function toggleMap(id) {
|
|
|
509
595
|
|
|
510
596
|
// Update vehicle markers to use the current visible map
|
|
511
597
|
for (const [vehicleId, vehicleMarker] of Object.entries(vehicleMarkers)) {
|
|
512
|
-
const coordinates = vehicleMarker.getLngLat();
|
|
513
|
-
|
|
514
|
-
// Remove previous event listeners
|
|
515
|
-
vehicleMarker
|
|
516
|
-
.getElement()
|
|
517
|
-
.removeEventListener(
|
|
518
|
-
'mouseenter',
|
|
519
|
-
vehicleMarkersEventListeners[vehicleId],
|
|
520
|
-
);
|
|
521
|
-
|
|
522
598
|
const vehiclePosition = vehiclePositions.find(
|
|
523
599
|
(vehiclePosition) => vehiclePosition.vehicle.vehicle.id === vehicleId,
|
|
524
600
|
);
|
|
525
601
|
|
|
526
|
-
const
|
|
602
|
+
const vehicleTripUpdate = tripUpdates.find(
|
|
527
603
|
(tripUpdate) => tripUpdate.trip_update.vehicle.id === vehicleId,
|
|
528
604
|
);
|
|
529
605
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
.addTo(maps[id]);
|
|
536
|
-
};
|
|
537
|
-
|
|
538
|
-
// Add updated event listener to marker
|
|
539
|
-
vehicleMarker
|
|
540
|
-
.getElement()
|
|
541
|
-
.addEventListener(
|
|
542
|
-
'mouseenter',
|
|
543
|
-
vehicleMarkersEventListeners[vehicleId],
|
|
544
|
-
);
|
|
606
|
+
attachVehicleMarkerClickHandler(
|
|
607
|
+
vehiclePosition,
|
|
608
|
+
vehicleTripUpdate,
|
|
609
|
+
maps[id],
|
|
610
|
+
);
|
|
545
611
|
|
|
546
612
|
// Move marker to the current visible map
|
|
547
613
|
vehicleMarker.addTo(maps[id]);
|
|
@@ -891,10 +957,18 @@ function createMaps() {
|
|
|
891
957
|
) {
|
|
892
958
|
// Popup for realtime vehicle locations
|
|
893
959
|
vehiclePopup = new mapboxgl.Popup({
|
|
894
|
-
closeButton: false,
|
|
895
960
|
closeOnClick: false,
|
|
896
961
|
className: 'vehicle-popup',
|
|
897
|
-
offset:
|
|
962
|
+
offset: {
|
|
963
|
+
top: [0, 10],
|
|
964
|
+
bottom: [0, -10],
|
|
965
|
+
left: [10, 0],
|
|
966
|
+
right: [-10, 0],
|
|
967
|
+
'top-left': [10, 10],
|
|
968
|
+
'top-right': [-10, 10],
|
|
969
|
+
'bottom-left': [10, -10],
|
|
970
|
+
'bottom-right': [-10, -10],
|
|
971
|
+
},
|
|
898
972
|
});
|
|
899
973
|
|
|
900
974
|
const arrivalUpdateInterval = 10 * 1000; // 10 seconds
|