gtfs-to-html 2.11.4 → 2.12.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/dist/app/index.js +39 -30
- package/dist/app/index.js.map +1 -1
- package/dist/bin/gtfs-to-html.js +55 -9
- package/dist/bin/gtfs-to-html.js.map +1 -1
- package/dist/index.js +55 -9
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
- package/views/default/css/overview_styles.css +4 -2
- package/views/default/css/timetable_styles.css +24 -9
- package/views/default/formatting_functions.pug +1 -1
- package/views/default/js/system-map.js +143 -92
- package/views/default/js/timetable-alerts.js +68 -50
- package/views/default/js/timetable-map.js +264 -164
- package/views/default/js/timetable-menu.js +64 -40
- package/views/default/overview.pug +1 -1
- package/views/default/overview_full.pug +4 -6
- package/views/default/timetable_continuation_as.pug +1 -1
- package/views/default/timetable_continuation_from.pug +1 -1
- package/views/default/timetable_horizontal.pug +2 -2
- package/views/default/timetable_hourly.pug +1 -1
- package/views/default/timetable_map.pug +1 -1
- package/views/default/timetable_menu.pug +2 -2
- package/views/default/timetable_note_symbol.pug +1 -1
- package/views/default/timetable_stop_name.pug +1 -1
- package/views/default/timetable_stoptime.pug +7 -7
- package/views/default/timetable_vertical.pug +3 -3
- package/views/default/timetablepage.pug +8 -8
- package/views/default/timetablepage_full.pug +2 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* global
|
|
1
|
+
/* global maplibregl, Pbf, FeedMessage, mapStyleUrl, stopData, routeData, routeIds, tripIds, geojsons, gtfsRealtimeUrls */
|
|
2
2
|
/* eslint prefer-arrow-callback: "off", no-unused-vars: "off" */
|
|
3
3
|
|
|
4
4
|
const maps = {};
|
|
@@ -18,8 +18,8 @@ function formatRouteTextColor(route) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function degToCompass(num) {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const val = Math.floor(num / 22.5 + 0.5);
|
|
22
|
+
const arr = [
|
|
23
23
|
'N',
|
|
24
24
|
'NNE',
|
|
25
25
|
'NE',
|
|
@@ -56,47 +56,56 @@ function formatSeconds(seconds) {
|
|
|
56
56
|
|
|
57
57
|
function formatRoute(route) {
|
|
58
58
|
const html = route.route_url
|
|
59
|
-
?
|
|
60
|
-
:
|
|
59
|
+
? document.createElement('a')
|
|
60
|
+
: document.createElement('div');
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
if (route.route_url) {
|
|
63
|
+
html.href = route.route_url;
|
|
64
|
+
}
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
const routeItemDivs = [];
|
|
66
|
+
html.className = 'map-route-item';
|
|
66
67
|
|
|
68
|
+
// Only add color swatch if route has a color
|
|
67
69
|
if (route.route_color) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
);
|
|
70
|
+
const swatch = document.createElement('div');
|
|
71
|
+
swatch.className = 'route-color-swatch';
|
|
72
|
+
swatch.style.backgroundColor = formatRouteColor(route);
|
|
73
|
+
swatch.style.color = formatRouteTextColor(route);
|
|
74
|
+
swatch.textContent = route.route_short_name ?? '';
|
|
75
|
+
html.appendChild(swatch);
|
|
75
76
|
}
|
|
76
|
-
routeItemDivs.push(
|
|
77
|
-
jQuery('<div>')
|
|
78
|
-
.addClass('underline-hover')
|
|
79
|
-
.text(route.route_long_name ?? `Route ${route.route_short_name}`),
|
|
80
|
-
);
|
|
81
77
|
|
|
82
|
-
|
|
78
|
+
const textDiv = document.createElement('div');
|
|
79
|
+
textDiv.className = 'underline-hover';
|
|
80
|
+
textDiv.textContent =
|
|
81
|
+
route.route_long_name ?? `Route ${route.route_short_name}`;
|
|
82
|
+
html.appendChild(textDiv);
|
|
83
83
|
|
|
84
|
-
return html.
|
|
84
|
+
return html.outerHTML;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
function getStopPopupHtml(feature, stop) {
|
|
88
88
|
const routeIds = JSON.parse(feature.properties.route_ids);
|
|
89
|
-
const html =
|
|
89
|
+
const html = document.createElement('div');
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
const title = document.createElement('div');
|
|
92
|
+
title.className = 'popup-title';
|
|
93
|
+
title.textContent = stop.stop_name;
|
|
94
|
+
html.appendChild(title);
|
|
92
95
|
|
|
93
96
|
if (stop.stop_code ?? false) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
const stopCodeContainer = document.createElement('div');
|
|
98
|
+
|
|
99
|
+
const label = document.createElement('div');
|
|
100
|
+
label.className = 'popup-label';
|
|
101
|
+
label.textContent = 'Stop Code:';
|
|
102
|
+
stopCodeContainer.appendChild(label);
|
|
103
|
+
|
|
104
|
+
const code = document.createElement('strong');
|
|
105
|
+
code.textContent = stop.stop_code;
|
|
106
|
+
stopCodeContainer.appendChild(code);
|
|
107
|
+
|
|
108
|
+
html.appendChild(stopCodeContainer);
|
|
100
109
|
}
|
|
101
110
|
|
|
102
111
|
if (tripUpdates) {
|
|
@@ -134,16 +143,19 @@ function getStopPopupHtml(feature, stop) {
|
|
|
134
143
|
});
|
|
135
144
|
|
|
136
145
|
if (stopTimeUpdates['0'].length > 0 || stopTimeUpdates['1'].length > 0) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
146
|
+
const departuresLabel = document.createElement('div');
|
|
147
|
+
departuresLabel.className = 'popup-label';
|
|
148
|
+
departuresLabel.textContent = 'Upcoming Departures:';
|
|
149
|
+
html.appendChild(departuresLabel);
|
|
141
150
|
|
|
142
151
|
for (const direction of ['0', '1']) {
|
|
143
152
|
if (stopTimeUpdates[direction].length > 0) {
|
|
144
|
-
const
|
|
153
|
+
const timetableElement = document.querySelector(
|
|
145
154
|
`.timetable[data-direction-id="${direction}"]`,
|
|
146
|
-
)
|
|
155
|
+
);
|
|
156
|
+
const directionName = timetableElement
|
|
157
|
+
? timetableElement.dataset.directionName
|
|
158
|
+
: '';
|
|
147
159
|
const departureTimes = stopTimeUpdates[direction].map(
|
|
148
160
|
(stopTimeUpdate) =>
|
|
149
161
|
Math.round(
|
|
@@ -161,34 +173,35 @@ function getStopPopupHtml(feature, stop) {
|
|
|
161
173
|
type: 'conjunction',
|
|
162
174
|
}).format(departureTimes.slice(0, 4).map((time) => `<b>${time}</b>`));
|
|
163
175
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
176
|
+
const departureDiv = document.createElement('div');
|
|
177
|
+
departureDiv.innerHTML = `<b>${directionName}</b> in ${formattedDepartures} min`;
|
|
178
|
+
html.appendChild(departureDiv);
|
|
167
179
|
}
|
|
168
180
|
}
|
|
169
181
|
}
|
|
170
182
|
}
|
|
171
183
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
184
|
+
const routesLabel = document.createElement('div');
|
|
185
|
+
routesLabel.className = 'popup-label';
|
|
186
|
+
routesLabel.textContent = 'Routes Served:';
|
|
187
|
+
html.appendChild(routesLabel);
|
|
188
|
+
|
|
189
|
+
const routeList = document.createElement('div');
|
|
190
|
+
routeList.className = 'route-list';
|
|
191
|
+
routeList.innerHTML = routeIds
|
|
192
|
+
.map((routeId) => formatRoute(routeData[routeId]))
|
|
193
|
+
.join('');
|
|
194
|
+
html.appendChild(routeList);
|
|
195
|
+
|
|
196
|
+
const streetviewLink = document.createElement('a');
|
|
197
|
+
streetviewLink.className = 'btn-blue btn-sm';
|
|
198
|
+
streetviewLink.href = `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`;
|
|
199
|
+
streetviewLink.target = '_blank';
|
|
200
|
+
streetviewLink.rel = 'noopener noreferrer';
|
|
201
|
+
streetviewLink.textContent = 'View on Streetview';
|
|
202
|
+
html.appendChild(streetviewLink);
|
|
203
|
+
|
|
204
|
+
return html.outerHTML;
|
|
192
205
|
}
|
|
193
206
|
|
|
194
207
|
function getBounds(geojson) {
|
|
@@ -213,7 +226,7 @@ function getBounds(geojson) {
|
|
|
213
226
|
}
|
|
214
227
|
|
|
215
228
|
function secondsInFuture(dateString) {
|
|
216
|
-
// Takes a dateString in the format of "YYYYMMDD HH:mm:ss" and returns
|
|
229
|
+
// Takes a dateString in the format of "YYYYMMDD HH:mm:ss" and returns number of seconds in the future or 0 if the date is in the past
|
|
217
230
|
|
|
218
231
|
const inputDate = new Date(
|
|
219
232
|
dateString.substring(0, 4), // Year
|
|
@@ -257,28 +270,33 @@ function formatMovingText(vehiclePosition) {
|
|
|
257
270
|
}
|
|
258
271
|
|
|
259
272
|
function getVehiclePopupHtml(vehiclePosition, vehicleTripUpdate) {
|
|
260
|
-
const html =
|
|
261
|
-
|
|
262
|
-
});
|
|
273
|
+
const html = document.createElement('div');
|
|
274
|
+
html.dataset.vehicleId = vehiclePosition.vehicle.vehicle.id;
|
|
263
275
|
|
|
264
276
|
const lastUpdated = new Date(vehiclePosition.vehicle.timestamp * 1000);
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
.
|
|
277
|
+
const tripElement = document.querySelector(
|
|
278
|
+
`.timetable [data-trip-id="${vehiclePosition.vehicle.trip.trip_id}"]`,
|
|
279
|
+
);
|
|
280
|
+
const timetableElement = tripElement
|
|
281
|
+
? tripElement.closest('.timetable')
|
|
282
|
+
: null;
|
|
283
|
+
const directionName = timetableElement
|
|
284
|
+
? timetableElement.dataset.directionName
|
|
285
|
+
: null;
|
|
270
286
|
|
|
271
287
|
if (directionName) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
288
|
+
const title = document.createElement('div');
|
|
289
|
+
title.className = 'popup-title';
|
|
290
|
+
title.textContent = `Vehicle: ${directionName}`;
|
|
291
|
+
html.appendChild(title);
|
|
276
292
|
}
|
|
277
293
|
|
|
278
294
|
const movingText = formatMovingText(vehiclePosition);
|
|
279
295
|
|
|
280
296
|
if (movingText) {
|
|
281
|
-
|
|
297
|
+
const movingDiv = document.createElement('div');
|
|
298
|
+
movingDiv.textContent = movingText;
|
|
299
|
+
html.appendChild(movingDiv);
|
|
282
300
|
}
|
|
283
301
|
|
|
284
302
|
const numberOfArrivalsToShow = 5;
|
|
@@ -308,37 +326,44 @@ function getVehiclePopupHtml(vehiclePosition, vehicleTripUpdate) {
|
|
|
308
326
|
}
|
|
309
327
|
|
|
310
328
|
if (nextArrivals.length > 0) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
)
|
|
333
|
-
.
|
|
329
|
+
const upcomingStops = document.createElement('div');
|
|
330
|
+
upcomingStops.className = 'upcoming-stops';
|
|
331
|
+
|
|
332
|
+
const timeHeader = document.createElement('div');
|
|
333
|
+
timeHeader.textContent = 'Time';
|
|
334
|
+
upcomingStops.appendChild(timeHeader);
|
|
335
|
+
|
|
336
|
+
const stopHeader = document.createElement('div');
|
|
337
|
+
stopHeader.textContent = 'Upcoming Stop';
|
|
338
|
+
upcomingStops.appendChild(stopHeader);
|
|
339
|
+
|
|
340
|
+
for (const arrival of nextArrivals) {
|
|
341
|
+
let delay = '';
|
|
342
|
+
|
|
343
|
+
if (arrival.delay > 0) {
|
|
344
|
+
delay = `(${formatSeconds(arrival.delay)} behind schedule)`;
|
|
345
|
+
} else if (arrival.delay < 0) {
|
|
346
|
+
delay = `(${formatSeconds(arrival.delay)} ahead of schedule)`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const timeDiv = document.createElement('div');
|
|
350
|
+
timeDiv.textContent = formatSeconds(arrival.secondsToArrival);
|
|
351
|
+
upcomingStops.appendChild(timeDiv);
|
|
352
|
+
|
|
353
|
+
const stopDiv = document.createElement('div');
|
|
354
|
+
stopDiv.textContent = `${arrival.stopName} ${delay}`;
|
|
355
|
+
upcomingStops.appendChild(stopDiv);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
html.appendChild(upcomingStops);
|
|
334
359
|
}
|
|
335
360
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
361
|
+
const updatedDiv = document.createElement('div');
|
|
362
|
+
updatedDiv.className = 'vehicle-updated';
|
|
363
|
+
updatedDiv.textContent = `Updated: ${lastUpdated.toLocaleTimeString()}`;
|
|
364
|
+
html.appendChild(updatedDiv);
|
|
340
365
|
|
|
341
|
-
return html.
|
|
366
|
+
return html.outerHTML;
|
|
342
367
|
}
|
|
343
368
|
|
|
344
369
|
function getVehicleBearing(vehiclePosition, vehicleTripUpdate) {
|
|
@@ -351,10 +376,7 @@ function getVehicleBearing(vehiclePosition, vehicleTripUpdate) {
|
|
|
351
376
|
}
|
|
352
377
|
|
|
353
378
|
// Else try to calculate bearing from next stop
|
|
354
|
-
if (
|
|
355
|
-
vehicleTripUpdate &&
|
|
356
|
-
vehicleTripUpdate?.trip_update?.stop_time_update?.length > 0
|
|
357
|
-
) {
|
|
379
|
+
if (vehicleTripUpdate?.trip_update?.stop_time_update?.length > 0) {
|
|
358
380
|
const nextStopTimeUpdate =
|
|
359
381
|
vehicleTripUpdate.trip_update.stop_time_update[0];
|
|
360
382
|
const nextStop = stopData[nextStopTimeUpdate.stop_id];
|
|
@@ -436,7 +458,16 @@ function addVehicleMarker(vehiclePosition, vehicleTripUpdate) {
|
|
|
436
458
|
return;
|
|
437
459
|
}
|
|
438
460
|
|
|
439
|
-
const
|
|
461
|
+
const visibleTimetable = document.querySelector(
|
|
462
|
+
'.timetable:not([style*="display: none"])',
|
|
463
|
+
);
|
|
464
|
+
const visibleTimetableId = visibleTimetable
|
|
465
|
+
? visibleTimetable.dataset.timetableId
|
|
466
|
+
: null;
|
|
467
|
+
|
|
468
|
+
if (!visibleTimetableId) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
440
471
|
|
|
441
472
|
const vehicleDirectionArrow = getVehicleDirectionArrow(
|
|
442
473
|
vehiclePosition,
|
|
@@ -495,9 +526,11 @@ function animateVehicleMarker(vehicleMarker, vehiclePosition) {
|
|
|
495
526
|
|
|
496
527
|
// Check if vehiclePopup element exists and is for this vehicle
|
|
497
528
|
const popupElement = vehiclePopup.getElement();
|
|
498
|
-
const vehiclePopupContentId = `vehicle-popup-${vehiclePosition.vehicle.vehicle.id}`;
|
|
499
529
|
const markerPopupIsOpenForThisVehicle =
|
|
500
|
-
popupElement &&
|
|
530
|
+
popupElement &&
|
|
531
|
+
popupElement.querySelector(
|
|
532
|
+
`[data-vehicle-id="${vehiclePosition.vehicle.vehicle.id}"]`,
|
|
533
|
+
);
|
|
501
534
|
|
|
502
535
|
// Check if the open vehicle popup is for this vehicle
|
|
503
536
|
if (vehiclePopup.isOpen() && markerPopupIsOpenForThisVehicle) {
|
|
@@ -505,7 +538,7 @@ function animateVehicleMarker(vehicleMarker, vehiclePosition) {
|
|
|
505
538
|
vehiclePopup.setLngLat([newLongitude, newLatitude]);
|
|
506
539
|
}
|
|
507
540
|
|
|
508
|
-
if (safeProgress
|
|
541
|
+
if (safeProgress !== 1) {
|
|
509
542
|
requestAnimationFrame(animation);
|
|
510
543
|
}
|
|
511
544
|
};
|
|
@@ -569,11 +602,13 @@ async function updateArrivals() {
|
|
|
569
602
|
]);
|
|
570
603
|
|
|
571
604
|
if (!latestVehiclePositions?.length) {
|
|
572
|
-
|
|
605
|
+
const legendItems = document.querySelectorAll('.vehicle-legend-item');
|
|
606
|
+
legendItems.forEach((item) => (item.style.display = 'none'));
|
|
573
607
|
return;
|
|
574
608
|
}
|
|
575
609
|
|
|
576
|
-
|
|
610
|
+
const legendItems = document.querySelectorAll('.vehicle-legend-item');
|
|
611
|
+
legendItems.forEach((item) => (item.style.display = ''));
|
|
577
612
|
|
|
578
613
|
vehiclePositions = latestVehiclePositions.filter((vehiclePosition) => {
|
|
579
614
|
if (
|
|
@@ -604,7 +639,7 @@ async function updateArrivals() {
|
|
|
604
639
|
return tripIds.includes(vehiclePosition.vehicle.trip.trip_id);
|
|
605
640
|
});
|
|
606
641
|
|
|
607
|
-
tripUpdates = latestTripUpdates
|
|
642
|
+
tripUpdates = latestTripUpdates?.filter((tripUpdate) => {
|
|
608
643
|
if (
|
|
609
644
|
!tripUpdate ||
|
|
610
645
|
!tripUpdate.trip_update ||
|
|
@@ -645,13 +680,20 @@ async function updateArrivals() {
|
|
|
645
680
|
);
|
|
646
681
|
}
|
|
647
682
|
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
attachVehicleMarkerClickHandler(
|
|
651
|
-
vehiclePosition,
|
|
652
|
-
vehicleTripUpdate,
|
|
653
|
-
maps[visibleTimetableId],
|
|
683
|
+
const visibleTimetable = document.querySelector(
|
|
684
|
+
'.timetable:not([style*="display: none"])',
|
|
654
685
|
);
|
|
686
|
+
const visibleTimetableId = visibleTimetable
|
|
687
|
+
? visibleTimetable.dataset.timetableId
|
|
688
|
+
: null;
|
|
689
|
+
|
|
690
|
+
if (visibleTimetableId) {
|
|
691
|
+
attachVehicleMarkerClickHandler(
|
|
692
|
+
vehiclePosition,
|
|
693
|
+
vehicleTripUpdate,
|
|
694
|
+
maps[visibleTimetableId],
|
|
695
|
+
);
|
|
696
|
+
}
|
|
655
697
|
}
|
|
656
698
|
|
|
657
699
|
// Remove vehicles not in the feed
|
|
@@ -677,11 +719,11 @@ function toggleMap(id) {
|
|
|
677
719
|
|
|
678
720
|
// Update vehicle markers to use the current visible map
|
|
679
721
|
for (const [vehicleId, vehicleMarker] of Object.entries(vehicleMarkers)) {
|
|
680
|
-
const vehiclePosition = vehiclePositions
|
|
722
|
+
const vehiclePosition = vehiclePositions?.find(
|
|
681
723
|
(vehiclePosition) => vehiclePosition.vehicle.vehicle.id === vehicleId,
|
|
682
724
|
);
|
|
683
725
|
|
|
684
|
-
const vehicleTripUpdate = tripUpdates
|
|
726
|
+
const vehicleTripUpdate = tripUpdates?.find(
|
|
685
727
|
(tripUpdate) => tripUpdate.trip_update.vehicle.id === vehicleId,
|
|
686
728
|
);
|
|
687
729
|
|
|
@@ -707,13 +749,18 @@ function createMap(id) {
|
|
|
707
749
|
const geojson = geojsons[id];
|
|
708
750
|
|
|
709
751
|
if (!geojson || geojson.features.length === 0) {
|
|
710
|
-
|
|
752
|
+
const mapElement = document.querySelector(
|
|
753
|
+
`.map[data-timetable-id="${id}"]`,
|
|
754
|
+
);
|
|
755
|
+
if (mapElement) {
|
|
756
|
+
mapElement.style.display = 'none';
|
|
757
|
+
}
|
|
711
758
|
return false;
|
|
712
759
|
}
|
|
713
760
|
|
|
714
761
|
const bounds = getBounds(geojson);
|
|
715
762
|
const map = new maplibregl.Map({
|
|
716
|
-
container:
|
|
763
|
+
container: document.querySelector(`.map[data-timetable-id="${id}"]`),
|
|
717
764
|
style: mapStyleUrl,
|
|
718
765
|
center: bounds.getCenter(),
|
|
719
766
|
zoom: 12,
|
|
@@ -954,8 +1001,12 @@ function unHighlightStop(map, id) {
|
|
|
954
1001
|
}
|
|
955
1002
|
|
|
956
1003
|
function highlightTimetableStops(id, stopIds) {
|
|
957
|
-
const table =
|
|
958
|
-
|
|
1004
|
+
const table = document.querySelector(
|
|
1005
|
+
`.timetable[data-timetable-id="${id}"] table`,
|
|
1006
|
+
);
|
|
1007
|
+
if (!table) return;
|
|
1008
|
+
|
|
1009
|
+
const isVertical = table.dataset.orientation === 'vertical';
|
|
959
1010
|
|
|
960
1011
|
if (isVertical) {
|
|
961
1012
|
highlightVerticalTimetableStops(id, stopIds);
|
|
@@ -965,79 +1016,128 @@ function highlightTimetableStops(id, stopIds) {
|
|
|
965
1016
|
}
|
|
966
1017
|
|
|
967
1018
|
function highlightVerticalTimetableStops(id, stopIds) {
|
|
968
|
-
const table =
|
|
1019
|
+
const table = document.querySelector(
|
|
1020
|
+
`.timetable[data-timetable-id="${id}"] table`,
|
|
1021
|
+
);
|
|
1022
|
+
if (!table) return;
|
|
1023
|
+
|
|
969
1024
|
const columnIndexes = [];
|
|
970
|
-
const
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
)
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1025
|
+
const allCols = Array.from(table.querySelectorAll('colgroup col'));
|
|
1026
|
+
|
|
1027
|
+
for (const stopId of stopIds) {
|
|
1028
|
+
const col = table.querySelector(`colgroup col[data-stop-id="${stopId}"]`);
|
|
1029
|
+
if (col) {
|
|
1030
|
+
const index = allCols.indexOf(col);
|
|
1031
|
+
if (index !== -1) {
|
|
1032
|
+
columnIndexes.push(index);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Remove highlighted class from all cells
|
|
1038
|
+
table
|
|
1039
|
+
.querySelectorAll('td, thead th')
|
|
1040
|
+
.forEach((el) => el.classList.remove('highlighted'));
|
|
982
1041
|
|
|
983
|
-
|
|
984
|
-
table.
|
|
985
|
-
|
|
1042
|
+
// Add highlighted class to cells in matching columns
|
|
1043
|
+
table.querySelectorAll('.trip-row').forEach((row) => {
|
|
1044
|
+
const cells = row.querySelectorAll('td');
|
|
1045
|
+
cells.forEach((cell, index) => {
|
|
986
1046
|
if (columnIndexes.includes(index)) {
|
|
987
|
-
|
|
1047
|
+
cell.classList.add('highlighted');
|
|
988
1048
|
}
|
|
989
1049
|
});
|
|
990
1050
|
});
|
|
991
1051
|
|
|
992
|
-
table.
|
|
993
|
-
|
|
1052
|
+
table.querySelectorAll('thead').forEach((thead) => {
|
|
1053
|
+
const headers = thead.querySelectorAll('th');
|
|
1054
|
+
headers.forEach((header, index) => {
|
|
994
1055
|
if (columnIndexes.includes(index)) {
|
|
995
|
-
|
|
1056
|
+
header.classList.add('highlighted');
|
|
996
1057
|
}
|
|
997
1058
|
});
|
|
998
1059
|
});
|
|
999
1060
|
}
|
|
1000
1061
|
|
|
1001
1062
|
function highlightHorizontalTimetableStops(id, stopIds) {
|
|
1002
|
-
const table =
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1063
|
+
const table = document.querySelector(
|
|
1064
|
+
`.timetable[data-timetable-id="${id}"] table`,
|
|
1065
|
+
);
|
|
1066
|
+
if (!table) return;
|
|
1067
|
+
|
|
1068
|
+
// Remove highlighted class from all stop rows
|
|
1069
|
+
table
|
|
1070
|
+
.querySelectorAll('.stop-row')
|
|
1071
|
+
.forEach((row) => row.classList.remove('highlighted'));
|
|
1072
|
+
|
|
1073
|
+
// Add highlighted class to matching stop rows
|
|
1074
|
+
for (const stopId of stopIds) {
|
|
1075
|
+
const stopRow = table.querySelector(`[data-stop-id="${stopId}"]`);
|
|
1076
|
+
if (stopRow) {
|
|
1077
|
+
stopRow.classList.add('highlighted');
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1008
1080
|
}
|
|
1009
1081
|
|
|
1010
1082
|
function unHighlightTimetableStops(id) {
|
|
1011
|
-
const table =
|
|
1012
|
-
|
|
1083
|
+
const table = document.querySelector(
|
|
1084
|
+
`.timetable[data-timetable-id="${id}"] table`,
|
|
1085
|
+
);
|
|
1086
|
+
if (!table) return;
|
|
1087
|
+
|
|
1088
|
+
const isVertical = table.dataset.orientation === 'vertical';
|
|
1013
1089
|
|
|
1014
1090
|
if (isVertical) {
|
|
1015
|
-
table
|
|
1091
|
+
table
|
|
1092
|
+
.querySelectorAll('td, thead th')
|
|
1093
|
+
.forEach((el) => el.classList.remove('highlighted'));
|
|
1016
1094
|
} else {
|
|
1017
|
-
table
|
|
1095
|
+
table
|
|
1096
|
+
.querySelectorAll('.stop-row')
|
|
1097
|
+
.forEach((row) => row.classList.remove('highlighted'));
|
|
1018
1098
|
}
|
|
1019
1099
|
}
|
|
1020
1100
|
|
|
1021
1101
|
function setupTableHoverListeners(id, map) {
|
|
1022
|
-
|
|
1023
|
-
|
|
1102
|
+
const table = document.querySelector(
|
|
1103
|
+
`.timetable[data-timetable-id="${id}"] table`,
|
|
1104
|
+
);
|
|
1105
|
+
if (!table) return;
|
|
1106
|
+
|
|
1107
|
+
const cells = table.querySelectorAll('th, td');
|
|
1108
|
+
cells.forEach((cell) => {
|
|
1109
|
+
cell.addEventListener('mouseenter', (event) => {
|
|
1024
1110
|
const stopId = getStopIdFromTableCell(event.target);
|
|
1025
1111
|
if (stopId !== undefined) {
|
|
1026
1112
|
highlightStop(map, id, [stopId.toString()]);
|
|
1027
1113
|
}
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
cell.addEventListener('mouseleave', () => {
|
|
1117
|
+
unHighlightStop(map, id);
|
|
1118
|
+
});
|
|
1119
|
+
});
|
|
1031
1120
|
}
|
|
1032
1121
|
|
|
1033
1122
|
function getStopIdFromTableCell(cell) {
|
|
1034
|
-
const table =
|
|
1035
|
-
if (table
|
|
1036
|
-
|
|
1037
|
-
|
|
1123
|
+
const table = cell.closest('table');
|
|
1124
|
+
if (!table) return undefined;
|
|
1125
|
+
|
|
1126
|
+
if (table.dataset.orientation === 'vertical') {
|
|
1127
|
+
const row = cell.parentElement;
|
|
1128
|
+
const cellIndex = Array.from(row.children).indexOf(cell);
|
|
1129
|
+
const cols = table.querySelectorAll('colgroup col');
|
|
1130
|
+
if (cols[cellIndex]) {
|
|
1131
|
+
return cols[cellIndex].dataset.stopId;
|
|
1132
|
+
}
|
|
1038
1133
|
} else {
|
|
1039
|
-
|
|
1134
|
+
const row = cell.closest('tr');
|
|
1135
|
+
if (row) {
|
|
1136
|
+
return row.dataset.stopId;
|
|
1137
|
+
}
|
|
1040
1138
|
}
|
|
1139
|
+
|
|
1140
|
+
return undefined;
|
|
1041
1141
|
}
|
|
1042
1142
|
|
|
1043
1143
|
function createMaps() {
|