gtfs-to-html 2.11.5 → 2.12.1
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 +46 -37
- package/dist/app/index.js.map +1 -1
- package/dist/bin/gtfs-to-html.js +62 -16
- package/dist/bin/gtfs-to-html.js.map +1 -1
- package/dist/index.js +62 -16
- package/dist/index.js.map +1 -1
- package/package.json +11 -8
- package/views/default/css/overview_styles.css +5 -5
- package/views/default/css/timetable_styles.css +26 -14
- 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 +4 -4
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gtfs-to-html",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
|
|
6
6
|
"keywords": [
|
|
@@ -47,23 +47,26 @@
|
|
|
47
47
|
"prepare": "husky"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@
|
|
51
|
-
"@turf/
|
|
50
|
+
"@maplibre/maplibre-gl-geocoder": "^1.9.1",
|
|
51
|
+
"@turf/helpers": "^7.3.0",
|
|
52
|
+
"@turf/simplify": "^7.3.0",
|
|
52
53
|
"anchorme": "^3.0.8",
|
|
53
54
|
"archiver": "^7.0.1",
|
|
54
55
|
"cli-table": "^0.3.11",
|
|
56
|
+
"css.escape": "^1.5.1",
|
|
55
57
|
"csv-stringify": "^6.6.0",
|
|
56
58
|
"express": "^5.1.0",
|
|
57
59
|
"gtfs": "^4.18.1",
|
|
58
60
|
"gtfs-realtime-pbf-js-module": "^1.0.0",
|
|
59
61
|
"js-beautify": "^1.15.4",
|
|
60
62
|
"lodash-es": "^4.17.21",
|
|
61
|
-
"
|
|
63
|
+
"maplibre-gl": "^5.13.0",
|
|
64
|
+
"marked": "^17.0.0",
|
|
62
65
|
"moment": "^2.30.1",
|
|
63
66
|
"pbf": "^4.0.1",
|
|
64
67
|
"pretty-error": "^4.0.0",
|
|
65
68
|
"pug": "^3.0.3",
|
|
66
|
-
"puppeteer": "^24.
|
|
69
|
+
"puppeteer": "^24.30.0",
|
|
67
70
|
"sanitize-filename": "^1.6.3",
|
|
68
71
|
"sanitize-html": "^2.17.0",
|
|
69
72
|
"sqlstring": "^2.3.3",
|
|
@@ -84,11 +87,11 @@
|
|
|
84
87
|
"@types/sanitize-html": "^2.16.0",
|
|
85
88
|
"@types/sqlstring": "^2.3.2",
|
|
86
89
|
"@types/toposort": "^2.0.7",
|
|
87
|
-
"@types/yargs": "^17.0.
|
|
90
|
+
"@types/yargs": "^17.0.35",
|
|
88
91
|
"husky": "^9.1.7",
|
|
89
|
-
"lint-staged": "^16.2.
|
|
92
|
+
"lint-staged": "^16.2.7",
|
|
90
93
|
"prettier": "^3.6.2",
|
|
91
|
-
"tsup": "^8.5.
|
|
94
|
+
"tsup": "^8.5.1",
|
|
92
95
|
"typescript": "^5.9.3"
|
|
93
96
|
},
|
|
94
97
|
"engines": {
|
|
@@ -98,7 +98,7 @@ a:hover {
|
|
|
98
98
|
align-items: center;
|
|
99
99
|
justify-content: center;
|
|
100
100
|
font-size: 12px;
|
|
101
|
-
letter-spacing: -0.
|
|
101
|
+
letter-spacing: -0.1px;
|
|
102
102
|
padding: 0 2px;
|
|
103
103
|
flex-shrink: 0;
|
|
104
104
|
font-weight: bold;
|
|
@@ -114,15 +114,15 @@ a:hover {
|
|
|
114
114
|
justify-content: center;
|
|
115
115
|
font-size: 20px;
|
|
116
116
|
font-weight: bold;
|
|
117
|
-
letter-spacing: -
|
|
117
|
+
letter-spacing: -0.2px;
|
|
118
118
|
padding: 0 6px;
|
|
119
119
|
flex-shrink: 0;
|
|
120
120
|
font-weight: bold;
|
|
121
121
|
color: white;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
.timetable-overview .btn-
|
|
125
|
-
color: rgb(255 255 255);
|
|
124
|
+
.timetable-overview .btn-active {
|
|
125
|
+
color: rgb(255, 255, 255);
|
|
126
126
|
padding: 0.75rem 1.5rem;
|
|
127
127
|
background-color: rgb(37 99 235);
|
|
128
128
|
border-radius: 0.375rem;
|
|
@@ -133,7 +133,7 @@ a:hover {
|
|
|
133
133
|
text-decoration: none;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
.timetable-overview .btn-
|
|
136
|
+
.timetable-overview .btn-active:hover {
|
|
137
137
|
background-color: rgb(29 78 216);
|
|
138
138
|
text-decoration: none;
|
|
139
139
|
}
|
|
@@ -201,8 +201,8 @@ a:hover {
|
|
|
201
201
|
display: none;
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
.timetable-page .btn-
|
|
205
|
-
color: rgb(255 255 255);
|
|
204
|
+
.timetable-page .btn-active {
|
|
205
|
+
color: rgb(255, 255, 255);
|
|
206
206
|
padding: 0.75rem 1.5rem;
|
|
207
207
|
background-color: rgb(37 99 235);
|
|
208
208
|
border-radius: 0.375rem;
|
|
@@ -213,12 +213,12 @@ a:hover {
|
|
|
213
213
|
text-decoration: none;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
.timetable-page .btn-
|
|
216
|
+
.timetable-page .btn-active:hover {
|
|
217
217
|
background-color: rgb(29 78 216);
|
|
218
218
|
text-decoration: none;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
.timetable-page .btn-
|
|
221
|
+
.timetable-page .btn-inactive {
|
|
222
222
|
color: rgb(75 85 99);
|
|
223
223
|
padding: 0.75rem 1.5rem;
|
|
224
224
|
background-color: rgb(209 213 219);
|
|
@@ -230,7 +230,7 @@ a:hover {
|
|
|
230
230
|
text-decoration: none;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
.timetable-page .btn-
|
|
233
|
+
.timetable-page .btn-inactive:hover {
|
|
234
234
|
background-color: rgb(201, 206, 213);
|
|
235
235
|
text-decoration: none;
|
|
236
236
|
}
|
|
@@ -422,7 +422,7 @@ a:hover {
|
|
|
422
422
|
align-items: center;
|
|
423
423
|
justify-content: center;
|
|
424
424
|
font-size: 0.75rem;
|
|
425
|
-
letter-spacing: -0.
|
|
425
|
+
letter-spacing: -0.1px;
|
|
426
426
|
padding: 0 2px;
|
|
427
427
|
flex-shrink: 0;
|
|
428
428
|
font-weight: bold;
|
|
@@ -438,7 +438,7 @@ a:hover {
|
|
|
438
438
|
justify-content: center;
|
|
439
439
|
font-size: 1.25rem;
|
|
440
440
|
font-weight: bold;
|
|
441
|
-
letter-spacing: -
|
|
441
|
+
letter-spacing: -0.2px;
|
|
442
442
|
padding: 0 6px;
|
|
443
443
|
flex-shrink: 0;
|
|
444
444
|
font-weight: bold;
|
|
@@ -551,33 +551,35 @@ a:hover {
|
|
|
551
551
|
}
|
|
552
552
|
|
|
553
553
|
.timetable-page .map-legend {
|
|
554
|
-
padding: 0
|
|
554
|
+
padding: 8px 8px 0 8px;
|
|
555
|
+
display: flex;
|
|
556
|
+
flex-direction: column;
|
|
557
|
+
gap: 6px;
|
|
555
558
|
}
|
|
556
559
|
|
|
557
560
|
@media screen and (min-width: 768px) {
|
|
558
561
|
.timetable-page .map-legend {
|
|
559
|
-
padding:
|
|
562
|
+
padding: 8px 6px;
|
|
560
563
|
background-color: #fff;
|
|
561
564
|
border-radius: 3px;
|
|
562
565
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
563
566
|
position: absolute;
|
|
564
567
|
left: 10px;
|
|
565
|
-
bottom:
|
|
568
|
+
bottom: 10px;
|
|
566
569
|
z-index: 1;
|
|
570
|
+
gap: 8px;
|
|
567
571
|
}
|
|
568
572
|
}
|
|
569
573
|
|
|
570
574
|
.timetable-page .map-legend .legend-item {
|
|
571
|
-
padding: 4px 0;
|
|
572
575
|
display: flex;
|
|
573
576
|
flex-direction: row;
|
|
574
|
-
align-items:
|
|
577
|
+
align-items: center;
|
|
575
578
|
gap: 4px;
|
|
576
579
|
}
|
|
577
580
|
|
|
578
581
|
.timetable-page .map-legend .legend-item .legend-icon {
|
|
579
582
|
width: 22px;
|
|
580
|
-
padding-top: 3px;
|
|
581
583
|
display: flex;
|
|
582
584
|
flex-direction: row;
|
|
583
585
|
align-items: center;
|
|
@@ -587,6 +589,7 @@ a:hover {
|
|
|
587
589
|
|
|
588
590
|
.timetable-page .map-legend .legend-item .legend-text {
|
|
589
591
|
font-size: 12px;
|
|
592
|
+
line-height: 1;
|
|
590
593
|
}
|
|
591
594
|
|
|
592
595
|
.timetable-page .stop-marker {
|
|
@@ -668,7 +671,7 @@ a:hover {
|
|
|
668
671
|
align-items: center;
|
|
669
672
|
justify-content: center;
|
|
670
673
|
font-size: 0.75rem;
|
|
671
|
-
letter-spacing: -0.
|
|
674
|
+
letter-spacing: -0.1px;
|
|
672
675
|
padding: 0 2px;
|
|
673
676
|
flex-shrink: 0;
|
|
674
677
|
font-weight: bold;
|
|
@@ -684,6 +687,15 @@ a:hover {
|
|
|
684
687
|
margin-top: 0.5rem;
|
|
685
688
|
}
|
|
686
689
|
|
|
690
|
+
.timetable-page .timetable-alerts .alert-body .alert-label {
|
|
691
|
+
margin-top: 0.5rem;
|
|
692
|
+
font-weight: bold;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.timetable-page .timetable-alerts .alert-body ul {
|
|
696
|
+
margin: 0.5rem 0;
|
|
697
|
+
}
|
|
698
|
+
|
|
687
699
|
.timetable-page .timetable-alerts .alert-more-info {
|
|
688
700
|
margin-top: 0.75rem;
|
|
689
701
|
}
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
minifiedGeojson.features.push(feature)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
geojsons[
|
|
97
|
+
geojsons[timetable.timetable_id] = optimizeGeojsonProperties(minifiedGeojson)
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
const gtfsRealtimeUrls = {}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* global
|
|
1
|
+
/* global maplibregl, geojson, mapStyleUrl, MaplibreGeocoder */
|
|
2
2
|
/* eslint prefer-arrow-callback: "off", no-unused-vars: "off" */
|
|
3
3
|
|
|
4
4
|
function formatRouteColor(route) {
|
|
@@ -10,86 +10,103 @@ function formatRouteTextColor(route) {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function formatRoute(route) {
|
|
13
|
-
const
|
|
14
|
-
?
|
|
15
|
-
:
|
|
13
|
+
const element = route.route_url
|
|
14
|
+
? document.createElement('a')
|
|
15
|
+
: document.createElement('div');
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
element.className = 'map-route-item';
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
if (route.route_url) {
|
|
20
|
+
element.href = route.route_url;
|
|
21
|
+
}
|
|
20
22
|
|
|
21
23
|
if (route.route_color) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
);
|
|
24
|
+
const colorSwatch = document.createElement('div');
|
|
25
|
+
colorSwatch.className = 'route-color-swatch';
|
|
26
|
+
colorSwatch.style.backgroundColor = formatRouteColor(route);
|
|
27
|
+
colorSwatch.style.color = formatRouteTextColor(route);
|
|
28
|
+
colorSwatch.textContent = route.route_short_name ?? '';
|
|
29
|
+
element.appendChild(colorSwatch);
|
|
29
30
|
}
|
|
30
|
-
routeItemDivs.push(
|
|
31
|
-
jQuery('<div>')
|
|
32
|
-
.addClass('underline-hover')
|
|
33
|
-
.text(route.route_long_name ?? `Route ${route.route_short_name}`),
|
|
34
|
-
);
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
const routeName = document.createElement('div');
|
|
33
|
+
routeName.className = 'underline-hover';
|
|
34
|
+
routeName.textContent =
|
|
35
|
+
route.route_long_name ?? `Route ${route.route_short_name}`;
|
|
36
|
+
element.appendChild(routeName);
|
|
37
37
|
|
|
38
|
-
return
|
|
38
|
+
return element.outerHTML;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function formatRoutePopup(features) {
|
|
42
|
-
const
|
|
42
|
+
const container = document.createElement('div');
|
|
43
43
|
|
|
44
44
|
if (features.length > 1) {
|
|
45
|
-
|
|
45
|
+
const title = document.createElement('div');
|
|
46
|
+
title.className = 'popup-title';
|
|
47
|
+
title.textContent = 'Routes';
|
|
48
|
+
container.appendChild(title);
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
features.forEach((feature) => {
|
|
52
|
+
const routeHTML = formatRoute(feature.properties);
|
|
53
|
+
container.insertAdjacentHTML('beforeend', routeHTML);
|
|
54
|
+
});
|
|
51
55
|
|
|
52
|
-
return
|
|
56
|
+
return container.outerHTML;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
function formatStopPopup(feature) {
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
let routes = [];
|
|
61
|
+
try {
|
|
62
|
+
routes = JSON.parse(feature.properties.routes);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('Failed to parse routes JSON:', error);
|
|
65
|
+
}
|
|
66
|
+
const container = document.createElement('div');
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
const title = document.createElement('div');
|
|
69
|
+
title.className = 'popup-title';
|
|
70
|
+
title.textContent = feature.properties.stop_name;
|
|
71
|
+
container.appendChild(title);
|
|
63
72
|
|
|
64
73
|
if (feature.properties.stop_code ?? false) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
const stopCodeContainer = document.createElement('div');
|
|
75
|
+
|
|
76
|
+
const label = document.createElement('div');
|
|
77
|
+
label.className = 'popup-label';
|
|
78
|
+
label.textContent = 'Stop Code:';
|
|
79
|
+
stopCodeContainer.appendChild(label);
|
|
80
|
+
|
|
81
|
+
const code = document.createElement('strong');
|
|
82
|
+
code.textContent = feature.properties.stop_code;
|
|
83
|
+
stopCodeContainer.appendChild(code);
|
|
84
|
+
|
|
85
|
+
container.appendChild(stopCodeContainer);
|
|
71
86
|
}
|
|
72
87
|
|
|
73
|
-
|
|
88
|
+
const routesLabel = document.createElement('div');
|
|
89
|
+
routesLabel.className = 'popup-label';
|
|
90
|
+
routesLabel.textContent = 'Routes Served:';
|
|
91
|
+
container.appendChild(routesLabel);
|
|
74
92
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
const routeList = document.createElement('div');
|
|
94
|
+
routeList.className = 'route-list';
|
|
95
|
+
routes.forEach((route) => {
|
|
96
|
+
const routeHTML = formatRoute(route);
|
|
97
|
+
routeList.insertAdjacentHTML('beforeend', routeHTML);
|
|
98
|
+
});
|
|
99
|
+
container.appendChild(routeList);
|
|
80
100
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
.appendTo(html);
|
|
91
|
-
|
|
92
|
-
return html.prop('outerHTML');
|
|
101
|
+
const streetviewLink = document.createElement('a');
|
|
102
|
+
streetviewLink.className = 'btn-active btn-sm';
|
|
103
|
+
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`;
|
|
104
|
+
streetviewLink.target = '_blank';
|
|
105
|
+
streetviewLink.rel = 'noopener noreferrer';
|
|
106
|
+
streetviewLink.textContent = 'View on Streetview';
|
|
107
|
+
container.appendChild(streetviewLink);
|
|
108
|
+
|
|
109
|
+
return container.outerHTML;
|
|
93
110
|
}
|
|
94
111
|
|
|
95
112
|
function getBounds(geojson) {
|
|
@@ -121,7 +138,10 @@ function createSystemMap() {
|
|
|
121
138
|
};
|
|
122
139
|
|
|
123
140
|
if (!geojson || geojson.features.length === 0) {
|
|
124
|
-
|
|
141
|
+
const systemMapElement = document.getElementById('system_map');
|
|
142
|
+
if (systemMapElement) {
|
|
143
|
+
systemMapElement.style.display = 'none';
|
|
144
|
+
}
|
|
125
145
|
return false;
|
|
126
146
|
}
|
|
127
147
|
|
|
@@ -132,11 +152,6 @@ function createSystemMap() {
|
|
|
132
152
|
center: bounds.getCenter(),
|
|
133
153
|
zoom: 12,
|
|
134
154
|
});
|
|
135
|
-
const routes = {};
|
|
136
|
-
|
|
137
|
-
for (const feature of geojson.features) {
|
|
138
|
-
routes[feature.properties.route_id] = feature.properties;
|
|
139
|
-
}
|
|
140
155
|
|
|
141
156
|
map.scrollZoom.disable();
|
|
142
157
|
map.addControl(new maplibregl.NavigationControl());
|
|
@@ -148,7 +163,7 @@ function createSystemMap() {
|
|
|
148
163
|
fitMapToBounds(map, bounds);
|
|
149
164
|
disablePointsOfInterest(map);
|
|
150
165
|
addMapLayers(map, geojson, defaultRouteColor, lineLayout);
|
|
151
|
-
setupEventListeners(map
|
|
166
|
+
setupEventListeners(map);
|
|
152
167
|
});
|
|
153
168
|
}
|
|
154
169
|
|
|
@@ -159,12 +174,15 @@ function addGeocoder(map, bounds) {
|
|
|
159
174
|
forwardGeocode: async (config) => {
|
|
160
175
|
const features = [];
|
|
161
176
|
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`;
|
|
177
|
+
const request = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(
|
|
178
|
+
config.query,
|
|
179
|
+
)}&format=geojson&polygon_geojson=1&addressdetails=1&viewbox=${bounds.getWest()},${bounds.getSouth()},${bounds.getEast()},${bounds.getNorth()}&bounded=1`;
|
|
165
180
|
const response = await fetch(request);
|
|
166
|
-
const
|
|
167
|
-
for (const feature of
|
|
181
|
+
const geocodeResult = await response.json();
|
|
182
|
+
for (const feature of geocodeResult.features) {
|
|
183
|
+
if (!feature.bbox || feature.bbox.length < 4) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
168
186
|
const center = [
|
|
169
187
|
feature.bbox[0] + (feature.bbox[2] - feature.bbox[0]) / 2,
|
|
170
188
|
feature.bbox[1] + (feature.bbox[3] - feature.bbox[1]) / 2,
|
|
@@ -195,6 +213,7 @@ function addGeocoder(map, bounds) {
|
|
|
195
213
|
},
|
|
196
214
|
{
|
|
197
215
|
maplibregl,
|
|
216
|
+
debounceSearch: 800,
|
|
198
217
|
proximity: {
|
|
199
218
|
latitude: bounds.getCenter()[0],
|
|
200
219
|
longitude: bounds.getCenter()[1],
|
|
@@ -247,11 +266,6 @@ function addMapLayers(map, geojson, defaultRouteColor, lineLayout) {
|
|
|
247
266
|
addRouteLabels(map, geojson);
|
|
248
267
|
}
|
|
249
268
|
|
|
250
|
-
function getFirstSymbolLayerId(map) {
|
|
251
|
-
const layers = map.getStyle().layers;
|
|
252
|
-
return layers.find((layer) => layer.type === 'symbol').id;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
269
|
function addRouteLineShadow(map, geojson, lineLayout, firstSymbolId) {
|
|
256
270
|
map.addLayer(
|
|
257
271
|
{
|
|
@@ -513,22 +527,23 @@ function addRouteLabels(map, geojson) {
|
|
|
513
527
|
});
|
|
514
528
|
}
|
|
515
529
|
|
|
516
|
-
function setupEventListeners(map
|
|
517
|
-
map.on('mousemove', (event) => handleMouseMove(event, map
|
|
530
|
+
function setupEventListeners(map) {
|
|
531
|
+
map.on('mousemove', (event) => handleMouseMove(event, map));
|
|
518
532
|
map.on('click', (event) => handleClick(event, map));
|
|
519
533
|
setupTableHoverListeners(map);
|
|
520
534
|
}
|
|
521
535
|
|
|
522
|
-
function handleMouseMove(event, map
|
|
536
|
+
function handleMouseMove(event, map) {
|
|
523
537
|
const features = map.queryRenderedFeatures(event.point, {
|
|
524
538
|
layers: ['routes', 'route-outlines', 'stops-highlighted', 'stops'],
|
|
525
539
|
});
|
|
526
540
|
if (features.length > 0) {
|
|
527
541
|
map.getCanvas().style.cursor = 'pointer';
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
542
|
+
// Get unique route IDs
|
|
543
|
+
const routeIds = features
|
|
544
|
+
.map((feature) => feature.properties.route_id)
|
|
545
|
+
.filter((value, index, self) => value && self.indexOf(value) === index);
|
|
546
|
+
highlightRoutes(map, routeIds);
|
|
532
547
|
|
|
533
548
|
if (features.some((feature) => feature.layer.id === 'stops')) {
|
|
534
549
|
highlightStop(
|
|
@@ -574,10 +589,32 @@ function showStopPopup(map, feature) {
|
|
|
574
589
|
}
|
|
575
590
|
|
|
576
591
|
function showRoutePopup(map, features, lngLat) {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
)
|
|
592
|
+
// Get list of unique routes, using route_short_name as the key
|
|
593
|
+
const seen = {};
|
|
594
|
+
const uniqueRoutes = [];
|
|
595
|
+
for (const feature of features) {
|
|
596
|
+
const routeShortName = feature.properties.route_short_name;
|
|
597
|
+
if (!seen[routeShortName]) {
|
|
598
|
+
seen[routeShortName] = true;
|
|
599
|
+
uniqueRoutes.push(feature);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Sort by route_short_name as number, then alphabetically
|
|
604
|
+
const routes = uniqueRoutes.sort((a, b) => {
|
|
605
|
+
const aNum = Number.parseInt(a.properties.route_short_name, 10);
|
|
606
|
+
|
|
607
|
+
if (Number.isNaN(aNum) && Number.isNaN(bNum)) {
|
|
608
|
+
return a.properties.route_short_name.localeCompare(
|
|
609
|
+
b.properties.route_short_name,
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
if (Number.isNaN(aNum)) return 1;
|
|
613
|
+
if (Number.isNaN(bNum)) return -1;
|
|
614
|
+
|
|
615
|
+
const bNum = Number.parseInt(b.properties.route_short_name, 10);
|
|
616
|
+
return aNum - bNum;
|
|
617
|
+
});
|
|
581
618
|
|
|
582
619
|
new maplibregl.Popup()
|
|
583
620
|
.setLngLat(lngLat)
|
|
@@ -676,18 +713,32 @@ function unHighlightRoutes(map, zoom) {
|
|
|
676
713
|
}
|
|
677
714
|
|
|
678
715
|
function setupTableHoverListeners(map) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
716
|
+
if (document.readyState === 'loading') {
|
|
717
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
718
|
+
initializeTableHoverListeners(map);
|
|
719
|
+
});
|
|
720
|
+
} else {
|
|
721
|
+
initializeTableHoverListeners(map);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function initializeTableHoverListeners(map) {
|
|
726
|
+
const overviewLinks = document.querySelectorAll('.overview-list a');
|
|
727
|
+
|
|
728
|
+
overviewLinks.forEach((link) => {
|
|
729
|
+
link.addEventListener('mouseenter', (event) => {
|
|
730
|
+
const routeIdString = event.currentTarget.dataset.routeIds;
|
|
682
731
|
if (routeIdString) {
|
|
683
732
|
const routeIds = routeIdString.toString().split(',');
|
|
684
733
|
highlightRoutes(map, routeIds, true);
|
|
685
734
|
}
|
|
686
735
|
});
|
|
687
|
-
|
|
688
|
-
jQuery('.overview-list').hover(
|
|
689
|
-
() => {},
|
|
690
|
-
() => unHighlightRoutes(map, true),
|
|
691
|
-
);
|
|
692
736
|
});
|
|
737
|
+
|
|
738
|
+
const overviewList = document.querySelector('.overview-list');
|
|
739
|
+
if (overviewList) {
|
|
740
|
+
overviewList.addEventListener('mouseleave', () => {
|
|
741
|
+
unHighlightRoutes(map, true);
|
|
742
|
+
});
|
|
743
|
+
}
|
|
693
744
|
}
|