Created
June 23, 2025 19:23
-
-
Save youssef22222/96bfba9ba30530d49b54c881b44d433e to your computer and use it in GitHub Desktop.
Speed Violation Map - S10FLT-026 - 1750706614
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Speed Violation Path - S10FLT-026</title> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> | |
<style> | |
html, body { | |
height: 100%; | |
margin: 0; | |
padding: 0; | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; | |
} | |
#map { | |
height: 100%; | |
position: relative; | |
} | |
/* Enhanced marker styles */ | |
.number-icon { | |
background-color: #FF1744; | |
border: 2px solid white; | |
border-radius: 50%; | |
color: white; | |
font-weight: bold; | |
text-align: center; | |
font-size: 12px; | |
line-height: 20px; | |
width: 24px; | |
height: 24px; | |
margin-left: -12px; | |
margin-top: -12px; | |
box-shadow: 0 2px 5px rgba(0,0,0,0.3); | |
} | |
.violation-icon { | |
background-color: #FF1744; | |
border: 3px solid white; | |
width: 32px; | |
height: 32px; | |
line-height: 26px; | |
font-size: 16px; | |
font-weight: bold; | |
margin-left: -16px; | |
margin-top: -16px; | |
position: relative; | |
} | |
/* Pulsing animation for last violation point */ | |
.pulse-ring { | |
border: 3px solid #FF1744; | |
border-radius: 50%; | |
height: 50px; | |
width: 50px; | |
position: absolute; | |
left: 50%; | |
top: 50%; | |
transform: translate(-50%, -50%); | |
animation: pulsate 2s ease-out; | |
animation-iteration-count: infinite; | |
opacity: 0; | |
} | |
@keyframes pulsate { | |
0% { | |
transform: translate(-50%, -50%) scale(0.1, 0.1); | |
opacity: 0; | |
} | |
50% { | |
opacity: 1; | |
} | |
100% { | |
transform: translate(-50%, -50%) scale(1.2, 1.2); | |
opacity: 0; | |
} | |
} | |
/* Enhanced popup styles */ | |
.popup-content { | |
min-width: 280px; | |
font-family: inherit; | |
} | |
.popup-content table { | |
width: 100%; | |
border-collapse: collapse; | |
} | |
.popup-content td { | |
padding: 4px 8px; | |
border-bottom: 1px solid #eee; | |
font-size: 13px; | |
} | |
.popup-content td:first-child { | |
font-weight: 600; | |
width: 45%; | |
color: #555; | |
} | |
.popup-content td:last-child { | |
color: #2c3e50; | |
} | |
.popup-title { | |
font-weight: 600; | |
color: #2c3e50; | |
margin-bottom: 8px; | |
padding-bottom: 8px; | |
border-bottom: 2px solid #e9ecef; | |
font-size: 16px; | |
} | |
.speed-violation { | |
color: #e74c3c; | |
font-weight: bold; | |
} | |
/* Enhanced legend styles */ | |
.legend { | |
position: absolute; | |
bottom: 20px; | |
right: 20px; | |
background: white; | |
padding: 15px 20px; | |
border-radius: 12px; | |
box-shadow: 0 4px 20px rgba(0,0,0,0.15); | |
font-size: 13px; | |
z-index: 1000; | |
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); | |
transform: translateX(0); | |
min-width: 200px; | |
} | |
.legend.collapsed { | |
transform: translateX(calc(100% + 20px)); | |
} | |
.legend-toggle-btn { | |
position: absolute; | |
left: -35px; | |
top: 50%; | |
width: 35px; | |
height: 50px; | |
background-color: #3498db; | |
color: white; | |
border: none; | |
cursor: pointer; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
font-size: 16px; | |
box-shadow: 0 2px 8px rgba(0,0,0,0.15); | |
transition: all 0.3s ease; | |
z-index: 1001; | |
border-radius: 8px 0 0 8px; | |
transform: translateY(-50%); | |
} | |
.legend-toggle-btn:hover { | |
background-color: #2980b9; | |
box-shadow: 0 4px 12px rgba(0,0,0,0.2); | |
} | |
.legend-toggle-btn i { | |
transition: transform 0.3s ease; | |
} | |
.legend.collapsed .legend-toggle-btn i { | |
transform: rotate(180deg); | |
} | |
.legend-title { | |
font-weight: 600; | |
margin-bottom: 12px; | |
color: #2c3e50; | |
font-size: 14px; | |
} | |
.legend-item { | |
display: flex; | |
align-items: center; | |
gap: 12px; | |
margin-bottom: 8px; | |
font-size: 12px; | |
} | |
.legend-item:last-child { | |
margin-bottom: 0; | |
} | |
.legend-marker { | |
width: 18px; | |
height: 18px; | |
border-radius: 50%; | |
border: 2px solid white; | |
box-shadow: 0 1px 3px rgba(0,0,0,0.2); | |
flex-shrink: 0; | |
} | |
.legend-line { | |
width: 25px; | |
height: 4px; | |
border-radius: 2px; | |
flex-shrink: 0; | |
} | |
.legend-arrow { | |
width: 25px; | |
height: 4px; | |
background: #3498db; | |
position: relative; | |
flex-shrink: 0; | |
} | |
.legend-arrow::after { | |
content: ''; | |
position: absolute; | |
right: 0; | |
top: -3px; | |
width: 0; | |
height: 0; | |
border-left: 8px solid #3498db; | |
border-top: 5px solid transparent; | |
border-bottom: 5px solid transparent; | |
} | |
.legend-path-arrow { | |
width: 25px; | |
height: 4px; | |
background: #000000; | |
position: relative; | |
flex-shrink: 0; | |
} | |
.legend-path-arrow::after { | |
content: ''; | |
position: absolute; | |
right: 0; | |
top: -3px; | |
width: 0; | |
height: 0; | |
border-left: 8px solid #000000; | |
border-top: 5px solid transparent; | |
border-bottom: 5px solid transparent; | |
} | |
/* Info control styles */ | |
.info-control { | |
min-width: 200px; | |
position: absolute; | |
top: 20px; | |
left: 20px; | |
background: rgba(255,255,255,0.95); | |
padding: 15px; | |
border-radius: 10px; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
font-size: 14px; | |
z-index: 1000; | |
} | |
.info-control h4 { | |
margin: 0 0 8px 0; | |
color: #2c3e50; | |
font-size: 16px; | |
} | |
.info-control div { | |
margin: 3px 0; | |
} | |
/* Enhanced leaflet popup */ | |
.leaflet-popup-content { | |
margin: 13px 19px; | |
font-size: 14px; | |
line-height: 1.5; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="map"></div> | |
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> | |
<script src="https://unpkg.com/leaflet-polylinedecorator@1.6.0/dist/leaflet.polylineDecorator.js"></script> | |
<script> | |
// Initialize the map | |
var map = L.map('map').setView([26.189318666666665, 50.01781233333333], 14); | |
// Add OpenStreetMap tile layer | |
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
maxZoom: 19, | |
attribution: '© OpenStreetMap contributors' | |
}).addTo(map); | |
// Define the coordinates and the detailed path data | |
var latlngs = [ | |
[26.167893, 50.003312], | |
[26.198738, 50.023297], | |
[26.201325, 50.026828] | |
]; | |
var pathData = [{"latitude": 26.167893, "longitude": 50.003312, "time": "06/23/25,22:16:33", "speed": 158, "speed_limit": 100, "street_name": "King Fahd Road", "street_name_ar": "\u0637\u0631\u064a\u0642 \u0627\u0644\u0645\u0644\u0643 \u0641\u0647\u062f", "distance_to_segment": 0.47, "vehicle_direction": 357, "segment_bearing": 0.3, "nearest_segment": [[26.1677225, 50.0033155], [26.1688912, 50.0033224]], "closest_point": [26.167894341559876, 50.003316513238566]}, {"latitude": 26.198738, "longitude": 50.023297, "time": "06/23/25,22:21:26", "speed": 146, "speed_limit": 90, "street_name": null, "street_name_ar": null, "distance_to_segment": 7.66, "vehicle_direction": 47, "segment_bearing": 49.4, "nearest_segment": [[26.1985552, 50.022941], [26.1989535, 50.0234595]], "closest_point": [26.198789402316947, 50.023245879773]}, {"latitude": 26.201325, "longitude": 50.026828, "time": "06/23/25,22:23:25", "speed": 119, "speed_limit": 90, "street_name": null, "street_name_ar": null, "distance_to_segment": 10.31, "vehicle_direction": 50, "segment_bearing": 60.9, "nearest_segment": [[26.2013159, 50.0265995], [26.2014117, 50.0267915]], "closest_point": [26.2014117, 50.0267915]}]; | |
var fullPathCoordinates = [[26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.158932, 50.003838], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.167893, 50.003312], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.17432, 50.008378], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.178493, 50.016442], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.187028, 50.016247], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.194512, 50.019235], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.198738, 50.023297], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.200503, 50.025537], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828], [26.201325, 50.026828]]; | |
// Add device name to each point's data | |
pathData.forEach(function(data) { | |
data.device_name = "S10FLT-026"; | |
}); | |
// Global variable to store path decorators for zoom-based updates | |
var pathDecorators = []; | |
// Function to calculate arrow spacing based on zoom level | |
function getArrowSpacing(zoomLevel) { | |
if (zoomLevel >= 16) return '50px'; // Close zoom: many arrows | |
if (zoomLevel >= 14) return '100px'; // Medium zoom: moderate arrows | |
if (zoomLevel >= 12) return '150px'; // Far zoom: fewer arrows | |
return '200px'; // Very far zoom: minimal arrows | |
} | |
// Function to update path arrows based on zoom level | |
function updatePathArrows() { | |
var currentZoom = map.getZoom(); | |
var spacing = getArrowSpacing(currentZoom); | |
// Remove existing decorators | |
pathDecorators.forEach(function(decorator) { | |
map.removeLayer(decorator); | |
}); | |
pathDecorators = []; | |
// Re-add decorators with new spacing if path exists | |
if (window.vehiclePath) { | |
var decorator = L.polylineDecorator(window.vehiclePath, { | |
patterns: [{ | |
offset: '25px', | |
repeat: spacing, | |
symbol: L.Symbol.arrowHead({ | |
pixelSize: 12, | |
polygon: false, | |
pathOptions: { | |
stroke: true, | |
color: '#000000', | |
weight: 2, | |
opacity: 0.8 | |
} | |
}) | |
}] | |
}); | |
decorator.addTo(map); | |
pathDecorators.push(decorator); | |
} | |
} | |
// Function to create numbered div icons for violations | |
function createNumberedIcon(number, isLast = false) { | |
if (isLast) { | |
return L.divIcon({ | |
className: 'number-icon violation-icon', | |
html: 'V<div class="pulse-ring"></div>', | |
iconSize: [32, 32] | |
}); | |
} else { | |
return L.divIcon({ | |
className: 'number-icon', | |
html: number.toString(), | |
iconSize: [24, 24] | |
}); | |
} | |
} | |
// Function to create popup content | |
function createPopupContent(pointNum, data, isLast) { | |
var lat = data.latitude; | |
var lon = data.longitude; | |
var speedClass = 'speed-violation'; | |
var html = '<div class="popup-content">'; | |
html += '<div class="popup-title">Violation Point ' + pointNum + (isLast ? ' (Latest)' : '') + '</div>'; | |
html += '<table>'; | |
html += '<tr><td>Latitude:</td><td>' + lat.toFixed(6) + '</td></tr>'; | |
html += '<tr><td>Longitude:</td><td>' + lon.toFixed(6) + '</td></tr>'; | |
if (data.time) { | |
html += '<tr><td>Time:</td><td>' + data.time + '</td></tr>'; | |
} | |
if (data.device_name) { | |
html += '<tr><td>Vehicle:</td><td>' + data.device_name + '</td></tr>'; | |
} | |
if (data.street_name_ar) { | |
html += '<tr><td>Street (AR):</td><td style="direction: rtl;">' + data.street_name_ar + '</td></tr>'; | |
} | |
if (data.street_name) { | |
html += '<tr><td>Street (EN):</td><td>' + data.street_name + '</td></tr>'; | |
} | |
if (data.distance_to_segment !== undefined) { | |
html += '<tr><td>Dist to street:</td><td>' + data.distance_to_segment.toFixed(2) + ' m</td></tr>'; | |
} | |
if (data.vehicle_direction !== undefined) { | |
html += '<tr><td>Vehicle Dir:</td><td>' + data.vehicle_direction.toFixed(1) + '°</td></tr>'; | |
} | |
if (data.segment_bearing !== undefined) { | |
html += '<tr><td>Street Bearing:</td><td>' + data.segment_bearing.toFixed(1) + '°</td></tr>'; | |
} | |
if (data.speed_limit) { | |
html += '<tr><td>Speed Limit:</td><td>' + data.speed_limit + ' km/h</td></tr>'; | |
} | |
if (data.speed) { | |
html += '<tr><td>Vehicle Speed:</td><td class="' + speedClass + '">' + data.speed + ' km/h</td></tr>'; | |
} | |
if (data.speed && data.speed_limit) { | |
var overSpeed = data.speed - data.speed_limit; | |
html += '<tr><td>Over Speed:</td><td class="speed-violation">+' + overSpeed + ' km/h</td></tr>'; | |
} | |
html += '</table>'; | |
html += '</div>'; | |
return html; | |
} | |
// Function to create direction arrow from point | |
function createDirectionArrow(lat, lon, direction) { | |
if (direction === undefined || direction === null) return null; | |
var arrowLength = 0.0002; // Length of arrow in degrees | |
var radians = direction * Math.PI / 180; | |
var endLat = lat + arrowLength * Math.cos(radians); | |
var endLon = lon + arrowLength * Math.sin(radians); | |
var arrow = L.polyline([[lat, lon], [endLat, endLon]], { | |
color: '#3498db', | |
weight: 3, | |
opacity: 0.8 | |
}); | |
var decorator = L.polylineDecorator(arrow, { | |
patterns: [{ | |
offset: '100%', | |
repeat: 0, | |
symbol: L.Symbol.arrowHead({ | |
pixelSize: 12, | |
polygon: false, | |
pathOptions: { | |
stroke: true, | |
color: '#3498db', | |
weight: 2, | |
opacity: 0.9 | |
} | |
}) | |
}] | |
}); | |
return { arrow: arrow, decorator: decorator }; | |
} | |
// Draw the vehicle path with directional arrows if full_path is provided | |
if (fullPathCoordinates && fullPathCoordinates.length > 1) { | |
// Create the main path line in black | |
window.vehiclePath = L.polyline(fullPathCoordinates, { | |
color: '#000000', | |
weight: 4, | |
opacity: 0.8, | |
smoothFactor: 1 | |
}).addTo(map); | |
// Add initial arrows | |
updatePathArrows(); | |
// Update arrows when zoom changes | |
map.on('zoomend', updatePathArrows); | |
} | |
// Add markers for all points - all are violation points | |
var allBounds = []; | |
for (var i = 0; i < pathData.length; i++) { | |
var data = pathData[i]; | |
var isLast = (i === latlngs.length - 1); | |
var icon = createNumberedIcon(i + 1, isLast); | |
var popupContent = createPopupContent(i + 1, data, isLast); | |
// Add marker to map | |
var vehicleMarker = L.marker([data.latitude, data.longitude], {icon: icon}) | |
.addTo(map) | |
.bindPopup(popupContent); | |
allBounds.push([data.latitude, data.longitude]); | |
// Add direction arrow for each point | |
if (data.vehicle_direction !== undefined && data.vehicle_direction !== null) { | |
var directionArrowData = createDirectionArrow(data.latitude, data.longitude, data.vehicle_direction); | |
if (directionArrowData) { | |
directionArrowData.arrow.addTo(map); | |
directionArrowData.decorator.addTo(map); | |
} | |
} | |
// Draw the nearest street segment for each point if available | |
if (data.nearest_segment && data.nearest_segment.length === 2) { | |
var segmentPolyline = L.polyline(data.nearest_segment, { | |
color: '#9b59b6', | |
weight: 8, | |
opacity: 0.8 | |
}).addTo(map).bindPopup('Nearest Street Segment for Violation ' + (i + 1)); | |
// Add enhanced start and end circles for the segment | |
L.circleMarker(data.nearest_segment[0], { | |
radius: 6, | |
color: '#8e44ad', | |
fillColor: '#9b59b6', | |
fillOpacity: 1, | |
weight: 2 | |
}).addTo(map); | |
L.circleMarker(data.nearest_segment[1], { | |
radius: 6, | |
color: '#8e44ad', | |
fillColor: '#9b59b6', | |
fillOpacity: 1, | |
weight: 2 | |
}).addTo(map); | |
} | |
// Draw the closest point on the segment and a line to it | |
if (data.closest_point && data.closest_point.length === 2) { | |
// Enhanced closest point marker | |
L.circleMarker(data.closest_point, { | |
radius: 8, | |
color: '#e67e22', | |
fillColor: '#f39c12', | |
fillOpacity: 1, | |
weight: 3 | |
}).addTo(map).bindPopup('Closest Point on Street<br>Distance: ' + (data.distance_to_segment ? data.distance_to_segment.toFixed(2) + ' m' : 'N/A')); | |
// Enhanced dashed line from vehicle to closest point | |
var lineToClosest = [ | |
[data.latitude, data.longitude], | |
data.closest_point | |
]; | |
L.polyline(lineToClosest, { | |
color: '#3498db', | |
weight: 3, | |
opacity: 0.7, | |
dashArray: '8, 8' | |
}).addTo(map); | |
} | |
} | |
// Include full path coordinates in bounds if available | |
if (fullPathCoordinates) { | |
fullPathCoordinates.forEach(function(coord) { | |
allBounds.push(coord); | |
}); | |
} | |
// Fit the map to show all points | |
if (allBounds.length > 0) { | |
var bounds = L.latLngBounds(allBounds); | |
map.fitBounds(bounds.pad(0.1)); | |
} | |
// Add enhanced legend with toggle functionality | |
var legendHtml = '<button class="legend-toggle-btn" onclick="toggleLegend()"><i class="fas fa-chevron-right"></i></button>'; | |
legendHtml += '<div class="legend-title">Map Legend</div>'; | |
legendHtml += '<div class="legend-item"><div class="legend-marker" style="background: #FF1744;"></div><span>Violation Points</span></div>'; | |
legendHtml += '<div class="legend-item"><div class="legend-marker" style="background: #FF1744; width: 24px; height: 24px;"></div><span>Latest Violation</span></div>'; | |
legendHtml += '<div class="legend-item"><div class="legend-line" style="background: #000000; height: 6px;"></div><span>Vehicle Path</span></div>'; | |
legendHtml += '<div class="legend-item"><div class="legend-path-arrow"></div><span>Path Direction</span></div>'; | |
legendHtml += '<div class="legend-item"><div class="legend-arrow"></div><span>Vehicle Direction</span></div>'; | |
legendHtml += '<div class="legend-item"><div class="legend-line" style="background: #9b59b6; height: 6px;"></div><span>Nearest Street Segment</span></div>'; | |
legendHtml += '<div class="legend-item"><div class="legend-marker" style="background: #f39c12;"></div><span>Closest Point</span></div>'; | |
legendHtml += '<div class="legend-item"><div class="legend-line" style="background: #3498db; border: 2px dashed #3498db; height: 2px;"></div><span>Distance Line</span></div>'; | |
var legendControl = L.control({position: 'bottomright'}); | |
legendControl.onAdd = function (map) { | |
var div = L.DomUtil.create('div', 'legend'); | |
div.id = 'mapLegend'; | |
div.innerHTML = legendHtml; | |
return div; | |
}; | |
legendControl.addTo(map); | |
// Toggle legend function | |
function toggleLegend() { | |
var legend = document.getElementById('mapLegend'); | |
legend.classList.toggle('collapsed'); | |
} | |
// Add info control | |
var infoControl = L.control({position: 'topleft'}); | |
infoControl.onAdd = function (map) { | |
var div = L.DomUtil.create('div', 'info-control'); | |
var pathInfo = fullPathCoordinates ? '<div><strong>Path Points:</strong> ' + fullPathCoordinates.length + '</div>' : ''; | |
div.innerHTML = '<h4><i class="fas fa-car" style="margin-right: 8px; color: #3498db;"></i>Vehicle Path Analysis</h4>' + | |
'<div><strong>Vehicle:</strong> S10FLT-026</div>' + | |
'<div><strong>Total Violations:</strong> ' + latlngs.length + '</div>' + | |
pathInfo; | |
return div; | |
}; | |
infoControl.addTo(map); | |
// Add zoom control to top right | |
L.control.zoom({position: 'topright'}).addTo(map); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment