Skip to content

Instantly share code, notes, and snippets.

@youssef22222
Created June 12, 2025 10:07
Show Gist options
  • Save youssef22222/afed15882f46407fe6cd3ed0775a8fe5 to your computer and use it in GitHub Desktop.
Save youssef22222/afed15882f46407fe6cd3ed0775a8fe5 to your computer and use it in GitHub Desktop.
Speed Violation Map - 1692 EDD/New (Afzal) - 1749722845
<!DOCTYPE html>
<html>
<head>
<title>Speed Violation Path - 1692 EDD/New (Afzal)</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" />
<style>
html, body { height: 100%; margin: 0; padding: 0; }
#map { height: 100%; }
.number-icon {
background-color: #4285F4;
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);
}
.start-icon {
background-color: #00C853;
}
.end-icon {
background-color: #FF6F00; /* Orange for violation/end point */
width: 30px;
height: 30px;
line-height: 26px;
font-size: 14px;
}
.popup-content {
min-width: 250px;
}
.popup-content table {
width: 100%;
border-collapse: collapse;
}
.popup-content td {
padding: 2px 5px;
border-bottom: 1px solid #eee;
}
.popup-content td:first-child {
font-weight: bold;
width: 40%;
}
.speed-ok { color: green; }
.speed-violation { color: red; font-weight: bold; }
.legend-toggle {
position: absolute;
top: 90px;
right: 10px;
z-index: 1000;
background: white;
padding: 8px;
border: 2px solid #ccc;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
font-family: Arial, sans-serif;
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
}
</style>
</head>
<body>
<div id="map"></div>
<div id="legend-toggle" class="legend-toggle">Hide Legend</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.67030875, 49.897912625], 14);
// Add OpenStreetMap tile layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; OpenStreetMap contributors'
}).addTo(map);
// Define the coordinates and the detailed path data
var latlngs = [
[26.864942, 49.770333],
[26.830917, 49.797538],
[26.748077, 49.86894],
[26.656478, 49.926685],
[26.651943, 49.92752],
[26.558905, 49.953805],
[26.55443, 49.955117],
[26.496778, 49.983363]
];
var pathData = [{"latitude": 26.864942, "longitude": 49.770333, "time": "06/12/25,12:40:55", "speed": 206, "speed_limit": 120, "street_name": "Dhahran Jubail Expressway", "street_name_ar": "\u0637\u0631\u064a\u0642 \u0627\u0644\u0638\u0647\u0631\u0627\u0646 \u0627\u0644\u062c\u0628\u064a\u0644 \u0627\u0644\u0633\u0631\u064a\u0639", "distance_to_segment": 7.55, "vehicle_direction": 129, "segment_bearing": 132.4, "nearest_segment": [[26.8667993, 49.7679405], [26.863677, 49.7717739]], "closest_point": [26.864887059454407, 49.77028825098724], "angle_difference": 3.4}, {"latitude": 26.830917, "longitude": 49.797538, "time": "06/12/25,12:43:37", "speed": 201, "speed_limit": 120, "street_name": "Dhahran Jubail Expressway", "street_name_ar": "\u0637\u0631\u064a\u0642 \u0627\u0644\u0638\u0647\u0631\u0627\u0646 \u0627\u0644\u062c\u0628\u064a\u0644 \u0627\u0644\u0633\u0631\u064a\u0639", "distance_to_segment": 2.01, "vehicle_direction": 149, "segment_bearing": 149.5, "nearest_segment": [[26.8329402, 49.7961802], [26.8305947, 49.7977272]], "closest_point": [26.830906255762233, 49.797521710013136], "angle_difference": 0.5}, {"latitude": 26.748077, "longitude": 49.86894, "time": "06/12/25,12:50:19", "speed": 206, "speed_limit": 120, "street_name": "Dhahran Jubail Expressway", "street_name_ar": "\u0637\u0631\u064a\u0642 \u0627\u0644\u0638\u0647\u0631\u0627\u0646 \u0627\u0644\u062c\u0628\u064a\u0644 \u0627\u0644\u0633\u0631\u064a\u0639", "distance_to_segment": 0.39, "vehicle_direction": 156, "segment_bearing": 156.2, "nearest_segment": [[26.7502086, 49.8678813], [26.7471033, 49.8694173]], "closest_point": [26.748075279543855, 49.86893652178876], "angle_difference": 0.2}, {"latitude": 26.656478, "longitude": 49.926685, "time": "06/12/25,12:56:45", "speed": 213, "speed_limit": 120, "street_name": "Dhahran Jubail Expressway", "street_name_ar": "\u0637\u0631\u064a\u0642 \u0627\u0644\u0638\u0647\u0631\u0627\u0646 \u0627\u0644\u062c\u0628\u064a\u0644 \u0627\u0644\u0633\u0631\u064a\u0639", "distance_to_segment": 5.66, "vehicle_direction": 170, "segment_bearing": 170.6, "nearest_segment": [[26.6593496, 49.9260945], [26.6545753, 49.9269804]], "closest_point": [26.656467657429527, 49.92662926172908], "angle_difference": 0.6}, {"latitude": 26.651943, "longitude": 49.92752, "time": "06/12/25,12:57:02", "speed": 217, "speed_limit": 120, "street_name": "Dhahran Jubail Expressway", "street_name_ar": "\u0637\u0631\u064a\u0642 \u0627\u0644\u0638\u0647\u0631\u0627\u0646 \u0627\u0644\u062c\u0628\u064a\u0644 \u0627\u0644\u0633\u0631\u064a\u0639", "distance_to_segment": 7.74, "vehicle_direction": 171, "segment_bearing": 170.9, "nearest_segment": [[26.6542951, 49.9270189], [26.6486678, 49.9280291]], "closest_point": [26.651929285840918, 49.92744360553614], "angle_difference": 0.1}, {"latitude": 26.558905, "longitude": 49.953805, "time": "06/12/25,13:02:47", "speed": 211, "speed_limit": 120, "street_name": "Dhahran Jubail Expressway", "street_name_ar": "\u0637\u0631\u064a\u0642 \u0627\u0644\u0638\u0647\u0631\u0627\u0646 \u0627\u0644\u062c\u0628\u064a\u0644 \u0627\u0644\u0633\u0631\u064a\u0639", "distance_to_segment": 3.36, "vehicle_direction": 152, "segment_bearing": 160.9, "nearest_segment": [[26.559338, 49.9536017], [26.5588739, 49.9537814]], "closest_point": [26.55889299869117, 49.95377400496702], "angle_difference": 8.9}, {"latitude": 26.55443, "longitude": 49.955117, "time": "06/12/25,13:03:04", "speed": 208, "speed_limit": 120, "street_name": "Dhahran Jubail Expressway", "street_name_ar": "\u0637\u0631\u064a\u0642 \u0627\u0644\u0638\u0647\u0631\u0627\u0646 \u0627\u0644\u062c\u0628\u064a\u0644 \u0627\u0644\u0633\u0631\u064a\u0639", "distance_to_segment": 2.32, "vehicle_direction": 160, "segment_bearing": 168.5, "nearest_segment": [[26.5552019, 49.9549171], [26.553759, 49.9552464]], "closest_point": [26.55442485107079, 49.955094438840106], "angle_difference": 8.5}, {"latitude": 26.496778, "longitude": 49.983363, "time": "06/12/25,13:07:10", "speed": 198, "speed_limit": 120, "street_name": "", "street_name_ar": "To Road 95", "distance_to_segment": 0.95, "vehicle_direction": 147, "segment_bearing": 147.8, "nearest_segment": [[26.4978788, 49.9825766], [26.496404, 49.9836152]], "closest_point": [26.49677273574902, 49.98335552482443], "angle_difference": 0.8}];
// Draw the polyline (straight lines between points)
var polyline = L.polyline(latlngs, {
color: 'blue',
weight: 4,
opacity: 0.6,
dashArray: '10, 5'
}).addTo(map);
// Add directional arrows
var decorator = L.polylineDecorator(polyline, {
patterns: [
{
offset: 25,
repeat: 50,
symbol: L.Symbol.arrowHead({
pixelSize: 12,
polygon: false,
pathOptions: {
stroke: true,
color: 'blue',
weight: 2,
opacity: 0.8
}
})
}
]
}).addTo(map);
// Function to create numbered div icons
function createNumberedIcon(number, extraClass = '') {
return L.divIcon({
className: 'number-icon ' + extraClass,
html: number.toString(),
iconSize: [24, 24]
});
}
// Function to create popup content
function createPopupContent(pointNum, data) {
var lat = data.latitude;
var lon = data.longitude;
var isViolation = data.speed && data.speed_limit && (data.speed > data.speed_limit);
var speedClass = isViolation ? 'speed-violation' : 'speed-ok';
var html = '<div class="popup-content">';
html += '<h4 style="margin: 0 0 5px 0;">Point ' + pointNum + '</h4>';
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) { // This is street_name_en
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.angle_difference !== undefined) {
html += '<tr><td>Direction Diff:</td><td>' + data.angle_difference.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 (isViolation) {
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;
}
// Add markers for all points
for (var i = 0; i < pathData.length; i++) {
var iconClass = '';
var icon;
var data = pathData[i];
data.device_name = "1692 EDD/New (Afzal)"; // Add device name to each point's data
if (i === 0) {
iconClass = 'start-icon';
icon = createNumberedIcon('S', iconClass);
} else if (i === latlngs.length - 1) {
iconClass = 'end-icon'; // End point is the violation point
icon = createNumberedIcon('V', iconClass);
} else {
icon = createNumberedIcon(i + 1);
}
var popupContent = createPopupContent(i + 1, data);
var vehicleMarker = L.marker([data.latitude, data.longitude], {icon: icon})
.addTo(map)
.bindPopup(popupContent);
// Draw the nearest street segment for each point if available
if (data.nearest_segment && data.nearest_segment.length === 2) {
L.polyline(data.nearest_segment, {
color: 'red',
weight: 6,
opacity: 0.8
}).addTo(map).bindPopup('Nearest Street Segment for Point ' + (i + 1));
// Add start and end circles for the segment
L.circleMarker(data.nearest_segment[0], { radius: 4, color: 'black', fillColor: 'red', fillOpacity: 1 }).addTo(map);
L.circleMarker(data.nearest_segment[1], { radius: 4, color: 'black', fillColor: 'red', fillOpacity: 1 }).addTo(map);
}
// Draw the closest point on the segment and a line to it
if (data.closest_point && data.closest_point.length === 2) {
// Draw a small yellow circle at the closest point on the segment
L.circleMarker(data.closest_point, { radius: 4, color: 'black', fillColor: 'yellow', fillOpacity: 1 }).addTo(map);
// Draw a dashed line from the vehicle to the closest point
var line_to_closest = [
[data.latitude, data.longitude],
data.closest_point
];
L.polyline(line_to_closest, {
color: 'grey',
weight: 2,
opacity: 0.8,
dashArray: '5, 5'
}).addTo(map);
}
}
// Fit the map to show all points
map.fitBounds(polyline.getBounds().pad(0.1));
// Add a legend
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'legend');
div.style.background = 'white';
div.style.padding = '10px';
div.style.border = '2px solid #ccc';
div.style.borderRadius = '5px';
div.style.fontSize = '12px';
div.innerHTML = '<h4 style="margin: 0 0 5px 0;">Legend</h4>' +
'<p style="margin: 2px 0;"><span style="color: #00C853; font-weight: bold;">S</span> Start Point</p>' +
'<p style="margin: 2px 0;"><span style="color: #4285F4; font-weight: bold;">1-9</span> Path Points</p>' +
'<p style="margin: 2px 0;"><span style="color: #FF6F00; font-weight: bold;">V</span> Violation Location</p>' +
'<p style="margin: 2px 0;"><span style="background: #9b59b6; display: inline-block; width: 30px; height: 6px; border-radius: 3px; margin-right: 5px;"></span> Nearest Street Segment</p>' +
'<p style="margin: 2px 0;"><span style="color: blue;">- - →</span> Vehicle Path</p>' +
'<hr style="margin: 5px 0;">' +
'<p style="margin: 2px 0; font-style: italic; font-size: 11px;">Note: Lines show direct paths<br>between GPS points, not<br>actual street routes.</p>';
return div;
};
legend.addTo(map);
var legendContainer = legend.getContainer();
// Add info control
var info = L.control({position: 'topleft'});
info.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info');
div.style.background = 'rgba(255,255,255,0.9)';
div.style.padding = '8px';
div.style.border = '2px solid #ccc';
div.style.borderRadius = '5px';
div.style.fontSize = '14px';
var violationCount = 0;
for (var i = 0; i < pathData.length; i++) {
var d = pathData[i];
if (d.speed && d.speed_limit && d.speed > d.speed_limit) {
violationCount++;
}
}
div.innerHTML = '<b>Vehicle:</b> 1692 EDD/New (Afzal)<br>' +
'<b>Total Points:</b> ' + latlngs.length + '<br>' +
'<b>Violations on Path:</b> ' + violationCount;
return div;
};
info.addTo(map);
// Legend toggle logic
var toggleButton = document.getElementById('legend-toggle');
var legendVisible = true;
toggleButton.addEventListener('click', function() {
if (legendVisible) {
legendContainer.style.display = 'none';
toggleButton.innerHTML = 'Show Legend';
} else {
legendContainer.style.display = 'block';
toggleButton.innerHTML = 'Hide Legend';
}
legendVisible = !legendVisible;
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment