-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
684 lines (613 loc) Β· 24.4 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
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
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
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
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Safe Crossing</title>
<meta name="description" content="Safe Crossing is a volunteer project to evaluate the compliance of pedestrian crossings in Luxembourg-City with laws, regulations, and official guidelines.">
<meta name="author" content="ZUG">
<meta property="og:title" content="Safe Crossing - Unsafe pedestrian crossings in Luxembourg-City">
<meta property="og:site_name" content="Safe Crossing by ZUG (Zentrum fir Urban Gerechtegkeet)">
<meta property="og:url" content="https://www.zug.lu/safe-crossing">
<meta property="og:description" content="Safe Crossing is a volunteer project to evaluate the compliance of pedestrian crossings in Luxembourg-City with laws, regulations, and official guidelines.">
<meta property="og:type" content="website">
<meta property="og:image" content="https://zug.lu/safe-crossing/img/banner.png">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha384-sHL9NAb7lN7rfvG5lfHpm643Xkcjzp4jFvuavGOndn6pjVqS6ny56CAt3nsEVT4H" crossorigin="" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha384-cxOPjt7s7Iz04uaHJceBmS+qpjv2JkIHNVcuOrM+YHwZOmJGBXI00mdUXEq65HTH" crossorigin=""></script>
<style>
@keyframes info-open-mobile {
from {height: 0;}
to {height: 70%;}
}
html, body {
height: 100%;
width: 100%;
background-color: #242121;
}
body {
padding: 0;
margin: 0;
display: flex;
flex-direction: row;
font-family: sans-serif;
}
@media only screen and (max-width: 768px) {
body {
display: block;
flex-direction: unset;
}
}
a {
color: orange;
}
h1 {
margin: 0;
}
h1, h2, h3, h4, h5 {
color: orange;
}
.leaflet-tooltip {
border-radius: 20px;
padding: 15px;
background-color: #242121;
color: lightgray;
border: 0;
}
.leaflet-tooltip-left::before {
border-left-color: #242121;
}
.leaflet-tooltip-right::before {
border-right-color: #242121;
}
#info {
z-index: 9999;
overflow: auto;
padding: 1.5rem;
max-width: 400px;
background-color: #242121;
color: lightgrey;
box-shadow: rgba(0, 0, 0, 0.95) 0px 5px 15px;
}
.leaflet-tooltip {
width: 350px;
}
.leaflet-tooltip p, .leaflet-tooltip h3 {
white-space: normal;
}
@media only screen and (max-width: 768px) {
#info {
height: 70%;
max-width: 100%;
position: absolute;
bottom: 0;
transition: .5s;
animation-name: info-open-mobile;
animation-duration: .5s;
}
#info.minimized {
height: 170px;
background-color: transparent;
box-shadow: none;
pointer-events: none;
overflow: hidden;
}
#info.minimized #btn-minimize {
display: none;
}
#info.minimized #btn-maximize {
display: block;
}
#info.minimized .project-switcher-title,
#info.minimized .spacer,
#info.minimized .project-switcher-description {
opacity: 0;
}
#info #btn-maximize {
display: none;
}
.leaflet-tooltip {
width: 250px;
}
}
#info h1 {
color: orange;
}
#map {
flex-grow: 1;
height: 100%;
width: 100%;
}
.invisible {
display: none;
}
.red {
color: red;
font-weight: bold;
}
.yellow {
color: yellow;
font-weight: bold;
}
.orange {
color: orange;
font-weight: bold;
}
.white {
color: white;
font-weight: bold;
}
.more-information {
text-align: center;
background-color: #4b4646;
border-radius: 4px;
padding: 20px;
margin: 40px 0;
}
a.btn {
text-decoration: none;
}
.btn {
border-radius: 20px;
padding: 15px;
width: 100%;
text-transform: uppercase;
font-weight: bold;
background-color: #242121;
color: orange;
border-color: orange;
border-style: solid;
box-shadow: 0 0 15px rgba(0,0,0,0.2);
transition: .2s;
font-size: small;
text-align: center;
}
.btn.round {
padding: 0;
float: right;
border-radius: 15px;
height: 30px;
width: 30px;
text-align: center;
}
.btn:hover {
background-color: orange;
color: #242121;
}
.info {
padding: 10px 14px;
font: 14px/16px sans-serif;
background: #242121;
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 20px;
}
.legend {
line-height: 18px;
color: lightgrey;
}
.legend p {
margin-top: 4px;
margin-bottom: 4px;
}
.legend p i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 1;
border-radius: 9px;
}
.project-switcher-title {
margin-bottom: 20px;
transition: .5s;
}
.project-switcher-head {
margin-bottom: 20px;
}
.project-switcher-description {
transition: .5s;
}
.btn-group {
display: flex;
flex-direction: row;
pointer-events: all;
}
.btn-group .btn {
border-radius: 0;
}
.btn-group .btn:first-child {
border-top-left-radius:20px;
border-bottom-left-radius:20px;
}
.btn-group .btn:last-child {
border-top-right-radius:20px;
border-bottom-right-radius:20px;
border-left-width: 0;
}
.btn-group .btn.active {
background-color: orange;
color: #242121;
}
.spacer {
margin-left: -1.5rem;
margin-right: -1.5rem;
border-bottom: 1px solid #484848;
margin-bottom: 20px;
}
.hidden {
display: none;
}
.inline {
display: inline;
}
.example-img {
width: 100%;
}
.zebra-30-marker {
font-size: 20px;
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
}
@media only screen and (min-width: 769px) {
.mobile {
display: none;
}
}
</style>
</head>
<body>
<div id="info">
<div class="project-switcher-title">
<div>
<h1 class="inline">Safe Crossing</h1>
<button id="btn-maximize" class="btn round mobile" onClick="maximizeInfo()">β</button>
<button id="btn-minimize" class="btn round mobile" onClick="minimizeInfo()">β</button>
</div>
</div>
<div class="project-switcher-head">
<div class="btn-group">
<a class="btn btn-project-selector btn-thirtykmh" href="#zebra30">2023: Missing crosswalks</a>
<a class="btn btn-project-selector btn-safe-crossing" href="#safe-crossing">2021: Unsafe crosswalks</a>
</div>
</div>
<div class="spacer"></div>
<div class="project-switcher-description hidden" id="project-safe-crossing">
<h2>Unsafe crosswalks</h2>
<p>Uses aerial imagery from 2020.</p>
<p>
This is the original "Safe Crossing" project, crowd-sourced and released in 2021.
</p>
<p>
The map shows dangerous pedestrian crossings in Luxembourg-City. They are dangerous because designated parking spots (including bus stops) are
located less than 5 metres away from them. This reduces drivers' visibility of the crosswalk and the people using it, putting them in
unnecessary danger. In fact, setting up parking spots so close to crosswalks is also in violation of the Highway Code ("Code de la route").
</p>
<div class="spacer"></div>
<h3>How to read the map</h3>
<p>
Red and orange dots mark the relevant crossings:
</p>
<p>
<span class="red">Red (475 crossings, 27%):</span> Dangerous crosswalk due to illegal parking spots nearby
</p>
<p>
<span class="yellow">Yellow (162 crossings, 9%):</span> Unable to determine whether the crosswalk is dangerous<br>
</p>
<p>
Safe crosswalks which are not affected by dangerously close parking spots are not shown on the map (1150 crossings, 64%).
</p>
<p>
By hovering (or tapping) on a dot, you can check the street name and neighbourhood, and how many users cast votes during the crowd-sourcing. You can
also check out the relevant OpenStreetMap node id.
</p>
<div class="spacer"></div>
<h3>How data was gathered</h3>
<p>The data for the project was gathered through a crowd-sourced effort with about 25 volunteers in 2021. You can find detailed information
about the process at the link at the bottom of this text.
</p>
<div class="spacer"></div>
<p>
Raw location data for the pedestrian crossings comes from OpenStreetMap. Satellite imagery comes from Geoportail. Download
the data <a href="https://zug.lu/safe-crossing/safe-crossing-results-u1.geojson">as GeoJSON</a> or as
<a href="https://zug.lu/safe-crossing/safe-crossing-results-u1.csv">CSV</a>.
</p>
<p class="more-information">
<a href="more.html">Want to know more? Click here for all the nitty-gritty details of the project</a>
</p>
</div>
<div class="project-switcher-description hidden" id="project-thirtykmh">
<h2>Missing or incorrect crosswalks at Zone 30 entrances</h2>
<p>
The map shows pedestrian crossings in Luxembourg-City that are missing or incorrect. When drivers enter a "zone 30" from a major road (N or CR road),
<a href="https://pch.gouvernement.lu/dam-assets/administration/competences/permission-de-voirie/ministerielle/tome-1/e-guide-application-zones30km.pdf">regulations</a> and <a href="https://transports.public.lu/content/dam/transport/circulation-routiere/Brochure-Apaisement-du-trafic-PDF.pdf">guidelines</a> from 2013 require a pedestrian crossing with a red background.
<b>In Luxembourg-City, the red background is missing in at least 119 cases, or the pedestrian crossing is missing completely in at least 106 cases</b>.
</p>
<p>
Without the red crossings, drivers might not realise they are entering a 30 zone, crossing the road is dangerous and uncomfortable for pedestrians, and walking gets discouraged.
</p>
<p>In 2021, <a href="#safe-crossing">we had already alerted</a> the city about unsafe crossings with parking spaces too close. This new project shows that the city keeps systematically failing to apply guidelines when it comes to pedestrian infrastructure.</a>
<div class="spacer"></div>
<h3>Examples of dangerous crossings</h3>
<p>
<img class="example-img" src="img/Oradour.jpg">
</p>
<p>
The crossing on rue d'Oradour is missing a red background. (The crossing on rue Walram has a red background.)
</p>
<div class="spacer"></div>
<h3>How to read the map</h3>
<p>
Dots made of emojis mark the relevant crossings:
</p>
<p>
π¨: Crosswalk completely missing at entrance of 30km/h zone, and probably dangerous and uncomfortable for pedestrians. Walking is discouraged.
</p>
<p>
π: Crosswalk is there but should have red background at entrance of 30km/h zone.
</p>
<p>
β
: Crosswalk has red background at entrance of 30km/h zone as required. All good.
</p>
<p>
Clicking on a dot shows the street name and neighbourhood.
</p>
<div class="spacer"></div>
<h3>FAQ</h3>
<h4>Why are there crossings with no dot?</h4>
<p>Only the crossings at the entrance of a <i>zone 30</i> are shown.</p>
<h4>Are crossings legally required at zone 30 entrances?</h4>
<p><b>Always</b> when coming from a <a href="https://overpass-turbo.eu/s/1vMS">major road</a> (<i>voirie Γ©tatique</i>, i.e. N or CR roads). See E.2.8. and E.2.9. of the <a href="https://pch.gouvernement.lu/dam-assets/administration/competences/permission-de-voirie/ministerielle/tome-1/e-guide-application-zones30km.pdf">regulations</a> and page 16 of the <a href="https://transports.public.lu/content/dam/transport/circulation-routiere/Brochure-Apaisement-du-trafic-PDF.pdf">guidelines</a> from 2013
<h4>Are crossings legally required at zone 30 exits on one-way streets?</h4>
<p>The regulations are unclear. Crossings certainly make sense there, for example along boulevard Roosevelt.</p>
<h4>I want to report a mistake, or want to help. How do I contact you?</h4>
<p><a href="https://zug.lu/contact/">On our contact page</a> or <a href="https://twitter.com/zug_lu">on Twitter</a>
<h4>How do you know that these are the applicable rules and guidelines?</h4>
<p>The city sent us these guidelines in response to an information request <a href="https://data.legilux.public.lu/filestore/eli/etat/leg/loi/2018/09/14/a883/jo/fr/html/eli-etat-leg-loi-2018-09-14-a883-jo-fr-html.html">under the <i>Loi du 14 septembre 2018 relative Γ une administration transparente et ouverte</i></a>. They did not mention any other rules or documents.</p>
<h4>What would you like to happen now?</h4>
<p>The city should fix all these pedestrian crossings.</p>
<h4>How is ZUG's <a href="https://zug.lu/safe-crossing-crowdfunding/">court case against the city of Luxembourg</a> going?</h4>
<p>Court cases take a long time. Our lawyers and the city's have submitted written arguments. We shook our heads a lot reading their arguments, and are confident that we will win when the case goes in front of a judge in a few months.</p>
<div class="spacer"></div>
<h3>Disclaimer and thanks</h3>
<p>
The location and paint data for the pedestrian crossings comes from OpenStreetMap, and the analysis was automated using the <a href="https://overpass-turbo.eu/s/1vMQ">Overpass API</a>. OpenStreetMap data is crowdsourced and may contain errors. ZUG
can't guarantee that the presented data is without errors, and kindly asks you to please report any that you find. 2022 Aerial imagery from Geoportail. Download raw data <a href="zebra30.geojson">as geojson</a>.
</p>
<div class="spacer"></div>
<h3>Revisions</h3>
<p>
2023-06-21: removed false positives in rue Irmine and parvis de la Gare. Updated figures.
</p>
<p>
2023-06-23: removed false positives in rue Chimay, rue de la Fonderie, rue de la Semois, chemin de la Corniche, rue Sosthène Weis. Updated figures.
</p>
</div>
</div>
<div id="map"></div>
<script>
const safeCrossingUrl = 'https://zug.lu/safe-crossing/safe-crossing-results-u1.geojson';
const zebra30Url = 'https://zug.lu/safe-crossing/zebra30.geojson';
const isMobile = window.matchMedia("only screen and (max-width: 760px)").matches;
let ownPositionMarker;
let ownPositionRadiusMarker;
function getResultDescription(result) {
switch (result) {
case 2:
case 12:
return "Designated parking too close to crossing";
break;
default:
return "Ambiguous or impossible to see";
}
}
function getLocationDescription(street, neighbourhood) {
if (street && neighbourhood) return `${street} (${neighbourhood})`;
else if (street) return street;
else if (neighbourhood) return neighbourhood;
return "";
}
async function loadCrosswalksGeojson(url) {
return (await fetch(url)).json();
}
function bindThirtyKmhTooltips(feature, layer) {
const { crossing, 'crossing:marking': crossingMarking } = feature.properties;
const options = { opacity: 1 };
const locationString = `<b>${feature.properties.quartier}</b>, ${feature.properties.rue}`;
switch(feature.properties.crossing_marking) {
case 'unmarked': return layer.bindTooltip(`<h3>Crossing missing completely! π¨</h3><p>A pedestrian crossing is missing completely at this <i>zone 30</i> entrance. It is probably dangerous and uncomfortable for pedestrians, and discourages walking.</p>${locationString}`, options);
case 'no': return layer.bindTooltip(`<h3>Missing red background π</h3><p>While there is a crossing at this <i>zone 30</i> entrance, the required red background is missing. Car drivers might not realise they are entering a 30 zone. This is dangerous for pedestrians, and discourages walking.</p>${locationString}`, options);
case 'red': return layer.bindTooltip(`<h3>Crossing is painted red β
</h3><p>The red backgrounds has been painted at this <i>zone 30</i> entrance. This doesn't mean that there are no other issues.</p>${locationString}`, options);
}
}
function bindSafeCrossingTooltips(feature, layer) {
const { street, neighbourhood, votesTotal, currentResult, osmnodeid } = feature.properties;
layer.bindTooltip(`<h3>${getLocationDescription(street, neighbourhood)}</h3><p><b>${getResultDescription(currentResult)}</b> - ${votesTotal} votes</p><p><a href="https://openstreetmap.org/node/${osmnodeid}">See on OpenStreetMap</a></p>`, { opacity: 1 });
}
const map = L.map('map').setView([49.6061209088, 6.12493950024], 14);
const mapLink = '<a href="http://geoportail.lu">Geoportail</a>';
const ortho2020 = L.tileLayer(
'https://wmts1.geoportail.lu/opendata/wmts/ortho_2020/GLOBAL_WEBMERCATOR_4_V3/{z}/{x}/{y}.jpeg',
{
attribution: 'Map data © <a href="https://openstreetmap.org/copyright">OpenStreetMap</a>. 2020 aerial photography CC0 <a href="http://geoportail.lu">Geoportail</a>',
maxZoom: 21,
}
);
const ortho2022 = L.tileLayer(
'https://wmts1.geoportail.lu/opendata/wmts/hybrid/GLOBAL_WEBMERCATOR_4_V3/{z}/{x}/{y}.png',
{
attribution: 'Map data © <a href="https://openstreetmap.org/copyright">OpenStreetMap</a>. 2022 aerial photography CC0 <a href="http://geoportail.lu">Geoportail</a>',
maxZoom: 21,
}
);
map.addLayer(ortho2022)
function onLocationFound(e) {
const radius = e.accuracy;
if (!ownPositionMarker) {
ownPositionMarker = L.marker(e.latlng).addTo(map);
} else {
ownPositionMarker.setLatLng(e.latlng)
}
if (!ownPositionRadiusMarker) {
ownPositionRadiusMarker = L.circle(e.latlng, { radius, interactive: false }).addTo(map);
} else {
ownPositionRadiusMarker.setLatLng(e.latlng)
}
}
map.on('locationfound', onLocationFound);
async function startLocation() {
map.locate({setView: false, watch: true, /* maxZoom: 17 */});
}
if (!isMobile) {
startLocation();
}
function hideInfoAndMaybeLocate() {
document.getElementById("info").className = "invisible";
map.invalidateSize();
if (isMobile) {
startLocation();
}
}
const notSureMarkerOptions = {
radius: 6,
color: "#fede42",
fillColor: "#242121",
weight: 3,
opacity: 1,
fillOpacity: 1
};
const tooCloseMarkerOptions = {
radius: 6,
color: "#ff0000",
fillColor: "#242121",
weight: 3,
opacity: 1,
fillOpacity: 1
};
const zebra30MarkerSize = 20;
const zebra30MarkerOptions = {
iconSize: [zebra30MarkerSize, zebra30MarkerSize],
iconAnchor: [zebra30MarkerSize/2, zebra30MarkerSize+9],
className: 'zebra-30-marker',
};
const buildZebra30MarkerOptions = (htmlContent) => ({
draggable: false,
icon: L.divIcon(Object.assign({}, zebra30MarkerOptions, { html: htmlContent }))
});
const zebra30MissingIcon = buildZebra30MarkerOptions('π¨');
const zebra30PresentButNotRed = buildZebra30MarkerOptions('π');
const zebra30Ok = buildZebra30MarkerOptions('β
');
const safeCrossingGroup = L.layerGroup().addTo(map);
loadCrosswalksGeojson(safeCrossingUrl).then(data => L.geoJson(data, {
onEachFeature: bindSafeCrossingTooltips,
pointToLayer: function (feature, latlng) {
switch (feature.properties.currentResult) {
case 2:
case 12:
return L.circleMarker(latlng, tooCloseMarkerOptions).addTo(safeCrossingGroup);
break;
default:
return L.circleMarker(latlng, notSureMarkerOptions).addTo(safeCrossingGroup);
}
}
}));
const thirtyKmhGroup = L.layerGroup();
loadCrosswalksGeojson(zebra30Url).then(data => L.geoJson(data, {
onEachFeature: bindThirtyKmhTooltips,
pointToLayer: function (feature, latlng) {
switch(feature.properties.crossing_marking) {
case 'unmarked': return L.marker(latlng, zebra30MissingIcon).addTo(thirtyKmhGroup);
case 'no': return L.marker(latlng, zebra30PresentButNotRed).addTo(thirtyKmhGroup);
case 'red': return L.marker(latlng, zebra30Ok).addTo(thirtyKmhGroup);
}
}
}));
const safeCrossingLegend = L.control({position: 'topright'});
safeCrossingLegend.onAdd = function(map) {
const div = L.DomUtil.create('div', 'info legend');
div.innerHTML +=
'<p><i style="background: #ff0000"></i> Parking spots too close to crossing</p>';
div.innerHTML +=
'<p><i style="background: #fede42"></i> Unclear because aerial view is obstructed</p>';
return div;
};
safeCrossingLegend.addTo(map);
const thirtyKmhLegend = L.control({position: 'topright'});
thirtyKmhLegend.onAdd = function(map) {
const div = L.DomUtil.create('div', 'info legend');
div.innerHTML +=
'<p>π¨ Crosswalk completely missing</p>';
div.innerHTML +=
'<p>π Red background missing from crosswalk</p>';
div.innerHTML +=
'<p>β
Red background painted</p>';
return div;
};
function toggleSafeCrossing() {
if (!map.hasLayer(safeCrossingGroup)) map.addLayer(safeCrossingGroup);
map.addControl(safeCrossingLegend);
map.removeLayer(thirtyKmhGroup);
map.removeControl(thirtyKmhLegend);
map.addLayer(ortho2020);
map.removeLayer(ortho2022);
const safeCrossingButtons = document.getElementsByClassName('btn-safe-crossing');
for (let i = 0; i < safeCrossingButtons.length; i++) {
safeCrossingButtons.item(i).classList.add("active");
}
const thirtyKmhButtons = document.getElementsByClassName('btn-thirtykmh');
for (let i = 0; i < thirtyKmhButtons.length; i++) {
thirtyKmhButtons.item(i).classList.remove("active");
}
document.getElementById("project-thirtykmh").classList.add("hidden");
document.getElementById("project-safe-crossing").classList.remove("hidden");
maximizeInfo();
}
function toggle30kmh() {
if (!map.hasLayer(thirtyKmhGroup)) map.addLayer(thirtyKmhGroup);
map.addControl(thirtyKmhLegend);
map.removeLayer(safeCrossingGroup);
map.removeControl(safeCrossingLegend);
map.addLayer(ortho2022);
map.removeLayer(ortho2020);
const thirtyKmhButtons = document.getElementsByClassName('btn-thirtykmh');
for (let i = 0; i < thirtyKmhButtons.length; i++) {
thirtyKmhButtons.item(i).classList.add("active");
}
const safeCrossingButtons = document.getElementsByClassName('btn-safe-crossing');
for (let i = 0; i < safeCrossingButtons.length; i++) {
safeCrossingButtons.item(i).classList.remove("active");
}
document.getElementById("project-safe-crossing").classList.add("hidden");
document.getElementById("project-thirtykmh").classList.remove("hidden");
maximizeInfo();
}
const maximizeInfo = (event) => {
document.getElementById("info").classList.remove("minimized");
};
const minimizeInfo = (event) => {
document.getElementById("info").classList.add("minimized");
document.getElementById("info").scrollTo({ top: 0, behavior: 'smooth' });
};
document.getElementById("map").onmousedown = minimizeInfo;
map.on('movestart', minimizeInfo);
function detectTabFromUrl() {
if(window.location.pathname.includes('/safe-crossing')) {
toggleSafeCrossing();
} else {
toggle30kmh();
}
}
function hashChange() {
const selectedTabFromUrl = window.location.hash.substr(1);
if(selectedTabFromUrl === 'safe-crossing') {
toggleSafeCrossing();
} else {
toggle30kmh();
}
}
if(window.location.hash) {
hashChange();
} else {
detectTabFromUrl();
}
window.onhashchange = hashChange;
</script>
</body>
</html>