506 lines
19 KiB
JavaScript
506 lines
19 KiB
JavaScript
/********************************************************************
|
|
* Dynamic Aircraftlist for Radarcape
|
|
********************************************************************
|
|
* Guenter Koellner Embedded Development GmbH
|
|
*
|
|
* Am Rain 24
|
|
* 85256 Vierkirchen
|
|
* Geschaeftsfuehrer Guenter Koellner
|
|
*
|
|
* http://www.modesbeast.com
|
|
********************************************************************/
|
|
|
|
var refresh_time = 1;
|
|
|
|
var aircraftlist_body;
|
|
var aircraftlist_header;
|
|
var aircraftlist_row2clone;
|
|
|
|
var cookie_sort = "aclist_sort";
|
|
var sort = {
|
|
"key" : "hex",
|
|
"asc" : true,
|
|
"empty_on_top" : true
|
|
};
|
|
|
|
var cookie_colselect = "aclist_colselect";
|
|
|
|
const category = {
|
|
"A0":"A0: Unspecified powered aircraft",
|
|
"A1":"A1: Light (< 15500 lbs)",
|
|
"A2":"A2: Small (15500 to 75000 lbs)",
|
|
"A3":"A3: Large (75000 to 300000 lbs)",
|
|
"A4":"A4: High Vortex Large",
|
|
"A5":"A5: Heavy (> 300000 lbs)",
|
|
"A6":"A6: High Performance ( > 5 g acceleration and > 400 kts)",
|
|
"A7":"A7: Rotorcraft",
|
|
"B0":"B0: Unspecified unpowered aircraft or UAV or spacecraft",
|
|
"B1":"B1: Glider/Sailplane",
|
|
"B2":"B2: Lighter-than-Air",
|
|
"B3":"B3: Parachutist/Skydiver",
|
|
"B4":"B4: Ultralight/Hang Glider/Paraglider",
|
|
"B5":"B5: Reserved",
|
|
"B6":"B6: Unmanned Aerial Vehicle",
|
|
"B7":"B7: Space/Trans-atmospheric Vehicle",
|
|
"C0":"C0: Unspecified ground installation or vehicle",
|
|
"C1":"C1: Surface Vehicle - Emergency Vehicle",
|
|
"C2":"C2: Surface Vehicle - Service Vehicle",
|
|
"C3":"C3: Fixed Ground or Tethered Obstruction",
|
|
"F0":"F00: Flarm: Unknown",
|
|
"F1":"F01: Flarm: Glider",
|
|
"F2":"F02: Flarm: Tow Plane",
|
|
"F3":"F03: Flarm: Helicopter,Rotorcraft",
|
|
"F4":"F04: Flarm: Parachute",
|
|
"F5":"F05: Flarm: Drop Plane",
|
|
"F6":"F06: Flarm: Hang Glider",
|
|
"F7":"F07: Flarm: Para Glider",
|
|
"F8":"F08: Flarm: Powered Aircraft",
|
|
"F9":"F09: Flarm: Jet Aircraft",
|
|
"F10":"F10: Flarm: UFO",
|
|
"F11":"F11: Flarm: Balloon",
|
|
"F12":"F12: Flarm: Airship",
|
|
"F13":"F13: Flarm: UAV",
|
|
"F14":"F14: Flarm: Unused",
|
|
"F15":"F15: Flarm: Static Object"
|
|
}
|
|
|
|
var category_title = '';
|
|
for( var key in category ) {
|
|
category_title += category[key] + '\r\n';
|
|
}
|
|
|
|
var aclist = {
|
|
"reg": { "visible":true, "span":"Click on the link to show aircraft on planespotters.net", "title":"Registration", "align":"center"},
|
|
"hex": { "visible":true, "span":null, "title":"Mode-S", "align":"center"},
|
|
"fli": { "visible":true, "span":null, "title":"Flight", "align":"center"},
|
|
"orgh": { "visible":true, "span":null, "title":"Origin", "align":"center"},
|
|
"org": { "visible":true, "span":null, "title":"Origin<br/>Name", "align":"center"},
|
|
"dsth": { "visible":true, "span":null, "title":"Destination", "align":"center"},
|
|
"dst": { "visible":true, "span":null, "title":"Destination<br/>Name","align":"center"},
|
|
"trk": { "visible":true, "span":"True Track", "title":"Track", "align":"center"},
|
|
"hdgm": { "visible":true, "span":null, "title":"Magn.<br/>Heading","align":"center"},
|
|
"hdgt": { "visible":true, "span":null, "title":"True<br/>Heading", "align":"center"},
|
|
"alt": { "visible":true, "span":"Barometric Altitude [feet]", "title":"Altitude", "align":"center"},
|
|
"altg": { "visible":true, "span":"GNSS Elipsoidal Altitude [feet]", "title":"GNSS<br/>Altitude","align":"center"},
|
|
"spd": { "visible":true, "span":"Ground Speed [knots]", "title":"Speed", "align":"center"},
|
|
"ias": { "visible":true, "span":"Indicated Air Speed", "title":" IAS ", "align":"center"},
|
|
"tas": { "visible":true, "span":"True Air Speed", "title":" TAS ", "align":"center"},
|
|
"vrt": { "visible":true, "span":"Vertical Rate [feet per minute]", "title":"Vertical<br/>Rate","align":"center"},
|
|
"gda": { "visible":true, "span":"Aircraft on Ground or Airborne", "title":"GND/<br/>AIR", "align":"center"},
|
|
"typ": { "visible":true, "span":"Aircraft Type", "title":"Type", "align":"center"},
|
|
"cat": { "visible":true, "span":category_title, "title":"Category", "align":"center"},
|
|
"opr": { "visible":true, "span":null, "title":"Operator", "align":"center"},
|
|
"cou": { "visible":true, "span":null, "title":"Country", "align":"center"},
|
|
"src": { "visible":true, "span":"Source of Position Information", "title":"Source", "align":"center"},
|
|
"squ": { "visible":true, "span":null, "title":"Squawk", "align":"center"},
|
|
"lat": { "visible":true, "span":"Click on Latitude to show aircraft in Openlayers map", "title":"Latitude", "align":"center"},
|
|
"lon": { "visible":true, "span":"Click on Longitude to show aircraft in Gmap", "title":"Longitude", "align":"center"},
|
|
"dis": { "visible":true, "span":"Distance from Receiver", "title":"Distance", "align":"center"},
|
|
"spi": { "visible":true, "span":null, "title":"Indicator", "align":"center"},
|
|
"alr": { "visible":true, "span":"0=no alert, 1=temporary (Squawk changed previous 18s) 2=Mode-S alert 3=permanent (Squawk 7500,7600,7700)","title":"Alert", "align":"center"},
|
|
"ape": { "visible":true, "span":null, "title":"Autopilot<br/>engaged","align":"center"},
|
|
"tcm": { "visible":true, "span":"TCAS 0=not operational, 1: operational, 2: alert", "title":"TCAS", "align":"center"},
|
|
"qnhs": { "visible":true, "span":null, "title":"QNH<br/>sel.", "align":"center"},
|
|
"alts": { "visible":true, "span":null, "title":"Altitude<br/>sel.","align":"center"},
|
|
"hdgs": { "visible":true, "span":null, "title":"Heading<br/>sel.", "align":"center"},
|
|
"dbm": { "visible":true, "span":"Signal Level [dBm]", "title":"Signal<br/>Level", "align":"center"},
|
|
"tmp": { "visible":true, "span":null, "title":"Temp.<br/>°C", "align":"center"},
|
|
"wsp": { "visible":true, "span":null, "title":"Wind<br/>Speed", "align":"center"},
|
|
"wdi": { "visible":true, "span":null, "title":"Wind<br/>Direction","align":"center"},
|
|
"tim": { "visible":true, "span":null, "title":"Last Signal", "align":"center"},
|
|
"mop": { "visible":true, "span":"null=no ADS-B received, 0=default for unconfirmed version","title":"MOPS<br/>version","align":"center"},
|
|
"tru": { "visible":true, "span":"Number of Trustable DF-11/17/18 Messages", "title":"Trust", "align":"center"},
|
|
"pic": { "visible":true, "span":null, "title":"ASTERIX<br/>PIC", "align":"center"}
|
|
}
|
|
|
|
|
|
function aircraftlistSetVisibleColumns() {
|
|
var cells = aircraftlist_header.cells;
|
|
for (var key in aclist) {
|
|
cells.namedItem(key).style.display = aclist[key].visible ? 'table-cell' : 'none';
|
|
}
|
|
}
|
|
|
|
|
|
function createAircraftlist() {
|
|
// create table
|
|
//
|
|
var table = document.createElement("TABLE");
|
|
table.setAttribute("class", "table table-striped table-condensed table-more-condensed table-bordered table-hover");
|
|
table.style.whiteSpace = "pre-line";
|
|
|
|
var div = document.getElementById("div_aircraftlist");
|
|
div.appendChild(table);
|
|
|
|
|
|
// create table header
|
|
//
|
|
var header = table.createTHead();
|
|
aircraftlist_header = header.insertRow(-1);
|
|
|
|
for (var key in aclist) {
|
|
var cell = aircraftlist_header.insertCell(-1);
|
|
|
|
var contents = '<b>' + aclist[key].title + '</b></span><br/>';
|
|
//contents += '<div class="arrow_box" id="' + key + '"><div class="arrow_up"></div><div class="arrow_down"></div></div>';
|
|
contents += '<div class="arrow-box" id="' + key + '"><span class="arrow-up glyphicon glyphicon-triangle-top" aria-hidden="true"></span><span class="arrow-down glyphicon glyphicon-triangle-bottom" aria-hidden="true"></span></div>';
|
|
|
|
if( aclist[key].span!=null ) {
|
|
contents = '<span title="' + aclist[key].span + '">' + contents;
|
|
}
|
|
|
|
cell.innerHTML=contents;
|
|
cell.id = key;
|
|
cell.align = aclist[key].align;
|
|
cell.style.display = aclist[key].visible ? 'table-cell' : 'none';
|
|
}
|
|
|
|
// create table body and insert invisible row to clone
|
|
// https://stackoverflow.com/questions/6481948/how-do-i-create-the-tbody-tag-in-a-table-with-pure-javascript
|
|
aircraftlist_body = table.appendChild(document.createElement('tbody'));
|
|
aircraftlist_row2clone = aircraftlist_body.insertRow(-1);
|
|
|
|
for (var key in aclist) {
|
|
var cell = aircraftlist_row2clone.insertCell(-1);
|
|
cell.id = key;
|
|
cell.style.display = 'none';
|
|
cell.align = aclist[key].align;
|
|
}
|
|
}
|
|
|
|
|
|
function getCategoryLongtext(cat) {
|
|
var retval = cat;
|
|
if (category[cat])
|
|
retval = category[cat];
|
|
return retval;
|
|
}
|
|
|
|
|
|
function gda_longtext (gda) {
|
|
switch (gda.toUpperCase())
|
|
{
|
|
case 'A': return "Air";
|
|
case 'G': return "Ground";
|
|
case '?': return "Unk.";
|
|
default: return '?'+gda+'?';
|
|
}
|
|
}
|
|
|
|
|
|
function src_longtext (src) {
|
|
switch (src.toUpperCase())
|
|
{
|
|
case 'A': return "ADS-B";
|
|
case 'M': return "MLAT";
|
|
case 'L': return "FLARM";
|
|
case 'O': return "OGN";
|
|
case 'F': return "FlAwa";
|
|
case '?': return null;
|
|
case null: return null;
|
|
default: return '?'+src+'?';
|
|
}
|
|
}
|
|
|
|
|
|
function get_date_time_string(unix_timestamp, nanoseconds) {
|
|
// Create a new JavaScript Date object based on the timestamp
|
|
// multiplied by 1000 so that the argument is in milliseconds, not seconds.
|
|
var date = new Date(unix_timestamp * 1000);
|
|
// Hours part from the timestamp
|
|
var hours = date.getHours();
|
|
var minutes = "0" + date.getMinutes();
|
|
var seconds = "0" + date.getSeconds();
|
|
|
|
var formattedTime = date.toISOString();
|
|
formattedTime = formattedTime.replace("T", " ");
|
|
var ns = "000000000" + nanoseconds;
|
|
formattedTime = formattedTime.substr(0, 20) + ns.substr(-9); // formattedTime.substr(20)
|
|
|
|
return formattedTime;
|
|
}
|
|
|
|
|
|
function updateStatistics() {
|
|
getJSON(rcd_globals.fcgipath+"mapinfo.json", function(stat_data) {
|
|
refresh_time = ( typeof stat_data["refreshaclist"]!=='undefined' ) ? stat_data["refreshaclist"] : refresh_time;
|
|
var table = document.getElementById("statistic").tBodies[0];
|
|
var cells = table.rows[0].cells;
|
|
var txt = "";
|
|
txt += '<b>Total Aircraft:</b> ' + stat_data["dbsize"];
|
|
txt += ' ADS-B: ' + stat_data["num_adsb"];
|
|
txt += ' MLAT: ' + stat_data["num_mlat"];
|
|
txt += ' Flarm: ' + stat_data["num_flarm"];
|
|
txt += ' <b>Active:</b> ' + stat_data["watched"];
|
|
txt += ' ADS-B: ' + stat_data["num_active_adsb"];
|
|
txt += ' MLAT: ' + stat_data["num_active_mlat"];
|
|
txt += ' Flarm: ' + stat_data["num_active_flarm"];
|
|
cells.namedItem("statistic_text").innerHTML = txt;
|
|
});
|
|
}
|
|
|
|
|
|
function updateTable() {
|
|
getJSON(rcd_globals.fcgipath+"aircraftlist.json", function(unsorted_data) {
|
|
for ( var i = 0; i < unsorted_data.length; i++) {
|
|
unsorted_data[i].tim = get_date_time_string(unsorted_data[i].uti, unsorted_data[i].ns);
|
|
unsorted_data[i].orgh = resolveAirportById(unsorted_data[i].org).label;
|
|
unsorted_data[i].dsth = resolveAirportById(unsorted_data[i].dst).label;
|
|
unsorted_data[i].src = src_longtext(unsorted_data[i].src);
|
|
var optional_field;
|
|
for( optional_field of ['altg','spi','alr','ape','tcm','ias','tas','hdgm','hdgt','qnhs','alts','hdgs','altg','pic','mop'] ) {
|
|
if( unsorted_data[i][optional_field]==null ) { unsorted_data[i][optional_field] = null; }
|
|
}
|
|
}
|
|
|
|
var data = unsorted_data.sort(function(a, b) {
|
|
var x = a[sort.key];
|
|
var y = b[sort.key];
|
|
if( x==y ) {
|
|
return 0;
|
|
}
|
|
if( x==null || x==="" ) {
|
|
return sort.empty_on_top==true ? -1 : 1;
|
|
}
|
|
if( y==null || y==="" ) {
|
|
return sort.empty_on_top==false ? -1 : 1;
|
|
}
|
|
if( sort.asc ) {
|
|
return (x < y) ? -1 : 1;
|
|
} else {
|
|
return (x > y) ? -1 : 1;
|
|
}
|
|
});
|
|
|
|
var rows_len = aircraftlist_body.rows.length - 1; // first is row to clone
|
|
while( rows_len>data.length ) {
|
|
aircraftlist_body.removeChild(aircraftlist_body.rows[rows_len--]);
|
|
}
|
|
|
|
for ( var i=0; i<data.length; ++i) {
|
|
|
|
if (i >= rows_len) {
|
|
var line = aircraftlist_body.rows[0].cloneNode(true);
|
|
line.style = ""; // remove style="display:none;"
|
|
aircraftlist_body.appendChild(line);
|
|
} else {
|
|
var line = aircraftlist_body.rows[1 + i];
|
|
}
|
|
|
|
var cells = line.cells;
|
|
|
|
// manipulate registration: link to planespotters
|
|
if( data[i].reg==null ) {
|
|
data[i].reg="";
|
|
}
|
|
else if( data[i].reg.includes("state") ) {
|
|
}
|
|
else if( data[i].reg!="" ) {
|
|
data[i].reg = "<a href=\"https://planespotters.net/search?q=" + data[i].reg + "\" target=\"_blank\">" + data[i].reg + "</a>";
|
|
}
|
|
|
|
// manipulate lat and lon to links into internal maps
|
|
if (data[i].lat == null || data[i].lon == null) {
|
|
data[i].lat = "";
|
|
data[i].lon = "";
|
|
} else {
|
|
// fragment identifier / anchor
|
|
data[i].lat = "<a href=\"map.html#icao=" + data[i].hex + "\">" + data[i].lat.toFixed(5) + "</a>";
|
|
data[i].lon = "<a href=\"gmap.html#icao=" + data[i].hex + "\">" + data[i].lon.toFixed(5) + "</a>";
|
|
}
|
|
|
|
if( data[i].org ) { data[i].org = '<span title="' + resolveAirportById(data[i].org).text + '">' + data[i].org; }
|
|
if( data[i].dst ) { data[i].dst = '<span title="' + resolveAirportById(data[i].dst).text + '">' + data[i].dst; }
|
|
|
|
// manipulate Ground/Air, categorie and data source, distance
|
|
data[i].gda = data[i].gda==null ? "" : gda_longtext(data[i].gda);
|
|
data[i].cat = data[i].cat==null ? "" : '<span title="' + getCategoryLongtext(data[i].cat) + '">' + data[i].cat + '</span>';
|
|
data[i].dis = data[i].dis==null ? "" : data[i].dis.toFixed(1);
|
|
|
|
['gda','cat','src','reg','fli','hex','org','orgh','dst','dsth','trk','hdgm','hdgt','alt','altg','spd','ias','vrt','typ','opr','cou','squ','spi','alr','ape','tcm','lat','lon','tas','qnhs','alts','hdgs','dbm','tmp','tim','tru','mop','dis','wsp','wdi','pic'].forEach(
|
|
function(key) {
|
|
if( aclist[key].visible ) {
|
|
cells.namedItem(key).style.display = 'table-cell';
|
|
cells.namedItem(key).innerHTML = data[i][key];
|
|
} else {
|
|
cells.namedItem(key).style.display = 'none';
|
|
}
|
|
}
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
function FilterControl(controlDiv) {
|
|
var controlUI = document.createElement('div');
|
|
controlUI.className = 'colselect-icon';
|
|
controlUI.title = 'Click to modify the column select';
|
|
controlDiv.appendChild(controlUI);
|
|
|
|
// create a string that contains all IDs of the checkboxes
|
|
var all_checkbox_ids = ''
|
|
var first = true;
|
|
for (var key in aclist) {
|
|
all_checkbox_ids += (first? '' : ', ') + '#enable_' + key;
|
|
first = false;
|
|
}
|
|
all_checkbox_ids += ', #empty_on_top, #empty_on_bottom';
|
|
|
|
controlUI.addEventListener( 'click', function() {
|
|
if (document.getElementById('colselect-dialog') === null) {
|
|
var colselectDialog = document.createElement('div');
|
|
colselectDialog.id = 'colselect-dialog';
|
|
|
|
function update_checkboxes() {
|
|
for (var key in aclist) {
|
|
var input_field_id = '#enable_' + key;
|
|
$(input_field_id).prop('checked', aclist[key].visible);
|
|
}
|
|
}
|
|
|
|
// create a table which contains checkboxes for all keys
|
|
var select_table = '<div class="close" title="Close"></div>'
|
|
+ '<table>'
|
|
+ '<tr><td><input type="radio" name="modify_all" id="clear_all" checked></td><td><b>Clear All</b></td></tr>'
|
|
+ '<tr><td><input type="radio" name="modify_all" id="set_all"></td><td><b>Select All</b></td></tr>';
|
|
for (var key in aclist) {
|
|
var non_breaked_title = aclist[key].title.replace("<br/>", " ");
|
|
select_table += '<tr><td><input type="checkbox" id="enable_' + key + '"' + '></td><td>' + non_breaked_title + "</td></tr>";
|
|
}
|
|
select_table +='<tr><td><input type="radio" name="sort_empty" id="empty_on_top" checked></td><td><b>Sort empty fields to top</b></td></tr>'
|
|
+ '<tr><td><input type="radio" name="sort_empty" id="empty_on_bottom"></td><td><b>Sort empty fields to bottom</b></td></tr>'
|
|
+ '<tr><td></td><td><button id="colselect-apply">Apply & Close</button></td></tr>'
|
|
+ '</table>';
|
|
|
|
colselectDialog.innerHTML = select_table;
|
|
controlDiv.appendChild(colselectDialog);
|
|
update_checkboxes();
|
|
|
|
function handleChange() {
|
|
for (var key in aclist) {
|
|
var input_field_id = "#enable_" + key;
|
|
aclist[key].visible = $(input_field_id).prop("checked") || false;
|
|
}
|
|
|
|
sort.empty_on_top = $("#empty_on_top").prop("checked") || false;
|
|
setCookie( cookie_sort, JSON.stringify(sort), 9999 );
|
|
|
|
colselect = {};
|
|
for (var key in aclist) { colselect[key] = aclist[key].visible; }
|
|
setCookie(cookie_colselect, JSON.stringify(colselect), 9999);
|
|
aircraftlistSetVisibleColumns();
|
|
}
|
|
|
|
$(all_checkbox_ids).change(handleChange);
|
|
|
|
function setAll() {
|
|
for (var key in aclist) { aclist[key].visible = true; }
|
|
update_checkboxes();
|
|
}
|
|
|
|
function clearAll() {
|
|
for (var key in aclist) { aclist[key].visible = false; }
|
|
update_checkboxes();
|
|
}
|
|
|
|
$("#set_all").change(setAll);
|
|
$("#clear_all").change(clearAll);
|
|
|
|
$("#colselect-dialog .close").click(function() {
|
|
handleChange();
|
|
$("#colselect-dialog").hide();
|
|
});
|
|
|
|
$("#colselect-dialog .clear").click(function() {
|
|
$(all_checkbox_ids).val(true);
|
|
handleChange();
|
|
});
|
|
|
|
$("#colselect-apply").click(function() {
|
|
handleChange();
|
|
$("#colselect-dialog").hide();
|
|
});
|
|
} else {
|
|
$("#colselect-dialog").show();
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
$(document).ready(function() {
|
|
|
|
console.log("dynamic_aircraftlist.js-->document ready");
|
|
|
|
// read cookies
|
|
// ------------
|
|
var tmp = [];
|
|
var cookie = getCookie(cookie_sort);
|
|
if( cookie ) {
|
|
$.extend( tmp, JSON.parse(cookie) );
|
|
}
|
|
for (var key in tmp) {
|
|
if( key in sort ) {
|
|
sort[key] = tmp[key];
|
|
}
|
|
}
|
|
|
|
tmp = [];
|
|
var cookie = getCookie(cookie_colselect);
|
|
if( cookie ) {
|
|
$.extend( tmp, JSON.parse(cookie) );
|
|
}
|
|
for (var key in tmp) {
|
|
if( key in aclist ) {
|
|
aclist[key].visible = tmp[key];
|
|
}
|
|
}
|
|
|
|
loadAirports();
|
|
createAircraftlist();
|
|
|
|
|
|
// create filter box
|
|
// -----------------
|
|
// https://stackoverflow.com/questions/18266231/display-a-small-image-at-the-top-right-corner
|
|
var filterControlDiv = document.getElementById("colselect-button");
|
|
var filterControl = new FilterControl(filterControlDiv);
|
|
|
|
|
|
// create event listeners
|
|
// ----------------------
|
|
$(".arrow-box .arrow-down").click(function(evt) {
|
|
sort.key = $(this).parent().attr("id");
|
|
sort.asc = false;
|
|
updateTable();
|
|
setCookie( cookie_sort, JSON.stringify(sort), 9999 );
|
|
});
|
|
|
|
$(".arrow-box .arrow-up").click(function(evt) {
|
|
sort.key = $(this).parent().attr("id");
|
|
sort.asc = true;
|
|
updateTable();
|
|
setCookie( cookie_sort, JSON.stringify(sort), 9999 );
|
|
});
|
|
|
|
$('body').on('click', '.hasicon > .icon', function() {
|
|
// console.log("Icon Clicked");
|
|
});
|
|
|
|
// execute
|
|
// -------
|
|
|
|
function loop() {
|
|
if( rcd_globals.initialized ) {
|
|
updateTable();
|
|
updateStatistics();
|
|
}
|
|
//else
|
|
// console.log(rcd_globals);
|
|
var delay = Math.max(1000, refresh_time*1000);
|
|
setTimeout( loop, delay );
|
|
}
|
|
|
|
loop();
|
|
});
|