Skip to content

Instantly share code, notes, and snippets.

@youssef22222
Created June 15, 2025 14:37
Show Gist options
  • Save youssef22222/177c74211262271ca70948a0306cc531 to your computer and use it in GitHub Desktop.
Save youssef22222/177c74211262271ca70948a0306cc531 to your computer and use it in GitHub Desktop.
Speed Violation Map - Testing Live Alerts - 1749998243
<!DOCTYPE html>
<html>
<head>
<title>Speed Violation Path</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: #D50000;
}
.violation-icon {
background-color: #FF6F00;
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; }
</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.27654006976744, 50.03174895348838], 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
var latlngs = [
[26.289155, 50.17985],
[26.283237, 50.180558],
[26.310383, 50.042167],
[26.234768, 49.988715],
[26.238613, 49.976818],
[26.249993, 49.96803],
[26.257137, 49.971052],
[26.265493, 49.96512],
[26.283655, 49.9634],
[26.28638, 49.967448],
[26.28974, 49.972313],
[26.274007, 49.958062],
[26.242683, 49.973628],
[26.23856, 49.976705],
[26.242117, 49.982692],
[26.234438, 49.98877],
[26.302303, 50.044562],
[26.284072, 50.058583],
[26.258203, 50.192012],
[26.276465, 50.193605],
[26.28289, 50.20002],
[26.280278, 50.209838],
[26.279125, 50.21758],
[26.284135, 50.21926],
[26.29788, 50.165563],
[26.284342, 49.964173],
[26.270368, 49.960843],
[26.24305, 49.97415],
[26.24265, 49.973617],
[26.249902, 49.968048],
[26.25708, 49.97107],
[26.265455, 49.965138],
[26.28388, 49.963737],
[26.28945, 49.971893],
[26.289873, 49.972495],
[26.28123, 50.206925],
[26.284072, 50.197112],
[26.284223, 49.964012],
[26.34355, 50.010828],
[26.344212, 50.009868],
[26.347965, 50.008448],
[26.283928, 49.768715],
[26.280283, 49.957782]
];
// Speed data for each point
var speedData = [{"time": "06/12/25,05:53:10", "speed": 68, "speed_limit": 50, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,05:53:47", "speed": 68, "speed_limit": 50, "street_name": "King Khaled Road", "street_name_ar": "\u0637\u0631\u064a\u0642 \u0627\u0644\u0645\u0644\u0643 \u062e\u0627\u0644\u062f"}, {"time": "06/12/25,10:20:43", "speed": 86, "speed_limit": 60, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,10:28:03", "speed": 44, "speed_limit": 40, "street_name": "", "street_name_ar": "Makkah Road"}, {"time": "06/12/25,10:30:49", "speed": 49, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,10:54:28", "speed": 66, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,10:55:40", "speed": 66, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,10:56:53", "speed": 77, "speed_limit": 40, "street_name": "", "street_name_ar": "Makkah Road"}, {"time": "06/12/25,10:59:20", "speed": 72, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,10:59:46", "speed": 70, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,11:01:09", "speed": 62, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,13:44:01", "speed": 41, "speed_limit": 40, "street_name": "", "street_name_ar": "Makkah Road"}, {"time": "06/12/25,13:48:22", "speed": 62, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,13:59:48", "speed": 50, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,14:00:55", "speed": 48, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,14:02:17", "speed": 75, "speed_limit": 40, "street_name": "", "street_name_ar": "Makkah Road"}, {"time": "06/12/25,14:09:27", "speed": 68, "speed_limit": 60, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,14:11:03", "speed": 123, "speed_limit": 120, "street_name": "Abu Hadriah Rd", "street_name_ar": "\u0637\u0631\u064a\u0642 \u0623\u0628\u0648\u062d\u062f\u0631\u064a\u0629"}, {"time": "06/12/25,14:25:37", "speed": 82, "speed_limit": 60, "street_name": "", "street_name_ar": "\u0637\u0631\u064a\u0642 \u0627\u0644\u0645\u0644\u0643 \u062e\u0627\u0644\u062f \u0627\u0644\u0641\u0631\u0639\u064a"}, {"time": "06/12/25,14:39:11", "speed": 41, "speed_limit": 40, "street_name": "Makkah Al Mukarramah Street", "street_name_ar": "\u0634\u0627\u0631\u0639 \u0645\u0643\u0629 \u0627\u0644\u0645\u0643\u0631\u0645\u0629"}, {"time": "06/12/25,14:44:06", "speed": 62, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,14:44:53", "speed": 73, "speed_limit": 60, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,14:45:33", "speed": 70, "speed_limit": 60, "street_name": "", "street_name_ar": ""}, {"time": "06/12/25,14:46:14", "speed": 55, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/14/25,06:23:48", "speed": 86, "speed_limit": 80, "street_name": "Custodian of the Two Holly Mosques Road", "street_name_ar": "\u0637\u0631\u064a\u0642 \u062e\u0627\u062f\u0645 \u0627\u0644\u062d\u0631\u0645\u064a\u0646 \u0627\u0644\u0634\u0631\u064a\u0641\u064a\u0646"}, {"time": "06/14/25,06:43:14", "speed": 67, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/14/25,09:55:11", "speed": 78, "speed_limit": 40, "street_name": "", "street_name_ar": "Makkah Road"}, {"time": "06/14/25,09:58:56", "speed": 60, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/14/25,09:59:06", "speed": 47, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/14/25,10:04:53", "speed": 64, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/14/25,10:06:10", "speed": 66, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/14/25,10:07:37", "speed": 57, "speed_limit": 40, "street_name": "", "street_name_ar": "Makkah Road"}, {"time": "06/14/25,10:10:21", "speed": 75, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/14/25,10:11:13", "speed": 73, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/14/25,10:11:21", "speed": 59, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/15/25,05:18:45", "speed": 65, "speed_limit": 60, "street_name": "", "street_name_ar": ""}, {"time": "06/15/25,05:19:34", "speed": 82, "speed_limit": 80, "street_name": "Custodian of the Two Holy Mosques Road", "street_name_ar": "\u0637\u0631\u0642 \u062e\u0627\u062f\u0645 \u0627\u0644\u062d\u0631\u0645\u064a\u0646 \u0627\u0644\u0634\u0631\u064a\u0641\u064a\u0646"}, {"time": "06/15/25,05:44:21", "speed": 75, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/15/25,09:46:52", "speed": 45, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/15/25,09:49:01", "speed": 59, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/15/25,09:49:29", "speed": 58, "speed_limit": 40, "street_name": "", "street_name_ar": ""}, {"time": "06/15/25,10:03:37", "speed": 141, "speed_limit": 140, "street_name": "Dammam Riyadh Road", "street_name_ar": "\u0637\u0631\u064a\u0642 \u0627\u0644\u062f\u0645\u0627\u0645 \u0627\u0644\u0631\u064a\u0627\u0636 \u0627\u0644\u0633\u0631\u064a\u0639"}, {"time": "06/15/25,14:37:23", "speed": 67, "speed_limit": 40, "street_name": "", "street_name_ar": ""}];
// 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, lat, lon, data) {
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.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.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 < latlngs.length; i++) {
var iconClass = '';
var icon;
var data = speedData[i] || {};
if (i === 0) {
iconClass = 'start-icon';
icon = createNumberedIcon('S', iconClass);
} else if (i === latlngs.length - 1) {
iconClass = 'end-icon';
icon = createNumberedIcon('E', iconClass);
} else {
icon = createNumberedIcon(i + 1);
}
var popupContent = createPopupContent(i + 1, latlngs[i][0], latlngs[i][1], data);
L.marker(latlngs[i], {icon: icon})
.addTo(map)
.bindPopup(popupContent);
}
// Add violation marker if provided
var violationLat = null;
var violationLon = null;
var violationDetails = {"time": "2025-06-15 17:37:23", "street_name_ar": "", "street_name": "", "speed_limit": 40, "speed": 67};
if (violationLat && violationLon) {
var lastPoint = latlngs[latlngs.length - 1];
if (Math.abs(lastPoint[0] - violationLat) > 0.0001 || Math.abs(lastPoint[1] - violationLon) > 0.0001) {
var violationPopup = createPopupContent('Violation', violationLat, violationLon, violationDetails);
L.marker([violationLat, violationLon], {
icon: createNumberedIcon('V', 'violation-icon')
})
.addTo(map)
.bindPopup(violationPopup);
}
}
// 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: #D50000; font-weight: bold;">E</span> End 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="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);
// 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 < speedData.length; i++) {
if (speedData[i].speed && speedData[i].speed_limit && speedData[i].speed > speedData[i].speed_limit) {
violationCount++;
}
}
div.innerHTML = '<b>Total Points:</b> ' + latlngs.length + '<br>' +
'<b>Violations:</b> ' + violationCount;
return div;
};
info.addTo(map);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment