4 <title>live adsb map</title>
6 <link rel="stylesheet" type="text/css" href="css/font-awesome.min.css">
7 <link rel="stylesheet" href="css/leaflet.css" />
8 <link rel="stylesheet" type="text/css" href="css/jquery-ui.min.css" />
9 <link rel="stylesheet" type="text/css" href="css/jquery.jspanel.min.css" />
10 <link rel="stylesheet" type="text/css" href="css/jquery.dataTables.min.css" />
11 <link rel="stylesheet" type="text/css" href="css/responsive.dataTables.min.css" />
12 <link rel="stylesheet" type="text/css" href="css/colReorder.dataTables.min.css" />
13 <link rel="stylesheet" type="text/css" href="css/Control.Loading.css" />
15 <script src="js/jquery-3.1.0.min.js"></script>
16 <script src="js/leaflet.js"></script>
17 <script src="js/Leaflet.MultiOptionsPolyline.min.js"></script>
18 <script src="js/Control.Loading.js"></script>
19 <script src="js/chroma.min.js"></script>
21 <script src="js/js.storage.min.js"></script>
22 <script src="js/kismet.utils.js"></script>
23 <script src="js/kismet.units.js"></script>
25 <script src="js/jquery.dataTables.min.js"></script>
26 <script src="js/dataTables.scroller.min.js"></script>
27 <script src="js/dataTables.scrollResize.js"></script>
28 <!-- <script src="js/dataTables.pageResize.min.js"></script> -->
29 <script src="js/dataTables.colReorder.min.js"></script>
30 <script src="js/dataTables.responsive.min.js"></script>
40 font: 10pt "Helvetica Neue", Arial, Helvetica, sans-serif;
48 -ms-transform: translate(-50%, -50%);
49 transform: translate(-50%, -50%);
58 border: 1px solid black;
70 border: 1px solid black;
83 border: 1px solid black;
86 background: linear-gradient(to right,
118 display: inline-block;
123 box-sizing: border-box;
124 height: calc(100% - 105px);
125 padding: 0.5em 0.5em 1.5em 0.5em;
126 border-radius: 0.5em;
127 background-color: #f9f9f9;
134 <div id="warning" class="warning">
136 <p>To display the live ADSB map, your browser will connect to the Leaflet and Open Street Map servers to fetch the map tiles. This requires you have a functional Internet connection, and will reveal something about your location (the bounding region where planes have been seen.)
137 <p><input id="dontwarn" type="checkbox">Don't warn me again</input>
138 <p><button id="continue">Continue</button>
141 <div id="alt_min"></div>
142 <div id="alt_mini"></div>
143 <div id="alt_maxi"></div>
144 <div id="alt_max"></div>
145 <div id="alt_title"><strong>Altitude</strong></div>
148 <div class="right-sidebar">
149 <div id="plane-count" style="height: 10px">
150 <i class="fa fa-plane" style="padding-right: 1em;"></i><span id="numplanes">0</span> planes in the past 10 minutes
153 <div id="plane-detail" style="padding-top: 10px; height: 75px;"></div>
155 <div height="100%" class="resize_wrapper">
156 <table width="100%" id="adsb_planes" style="font-size: 80%">
174 if (kismet.getStorage('kismet.base.unit.distance') === 'metric' ||
175 kismet.getStorage('kismet.base.unit.distance') === '')
179 $('#alt_min').html("0m");
180 $('#alt_mini').html("3000m");
181 $('#alt_maxi').html("9000m");
182 $('#alt_max').html("12000m");
184 $('#alt_min').html("0ft");
185 $('#alt_mini').html("10000ft");
186 $('#alt_maxi').html("30000ft");
187 $('#alt_max').html("40000ft");
190 var window_visible = true;
192 // Visibility detection from https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
193 // Set the name of the hidden property and the change event for visibility
194 var hidden, visibilityChange;
195 if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
197 visibilityChange = "visibilitychange";
198 } else if (typeof document.msHidden !== "undefined") {
200 visibilityChange = "msvisibilitychange";
201 } else if (typeof document.webkitHidden !== "undefined") {
202 hidden = "webkitHidden";
203 visibilityChange = "webkitvisibilitychange";
206 function handleVisibilityChange() {
207 if (document[hidden]) {
208 window_visible = false;
210 window_visible = true;
214 // Warn if the browser doesn't support addEventListener or the Page Visibility API
215 if (typeof document.addEventListener === "undefined" || hidden === undefined) {
218 // Handle page visibility change
219 document.addEventListener(visibilityChange, handleVisibilityChange, false);
222 var urlparam = new URL(window.location.href);
223 var param_url = urlparam.searchParams.get('parent_url') + "/";
224 var param_prefix = urlparam.searchParams.get('local_uri_prefix', "");
225 var KISMET_PROXY_PREFIX = urlparam.searchParams.get('KISMET_PROXY_PREFIX', "");
227 if (param_prefix == 0)
230 var local_uri_prefix = param_url + param_prefix;
231 if (typeof(KISMET_URI_PREFIX) !== 'undefined')
232 local_uri_prefix = KISMET_URI_PREFIX;
234 var map_configured = false;
242 function get_alt_color(alt, v_perc=50) {
243 // Colors go from 50 to 360 on the HSV slider, so scale to 310
250 h = 40 + (310 * (alt / 12000));
253 return `hsl(${hv}, 100%, ${v_perc}%)`
255 alt_f = alt * 3.2808399;
261 h = 40 + (310 * (alt_f / 40000));
264 return `hsl(${hv}, 100%, ${v_perc}%)`
268 var moused_icao = null;
269 var moused_id = null;
271 var planes_dt = $('#adsb_planes').DataTable({
279 createdRow: function(row, data, index) {
280 row.id = `ROW_ICAO_${data[0]}`;
285 function wrap_closure_click(k) {
287 $('#adsb_planes').DataTable().row(`#ROW_ICAO_${markers[k]['icao']}`).scrollTo();
289 if (moused_icao != null)
290 $(`#ROW_ICAO_${moused_icao}`).css('background-color', '');
293 moused_icao = markers[k]['icao'];
295 $(`#ROW_ICAO_${markers[k]['icao']}`).css('background-color', 'red');
300 function wrap_closure_mouseover(k) {
302 if (markers[k]['path'] != null) {
303 markers[k]['path'].setStyle({
309 // $('#adsb_marker_icon_' + kismet.sanitizeId(k)).css('color', 'red');
310 $('#adsb_marker_icon_' + kismet.sanitizeId(k)).css('font-size', '24px');
312 $('#plane-detail').html("<b>Flight:</b> " + markers[k]['callsign'] + "<br>" +
313 "<b>Model:</b> " + markers[k]['model'] + "<br>" +
314 "<b>Operator: </b>" + markers[k]['operator'] + "<br>" +
315 "<b>Altitude: </b>" + kismet_units.renderHeightDistance(markers[k]['altitude'], 0, true) + "<br>" +
316 "<b>Speed: </b>" + kismet_units.renderSpeed(markers[k]['speed'], 0) + "<br>");
321 function wrap_closure_mouseout(k) {
323 if (markers[k]['path'] != null) {
324 markers[k]['path'].setStyle({
330 // $('#adsb_marker_icon_' + kismet.sanitizeId(k)).css('color', get_alt_color(markers[k]['altitude']));
331 $('#adsb_marker_icon_' + kismet.sanitizeId(k)).css('font-size', '18px');
336 data = kismet.sanitizeObject(d);
338 // $('#count').html("Active in the last 10 minutes: " + data['kismet.adsb.map.devices'].length);
339 $('#numplanes').html(data['kismet.adsb.map.devices'].length);
341 if (!map_configured) {
342 var lat1 = data['kismet.adsb.map.min_lat'];
343 var lon1 = data['kismet.adsb.map.min_lon'];
344 var lat2 = data['kismet.adsb.map.max_lat'];
345 var lon2 = data['kismet.adsb.map.max_lon'];
350 map.fitBounds([[lat1, lon1], [lat2, lon2]])
351 L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
353 attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
356 map_configured = true;
359 var dt = $('#adsb_planes').DataTable();
362 'top': $(dt.settings()[0].nScrollBody).scrollTop(),
363 'left': $(dt.settings()[0].nScrollBody).scrollLeft()
368 for (var d = 0; d < data['kismet.adsb.map.devices'].length; d++) {
370 var lat = data['kismet.adsb.map.devices'][d]['kismet.device.base.location']['kismet.common.location.last']['kismet.common.location.geopoint'][1];
371 var lon = data['kismet.adsb.map.devices'][d]['kismet.device.base.location']['kismet.common.location.last']['kismet.common.location.geopoint'][0];
372 var heading = data['kismet.adsb.map.devices'][d]['kismet.device.base.location']['kismet.common.location.last']['kismet.common.location.heading'];
373 var altitude = data['kismet.adsb.map.devices'][d]['kismet.device.base.location']['kismet.common.location.last']['kismet.common.location.alt'];
374 var speed = data['kismet.adsb.map.devices'][d]['kismet.device.base.location']['kismet.common.location.last']['kismet.common.location.speed'];
375 var icao = data['kismet.adsb.map.devices'][d]['adsb.device']['adsb.device.icao'];
376 var id = data['kismet.adsb.map.devices'][d]['adsb.device']['kismet.adsb.icao_record']['adsb.icao.regid'];
377 var packets = data['kismet.adsb.map.devices'][d]['kismet.device.base.packets.data'];
378 var atype = data['kismet.adsb.map.devices'][d]['adsb.device']['kismet.adsb.icao_record']['adsb.icao.atype_short'];
381 // console.log([icao, id, altitude, speed, heading, packets]);
383 dt.row.add([icao, id, kismet_units.renderHeightDistanceUnitless(altitude, 0), kismet_units.renderSpeedUnitless(speed, 0, true), heading.toFixed(0), packets]);
385 if (lat == 0 || lon == 0)
388 key = data['kismet.adsb.map.devices'][d]['kismet.device.base.key'];
390 var icontype = 'fa-plane';
395 * 3 - Blimp/Dirigible
396 * 4 - Fixed wing single engine
397 * 5 - Fixed wing multi engine
399 * 7 - Weight-shift-control
400 * 8 - Powered Parachute
405 if (atype == "1".charCodeAt(0) || atype == "7".charCodeAt(0))
406 icontype == 'fa-paper-plane';
407 else if (atype == "6".charCodeAt(0))
408 icontype == 'fa-helicopter';
410 var myIcon = L.divIcon({
411 className: 'plane-icon',
412 html: '<div id="adsb_marker_' + kismet.sanitizeId(key) + '" style="width: 24px; height: 24px; transform-origin: center;"><i id="adsb_marker_icon_' + kismet.sanitizeId(key) + '" class="marker-center fa ' + icontype + '" style="font-size: 18px; color: ' + get_alt_color(altitude) + ';"></div>',
413 iconAnchor: [12, 12],
416 if (key in markers) {
417 marker = markers[key]['marker'];
418 markers[key]['keep'] = true;
421 $('#adsb_marker_' + kismet.sanitizeId(key)).css('transform', 'rotate(' + (heading - 45) + 'deg)');
422 var new_loc = new L.LatLng(lat, lon);
423 marker.setLatLng(new_loc);
425 // Recolor the marker
426 $('#adsb_marker_icon_' + kismet.sanitizeId(k)).css('color', get_alt_color(altitude));
429 if (markers[key]['last_lat'] != lat || markers[key]['last_lon'] != lon) {
430 markers[key]['pathlist'].push([lat, lon]);
432 markers[key]['last_lat'] = lat;
433 markers[key]['last_lon'] = lon;
434 markers[key]['heading'] = heading;
436 if (markers[key]['path'] != null) {
437 markers[key]['path'].addLatLng([lat, lon]);
439 markers[key]['path'] = L.polyline(markers[key]['pathlist'], {
447 markers[key]['path'].on('mouseover', wrap_closure_mouseover(key));
448 markers[key]['path'].on('mouseout', wrap_closure_mouseout(key));
454 /* Make a new marker */
456 var marker = L.marker([lat, lon], { icon: myIcon} ).addTo(map);
457 $('#adsb_marker_' + kismet.sanitizeId(key)).css('transform', 'rotate(' + (heading - 45) + 'deg)');
460 markers[key]['marker'] = marker;
461 markers[key]['icao'] = icao;
462 markers[key]['keep'] = true;
463 markers[key]['pathlist'] = [[lat, lon]];
464 markers[key]['path'] = null;
465 markers[key]['last_path_ts'] = 0;
467 markers[key]['model'] = data['kismet.adsb.map.devices'][d]['adsb.device']['kismet.adsb.icao_record']['adsb.icao.model'];
468 markers[key]['operator'] = data['kismet.adsb.map.devices'][d]['adsb.device']['kismet.adsb.icao_record']['adsb.icao.owner'];
469 markers[key]['callsign'] = data['kismet.adsb.map.devices'][d]['adsb.device']['adsb.device.callsign'];
471 markers[key]['marker'].on('mouseover', wrap_closure_mouseover(key));
472 markers[key]['marker'].on('mouseout', wrap_closure_mouseout(key));
473 markers[key]['marker'].on('click', wrap_closure_click(key));
476 markers[key]['altitude'] = altitude;
477 markers[key]['heading'] = heading;
478 markers[key]['speed'] = speed;
479 markers[key]['last_lat'] = lat;
480 markers[key]['last_lon'] = lon;
483 // Assign the historic path, if location history is available
485 var history = data['kismet.adsb.map.devices'][d]['kismet.device.base.location_cloud']['kis.gps.rrd.samples_100'];
487 for (var s in history) {
488 // Ignore non-location historic points (caused by heading/altitude before we got
490 var s_lat = history[s]['kismet.historic.location.geopoint'][1];
491 var s_lon = history[s]['kismet.historic.location.geopoint'][0];
492 var s_alt = history[s]['kismet.historic.location.alt'];
493 var s_ts = history[s]['kismet.historic.location.time_sec'];
495 if (s_lat == 0 || s_lon == 0 || s_ts < markers[key]['last_path_ts'])
498 markers[key]['last_path_ts'] = s_ts;
500 if (markers[key]['path'] != null) {
501 markers[key]['path'].addLatLng([s_lat, s_lon]);
503 markers[key]['path'] = L.polyline([[s_lat, s_lon], [s_lat, s_lon]], {
504 color: get_alt_color(s_alt, 25),
508 options: function(v) {
509 return {'color': get_alt_color(s_alt)};
519 markers[key]['path'].on('mouseover', wrap_closure_mouseover(key));
520 markers[key]['path'].on('mouseout', wrap_closure_mouseout(key));
531 if (moused_icao != null) {
532 $(`#ROW_ICAO_${moused_icao}`).css('background-color', 'red');
535 // Restore our scroll position
536 $(dt.settings()[0].nScrollBody).scrollTop( prev_pos.top );
537 $(dt.settings()[0].nScrollBody).scrollLeft( prev_pos.left );
546 for (var k in markers) {
547 if (markers[k]['keep']) {
548 markers[k]['keep'] = false;
552 if (markers[k]['marker'] != null)
553 map.removeLayer(markers[k]['marker']);
554 if (markers[k]['path'] != null)
555 map.removeLayer(markers[k]['path']);
561 var load_maps = kismet.getStorage('kismet.adsb.maps_ok', false);
563 function poll_map() {
564 if (window_visible && !$('#map').is(':hidden') && load_maps) {
565 $.get(local_uri_prefix + KISMET_PROXY_PREFIX + "phy/ADSB/map_data.json")
569 .always(function(d) {
570 tid = setTimeout(function() { poll_map(); }, 2000);
573 tid = setTimeout(function() { poll_map(); }, 2000);
577 // Set a global timeout
581 withCredentials: true
586 $('#warning').hide();
588 $('#continue').on('click', function() {
589 if ($('#dontwarn').is(":checked"))
590 kismet.putStorage('kismet.adsb.maps_ok', true);
591 $('#warning').hide();