4 <title>live adsb map</title>
6 <script src="js/jquery-3.1.0.min.js"></script>
7 <script src="js/chart.umd.js"></script>
8 <script src="js/js.storage.min.js"></script>
9 <script src="js/kismet.ui.theme.js"></script>
11 <link rel="stylesheet" type="text/css" href="css/font-awesome.min.css">
12 <link rel="stylesheet" href="css/leaflet.css" />
13 <link rel="stylesheet" type="text/css" href="css/jquery.jspanel.min.css" />
14 <link rel="stylesheet" type="text/css" href="css/Control.Loading.css" />
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/datatables.min.js"></script>
26 <script src="js/dataTables.scrollResize.js"></script>
30 --adsb-sidebar-background: white;
31 --adsb-sidebar-background-offset: #f9f9f9;
35 --adsb-sidebar-background: #222;
36 --adsb-sidebar-background-offset: #444;
37 --map-tiles-filter: brightness(0.6) invert(1) contrast(3) hue-rotate(200deg) saturate(0.3) brightness(0.7);
41 filter: var(--map-tiles-filter, none);
51 font: 10pt "Helvetica Neue", Arial, Helvetica, sans-serif;
59 -ms-transform: translate(-50%, -50%);
60 transform: translate(-50%, -50%);
69 border: 1px solid black;
70 background: var(--adsb-sidebar-background);
81 border: 1px solid black;
82 background: var(--adsb-sidebar-background);
94 border: 1px solid black;
97 background: linear-gradient(to right,
129 display: inline-block;
134 box-sizing: border-box;
135 height: calc(100% - 125px);
136 padding: 0.5em 0.5em 1.5em 0.5em;
137 border-radius: 0.5em;
138 background: var(--adsb-sidebar-background-offset);
145 <div id="warning" class="warning">
147 <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.)
148 <p><input id="dontwarn" type="checkbox">Don't warn me again</input>
149 <p><button id="continue">Continue</button>
152 <div id="alt_min"></div>
153 <div id="alt_mini"></div>
154 <div id="alt_maxi"></div>
155 <div id="alt_max"></div>
156 <div id="alt_title"><strong>Altitude</strong></div>
159 <div class="right-sidebar">
160 <div id="plane-count" style="height: 10px">
161 <i class="fa fa-plane" style="padding-right: 1em;"></i><span id="numplanes">0</span> planes in the past 10 minutes
164 <div id="plane-detail" style="padding-top: 10px; height: 75px;"></div>
166 <div height="100%" class="resize_wrapper">
167 <table width="100%" id="adsb_planes" style="font-size: 80%">
185 if (kismet.getStorage('kismet.base.unit.distance') === 'metric' ||
186 kismet.getStorage('kismet.base.unit.distance') === '')
190 $('#alt_min').html("0m");
191 $('#alt_mini').html("3000m");
192 $('#alt_maxi').html("9000m");
193 $('#alt_max').html("12000m");
195 $('#alt_min').html("0ft");
196 $('#alt_mini').html("10000ft");
197 $('#alt_maxi').html("30000ft");
198 $('#alt_max').html("40000ft");
201 var window_visible = true;
203 // Visibility detection from https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
204 // Set the name of the hidden property and the change event for visibility
205 var hidden, visibilityChange;
206 if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
208 visibilityChange = "visibilitychange";
209 } else if (typeof document.msHidden !== "undefined") {
211 visibilityChange = "msvisibilitychange";
212 } else if (typeof document.webkitHidden !== "undefined") {
213 hidden = "webkitHidden";
214 visibilityChange = "webkitvisibilitychange";
217 function handleVisibilityChange() {
218 if (document[hidden]) {
219 window_visible = false;
221 window_visible = true;
225 // Warn if the browser doesn't support addEventListener or the Page Visibility API
226 if (typeof document.addEventListener === "undefined" || hidden === undefined) {
229 // Handle page visibility change
230 document.addEventListener(visibilityChange, handleVisibilityChange, false);
233 var urlparam = new URL(window.location.href);
234 var param_url = urlparam.searchParams.get('parent_url') + "/";
235 var param_prefix = urlparam.searchParams.get('local_uri_prefix', "");
236 var KISMET_PROXY_PREFIX = urlparam.searchParams.get('KISMET_PROXY_PREFIX', "");
238 if (param_prefix == 0)
241 var local_uri_prefix = param_url + param_prefix;
242 if (typeof(KISMET_URI_PREFIX) !== 'undefined')
243 local_uri_prefix = KISMET_URI_PREFIX;
245 var map_configured = false;
253 function get_alt_color(alt, v_perc=50) {
254 // Colors go from 50 to 360 on the HSV slider, so scale to 310
261 h = 40 + (310 * (alt / 12000));
264 return `hsl(${hv}, 100%, ${v_perc}%)`
266 alt_f = alt * 3.2808399;
272 h = 40 + (310 * (alt_f / 40000));
275 return `hsl(${hv}, 100%, ${v_perc}%)`
279 var moused_icao = null;
280 var moused_id = null;
282 var planes_dt = $('#adsb_planes').DataTable({
290 createdRow: function(row, data, index) {
291 row.id = `ROW_ICAO_${data[0]}`;
296 function wrap_closure_click(k) {
298 $('#adsb_planes').DataTable().row(`#ROW_ICAO_${markers[k]['icao']}`).scrollTo();
300 if (moused_icao != null)
301 $(`#ROW_ICAO_${moused_icao}`).css('background-color', '');
304 moused_icao = markers[k]['icao'];
306 $(`#ROW_ICAO_${markers[k]['icao']}`).css('background-color', 'red');
311 function wrap_closure_mouseover(k) {
313 if (markers[k]['path'] != null) {
314 markers[k]['path'].setStyle({
320 // $('#adsb_marker_icon_' + kismet.sanitizeId(k)).css('color', 'red');
321 $('#adsb_marker_icon_' + kismet.sanitizeId(k)).css('font-size', '24px');
323 $('#plane-detail').html("<b>Flight:</b> " + markers[k]['callsign'] + "<br>" +
324 "<b>Model:</b> " + markers[k]['model'].MiddleShorten(20) + "<br>" +
325 "<b>Operator: </b>" + markers[k]['operator'].MiddleShorten(20) + "<br>" +
326 "<b>Altitude: </b>" + kismet_units.renderHeightDistance(markers[k]['altitude'], 0, true) + "<br>" +
327 "<b>Speed: </b>" + kismet_units.renderSpeed(markers[k]['speed'], 0) + "<br>");
332 function wrap_closure_mouseout(k) {
334 if (markers[k]['path'] != null) {
335 markers[k]['path'].setStyle({
341 // $('#adsb_marker_icon_' + kismet.sanitizeId(k)).css('color', get_alt_color(markers[k]['altitude']));
342 $('#adsb_marker_icon_' + kismet.sanitizeId(k)).css('font-size', '18px');
347 data = kismet.sanitizeObject(d);
349 // $('#count').html("Active in the last 10 minutes: " + data['kismet.adsb.map.devices'].length);
350 $('#numplanes').html(data['kismet.adsb.map.devices'].length);
352 if (!map_configured) {
353 var lat1 = data['kismet.adsb.map.min_lat'];
354 var lon1 = data['kismet.adsb.map.min_lon'];
355 var lat2 = data['kismet.adsb.map.max_lat'];
356 var lon2 = data['kismet.adsb.map.max_lon'];
361 map.fitBounds([[lat1, lon1], [lat2, lon2]])
362 L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
364 attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
365 className: 'map-tiles',
368 map_configured = true;
371 var dt = $('#adsb_planes').DataTable();
374 'top': $(dt.settings()[0].nScrollBody).scrollTop(),
375 'left': $(dt.settings()[0].nScrollBody).scrollLeft()
380 for (var d = 0; d < data['kismet.adsb.map.devices'].length; d++) {
382 var lat = data['kismet.adsb.map.devices'][d]['kismet.device.base.location']['kismet.common.location.last']['kismet.common.location.geopoint'][1];
383 var lon = data['kismet.adsb.map.devices'][d]['kismet.device.base.location']['kismet.common.location.last']['kismet.common.location.geopoint'][0];
384 var heading = data['kismet.adsb.map.devices'][d]['kismet.device.base.location']['kismet.common.location.last']['kismet.common.location.heading'];
385 var altitude = data['kismet.adsb.map.devices'][d]['kismet.device.base.location']['kismet.common.location.last']['kismet.common.location.alt'];
386 var speed = data['kismet.adsb.map.devices'][d]['kismet.device.base.location']['kismet.common.location.last']['kismet.common.location.speed'];
387 var icao = data['kismet.adsb.map.devices'][d]['adsb.device']['adsb.device.icao'];
388 var id = data['kismet.adsb.map.devices'][d]['adsb.device']['kismet.adsb.icao_record']['adsb.icao.regid'];
389 var packets = data['kismet.adsb.map.devices'][d]['kismet.device.base.packets.data'];
390 var atype = data['kismet.adsb.map.devices'][d]['adsb.device']['kismet.adsb.icao_record']['adsb.icao.atype_short'];
393 // console.log([icao, id, altitude, speed, heading, packets]);
395 dt.row.add([icao, id, kismet_units.renderHeightDistanceUnitless(altitude, 0), kismet_units.renderSpeedUnitless(speed, 0, true), heading.toFixed(0), packets]);
397 if (lat == 0 || lon == 0)
400 key = data['kismet.adsb.map.devices'][d]['kismet.device.base.key'];
402 var icontype = 'fa-plane';
407 * 3 - Blimp/Dirigible
408 * 4 - Fixed wing single engine
409 * 5 - Fixed wing multi engine
411 * 7 - Weight-shift-control
412 * 8 - Powered Parachute
417 if (atype == "1".charCodeAt(0) || atype == "7".charCodeAt(0))
418 icontype == 'fa-paper-plane';
419 else if (atype == "6".charCodeAt(0))
420 icontype == 'fa-helicopter';
422 var myIcon = L.divIcon({
423 className: 'plane-icon',
424 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>',
425 iconAnchor: [12, 12],
428 if (key in markers) {
429 marker = markers[key]['marker'];
430 markers[key]['keep'] = true;
433 $('#adsb_marker_' + kismet.sanitizeId(key)).css('transform', 'rotate(' + (heading - 45) + 'deg)');
434 var new_loc = new L.LatLng(lat, lon);
435 marker.setLatLng(new_loc);
437 // Recolor the marker
438 $('#adsb_marker_icon_' + kismet.sanitizeId(k)).css('color', get_alt_color(altitude));
441 if (markers[key]['last_lat'] != lat || markers[key]['last_lon'] != lon) {
442 markers[key]['pathlist'].push([lat, lon]);
444 markers[key]['last_lat'] = lat;
445 markers[key]['last_lon'] = lon;
446 markers[key]['heading'] = heading;
448 if (markers[key]['path'] != null) {
449 markers[key]['path'].addLatLng([lat, lon]);
451 markers[key]['path'] = L.polyline(markers[key]['pathlist'], {
459 markers[key]['path'].on('mouseover', wrap_closure_mouseover(key));
460 markers[key]['path'].on('mouseout', wrap_closure_mouseout(key));
466 /* Make a new marker */
468 var marker = L.marker([lat, lon], { icon: myIcon} ).addTo(map);
469 $('#adsb_marker_' + kismet.sanitizeId(key)).css('transform', 'rotate(' + (heading - 45) + 'deg)');
472 markers[key]['marker'] = marker;
473 markers[key]['icao'] = icao;
474 markers[key]['keep'] = true;
475 markers[key]['pathlist'] = [[lat, lon]];
476 markers[key]['path'] = null;
477 markers[key]['last_path_ts'] = 0;
479 markers[key]['model'] = data['kismet.adsb.map.devices'][d]['adsb.device']['kismet.adsb.icao_record']['adsb.icao.model'];
480 markers[key]['operator'] = data['kismet.adsb.map.devices'][d]['adsb.device']['kismet.adsb.icao_record']['adsb.icao.owner'];
481 markers[key]['callsign'] = data['kismet.adsb.map.devices'][d]['adsb.device']['adsb.device.callsign'];
483 markers[key]['marker'].on('mouseover', wrap_closure_mouseover(key));
484 markers[key]['marker'].on('mouseout', wrap_closure_mouseout(key));
485 markers[key]['marker'].on('click', wrap_closure_click(key));
488 markers[key]['altitude'] = altitude;
489 markers[key]['heading'] = heading;
490 markers[key]['speed'] = speed;
491 markers[key]['last_lat'] = lat;
492 markers[key]['last_lon'] = lon;
495 // Assign the historic path, if location history is available
497 var history = data['kismet.adsb.map.devices'][d]['kismet.device.base.location_cloud']['kis.gps.rrd.samples_100'];
499 for (var s in history) {
500 // Ignore non-location historic points (caused by heading/altitude before we got
502 var s_lat = history[s]['kismet.historic.location.geopoint'][1];
503 var s_lon = history[s]['kismet.historic.location.geopoint'][0];
504 var s_alt = history[s]['kismet.historic.location.alt'];
505 var s_ts = history[s]['kismet.historic.location.time_sec'];
507 if (s_lat == 0 || s_lon == 0 || s_ts < markers[key]['last_path_ts'])
510 markers[key]['last_path_ts'] = s_ts;
512 if (markers[key]['path'] != null) {
513 markers[key]['path'].addLatLng([s_lat, s_lon]);
515 markers[key]['path'] = L.polyline([[s_lat, s_lon], [s_lat, s_lon]], {
516 color: get_alt_color(s_alt, 25),
520 options: function(v) {
521 return {'color': get_alt_color(s_alt)};
531 markers[key]['path'].on('mouseover', wrap_closure_mouseover(key));
532 markers[key]['path'].on('mouseout', wrap_closure_mouseout(key));
543 if (moused_icao != null) {
544 $(`#ROW_ICAO_${moused_icao}`).css('background-color', 'red');
547 // Restore our scroll position
548 $(dt.settings()[0].nScrollBody).scrollTop( prev_pos.top );
549 $(dt.settings()[0].nScrollBody).scrollLeft( prev_pos.left );
558 for (var k in markers) {
559 if (markers[k]['keep']) {
560 markers[k]['keep'] = false;
564 if (markers[k]['marker'] != null)
565 map.removeLayer(markers[k]['marker']);
566 if (markers[k]['path'] != null)
567 map.removeLayer(markers[k]['path']);
573 var load_maps = kismet.getStorage('kismet.adsb.maps_ok', false);
575 function poll_map() {
576 if (window_visible && !$('#map').is(':hidden') && load_maps) {
577 $.get(local_uri_prefix + KISMET_PROXY_PREFIX + "phy/ADSB/map_data.json")
581 .always(function(d) {
582 tid = setTimeout(function() { poll_map(); }, 2000);
585 tid = setTimeout(function() { poll_map(); }, 2000);
589 // Set a global timeout
593 withCredentials: true
598 $('#warning').hide();
600 $('#continue').on('click', function() {
601 if ($('#dontwarn').is(":checked"))
602 kismet.putStorage('kismet.adsb.maps_ok', true);
603 $('#warning').hide();