/********************************************************************
* 2D Map based on Google Maps
********************************************************************
* Guenter Koellner Embedded Development GmbH
*
* Am Rain 24
* 85256 Vierkirchen
* Geschaeftsfuehrer Guenter Koellner
*
********************************************************************/
var Gmap=null;
var Planes={};
var Sensors={};
var flightPath={};
var flightPath2={};
var HighlightSensors = null;
var SingleAircraft = null;
var SingleSensor = null;
var Filters = {
enable_alt:false, altitude_min:null,altitude_max:null,
enable_gnd : true,
enable_spd:false, speed_min :null,speed_max:null,
enable_dis:false, distance_min:null,distance_max:null,
reg_enable:false, reg_filter :null,
sensors_enable:false,sensors_filter:null,
typ_enable: false,typ_filter: null,
fli_enable : false,
fli_filter : null,
dest_enable : false,
dest_filter : null,
orig_enable : false,
orig_filter : null,
squawk_enable : false,
squawk_filter : null,
prio_enable : false,
prio_filter : null,
enable_adsb : true,
enable_mlat : true,
enable_modeac : true,
enable_flawa : true,
enable_ogn : true,
enable_flarm : true,
pref_source : 'auto'
};
var prio_filter_splitted = [];
var GmapInit = {
Zoomlevel : 8,
CenterLat : 48.353013,
CenterLon : 11.785958,
LatLonSrc : 'GPS',
DistanceUnit : 'km',
enable_night : true,
mapstyle : 'terrain',
refreshtime : 5,
tracklength : 300,
enable_atrack: true,
enable_mtrack: true,
show_sensors : true
};
var Selected=[];
var PreSelected=undefined;
var NauticalMiles_to_Meters=1852;
var KiloMeters_to_Meters=1000;
//var map_container;
var map_box;
var map_info_box;
var flight_info_box_adsb;
var flight_info_box_modeac;
var flight_info_box_klick;
var hide_info;
var hide_info_btn;
// https://stackoverflow.com/questions/26246601/wildcard-string-comparison-in-javascript
// Short code
function matchRuleShort(str, rule) {
var escapeRegex = (str) => str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
return new RegExp("^" + rule.split("*").map(escapeRegex).join(".*") + "$").test(str);
}
function getTimeString(isLocal) {
var hours;
var minutes;
var date = new Date();
var zone_offset = date.getTimezoneOffset();
if (isLocal) {
hours = date.getHours();
minutes = date.getMinutes();
seconds = date.getSeconds();
} else {
hours = date.getUTCHours();
minutes = date.getUTCMinutes();
seconds = date.getUTCSeconds();
}
if (hours < 10) { hours = '0' + hours; }
if (minutes < 10) { minutes = '0' + minutes; }
if (seconds < 10) { seconds = '0' + seconds; }
var zeit_string = hours + ':' + minutes + ':' + seconds;
return zeit_string;
}
function getStrokeColor(alt, isGround) {
const colorMap = [
[ 0,"ffffff"],
[ 100,"ffe062"],
[ 200,"ffea00"],
[ 300,"f0ff00"],
[ 400,"ccff00"],
[ 600,"42ff00"],
[ 800,"1eff00"],
[ 1000,"00ff0c"],
[ 1200,"00ff36"],
[ 1500,"00ff72"],
[ 2000,"00ff9c"],
[ 2500,"00ffd2"],
[ 3000,"00ffe4"],
[ 3500,"00eaff"],
[ 4000,"00c0ff"],
[ 4500,"00a8ff"],
[ 5000,"0096ff"],
[ 5500,"0078ff"],
[ 6000,"0054ff"],
[ 6500,"0030ff"],
[ 7000,"001eff"],
[ 7500,"0000ff"],
[ 8500,"1200ff"],
[ 8500,"2400ff"],
[ 9000,"3600ff"],
[ 9500,"4e00ff"],
[10000,"6000ff"],
[10500,"7800ff"],
[11000,"9600ff"],
[11500,"ae00ff"],
[12000,"d800ff"],
[12500,"ff00e4"],
[13000,"ff0000"]
];
if( isGround ) { return '8B4513'; }
var color;
for (var i = 0; i < colorMap.length; i++) {
if (alt > colorMap[i][0]) {
color = colorMap[i][1];
} else {
break;
}
}
return color;
}
function getIconForPlane(plane)
{
var Border = plane.src == 'M' ? 'Red'
: plane.src == 'F' ? 'LimeGreen'
: plane.src == 'L' ? 'DeepPink'
: plane.src == 'O' ? 'HotPink'
: plane.src == 'AC' ? 'Blue'
: 'Black';
var Fill = plane.vrt > 300 ? 'rgb(53, 235, 244)' //'DeepSkyBlue'
: plane.vrt < -300 ? 'rgb(185, 92, 0)' //'Sienna'
: 'rgb(255,255,0)'; //'Yellow';
var maxalt = 40000; /* Max altitude in the average case */
var invalt = maxalt-plane.alt;
if( plane.data_src=='AC' ) {
var imageObj = get_modeac_image_object();
} else {
var imageObj = get_image_object(plane.cat, plane.typ);
}
var object_box = Raphael.pathBBox(imageObj.path);
var offset_x = (object_box.x + object_box.x2) / 2;
var offset_y = (object_box.y + object_box.y2) / 2;
var strokew = Selected.includes(plane.hex) ? 2 : 1;
var Zindex = Selected.includes(plane.hex) ? 5 : 1;
var myZoom = Gmap.getZoom();
var Scale = myZoom>=12 ? imageObj.scale * 1.45
: myZoom==11 ? imageObj.scale * 1.3
: myZoom==10 ? imageObj.scale * 1.15
: myZoom== 9 ? imageObj.scale * 1.0
: myZoom== 8 ? imageObj.scale * 0.85
: myZoom== 7 ? imageObj.scale * 0.7
: myZoom== 6 ? imageObj.scale * 0.55
: imageObj.scale * 0.4
var ac_symbol = {
strokeColor : Border,
strokeWeight: strokew,
path : imageObj.path,
scale : Scale,
fillColor : Fill,
fillOpacity : 0.9,
anchor : new google.maps.Point(offset_x, offset_y),
rotation : (plane.trk + imageObj.rotcorr) % 360,
zIndex : Zindex
};
return ac_symbol;
}
function showInfoBox() {
hide_info.style.visibility = 'visible';
map_info_box.style.width = '210px';
hide_info_btn.setAttribute("class", "glyphicon glyphicon-minus");
}
function hideInfoBox() {
hide_info.style.visibility = 'hidden';
map_info_box.style.width = '1px';
hide_info_btn.setAttribute("class", "glyphicon glyphicon-plus");
}
function toggleInfoBox() {
if (hide_info.style.visibility == 'hidden')
showInfoBox();
else
hideInfoBox();
}
/********************************************************************************
* clicking on a sensor selects only this one
*******************************************************************************/
function selectSensor() {
//console.log("selectSensor="+this.sensorid);
SingleSensor = this.sensorid;
fetchAllData();
}
/********************************************************************************
* clicking on a plane selects display of details in sidebar
*******************************************************************************/
function selectPlane() {
var hex = this.hex;
//console.log('selectPlane hex='+hex+' event.altKey='+event.altKey+' event.ctrlKey='+event.ctrlKey);
//console.log('Selected='+Selected);
if( !Planes[hex] ) { console.log ('not in Planes'); return; }
if( Selected && Selected.includes(hex) ) { Selected.splice(Selected.indexOf(hex), 1); }
if( Selected===null) Selected = [hex];
else Selected.push(hex);
Planes[hex].marker1.setIcon(getIconForPlane(Planes[hex]));
labelSetStyle(Planes[hex], Planes[hex].marker1);
if (event.shiftKey) {
SingleAircraft = (SingleAircraft==hex) ? null : hex;
}
refreshSelectedInfo();
fetchAllData();
showInfoBox();
}
function rightSelectPlane() {
var hex = this.hex;
//console.log('rightSelectPlane hex='+hex+' event.altKey='+event.altKey+' event.ctrlKey='+event.ctrlKey);
if( Selected && !Selected.includes(hex) ) { console.log ('not in Selected'); return; }
deselectPlane(hex);
}
function preselectPlane(hex) {
//console.log('preselectPlane src=ADSB id='+hex);
this.src='ADSB';
this.id=hex;
selectPlane();
}
function updateURLPlaneSelection() {
var hashObj = {};
location.hash.replace('#', '').split('&').forEach(function(item) {
hashObj[item.split('=')[0]] = item.split('=')[1];
});
PreSelected = { 'src':'ADSB', 'hex':hashObj.icao };
}
function deselectPlaneAll() {
var old = Selected;
Selected = [];
old.forEach( function(hex) { deselectPlane(hex); });
SingleAircraft = null;
SingleSensor = null;
fetchAllData();
}
function deselectPlane(hex)
{
Selected.splice(Selected.indexOf(hex), 1);
/* Remove the highlight in the previously selected plane. */
if( Planes[hex] ) {
Planes[hex].marker1.setIcon(getIconForPlane(Planes[hex]));
labelSetStyle(Planes[hex], Planes[hex].marker1);
}
if( flightPath[hex] ) {
flightPath[hex].forEach ( function( X ) { X.setMap(null); });
delete flightPath[hex];
}
if( flightPath2[hex] ) {
flightPath2[hex].forEach ( function( X ) { X.setMap(null); });
delete flightPath2[hex];
}
if( !Selected.length ) { // Selected is empty now, remove sidebar
flight_info_box_adsb.style.display='none';
flight_info_box_modeac.style.display='none';
flight_info_box_klick.style.display='';
}
}
/*******************************************************************************
* Info box in div map_info_box - left side, always visible
******************************************************************************/
function fetchMapInfo() {
getJSON(rcd_globals.fcgipath+'mapinfo.json', function(mapinfo) {
// equal, but not better: $('#TimeStringUTC')[0].innerHTML = ''+getTimeString(false)+'';
document.getElementById('TimeStringUTC' ).innerHTML = ''+getTimeString(false)+'';
document.getElementById('TimeStringLocal' ).innerHTML = ''+getTimeString(true )+'';
document.getElementById('mapinfo_dbsize' ).innerHTML = ''+mapinfo.dbsize +'';
document.getElementById('mapinfo_watched' ).innerHTML = ''+mapinfo.watched +'';
document.getElementById('mapinfo_num_adsb' ).innerHTML = ''+mapinfo.num_adsb +'';
document.getElementById('mapinfo_num_mlat' ).innerHTML = ''+mapinfo.num_mlat +'';
document.getElementById('mapinfo_num_flarm').innerHTML = ''+mapinfo.num_flarm+'';
});
}
function isAltitude ( code )
{
if( typeof(code)!='string' || code.length!=4 ) return false;
var d = parseInt(code.substring(3,4));
var c = code.substring(2,3);
var ret = !( d&1 || c=='0' || c=='5' || c=='7' )
return ret;
}
function decodeModeACAltitude ( code )
{
if( typeof(code)!='string' || code.length!=4 ) return false;
var V0 = parseInt(code.substr(0,2),16);
var V1 = parseInt(code.substr(2,4),16);
var altitude = ((V1 << 9) & 0x400) | // D2 1 + 9 =10
((V1 << 7) & 0x200) | // D4 2 + 7 = 9
((V0 << 4) & 0x100) | // A1 4 + 4 = 8
((V0 << 2) & 0x080) | // A2 5 + 2 = 7
((V0 ) & 0x040) | // A4 6 + 0 = 6
((V0 << 5) & 0x020) | // B1 0 + 5 = 5
((V0 << 3) & 0x010) | // B2 1 + 3 = 4
((V0 << 1) & 0x008) | // B4 2 + 1 = 3
((V1 >> 2) & 0x004) | // C1 4 - 2 = 2
((V1 >> 4) & 0x002) | // C2 5 - 4 = 1
((V1 >> 6) & 0x001); // C4 6 - 6 = 0
altitude ^= (altitude >> 8);
altitude ^= (altitude >> 4);
altitude ^= (altitude >> 2);
altitude ^= (altitude >> 1);
altitude -= ((altitude >> 4) * 6) + (((altitude % 16) / 5) * 2);
altitude = (altitude - 13) * 100;
ret = Math.round(altitude);
return ret;
}
/********************************************************************************
* Additional info about plane, visible in left sidebar if a plane is selected
*******************************************************************************/
function refreshSelectedInfo() {
var p = Planes[Selected.slice(-1)[0]];
if ( p ) {
if( p.data_src=='AC' )
{
flight_info_box_adsb.style.display = 'none';
flight_info_box_modeac.style.display = '';
flight_info_box_klick.style.display = 'none';
document.getElementById('fib_ac_code' ).innerHTML = p.ac + ' ';
document.getElementById('fib_ac_decoded').innerHTML = (isAltitude(p.ac) ? decodeModeACAltitude(p.ac)+'ft' : 'Squawk');
p.achistory.sort(function(a, b) {
if( a[2]==b[2] ) return 0;
return (a[2] < b[2]) ? -1 : 1;
});
var nof_ac_codes = p.achistory.length;
var counts = 'Count
';
var codes = 'Mode-A/C
';
var alts = 'Alt [ft]
';
var llas = 'Age[s]
';
for ( var i=0; i';
alts += (isAltitude(p.achistory[i][1]) ? decodeModeACAltitude(p.achistory[i][1]) : '') + '
';
counts += p.achistory[i ][0] + '
';
llas += p.achistory[i ][2] + '
';
}
document.getElementById('fib_ac_history_c1').innerHTML = counts;
document.getElementById('fib_ac_history_c2').innerHTML = codes;
document.getElementById('fib_ac_history_c3').innerHTML = alts;
document.getElementById('fib_ac_history_c4').innerHTML = llas;
document.getElementById('fib_ac_trk').innerHTML = p.trk + ' °';
document.getElementById('fib_ac_spd').innerHTML = (p.spd) ? (p.spd + ' kt' ) : ' ';
document.getElementById('fib_ac_alt').innerHTML = (p.alt) ? (p.alt + ' ft') : ' ';
document.getElementById('fib_ac_vrt').innerHTML = (p.vrt) ? (p.vrt + ' ft/min') : ' ';
document.getElementById('fib_ac_lat').innerHTML = p.lat + ' °';
document.getElementById('fib_ac_lon').innerHTML = p.lon + ' °';
document.getElementById('fib_ac_dis').innerHTML = ' ';
document.getElementById('fib_ac_src').innerHTML = 'Mode-A/C';
document.getElementById('fib_ac_lla' ).innerHTML = p.lla + ' sec';
document.getElementById('fib_ac_pest').innerHTML = p.pest + ' ';
}
else
{
flight_info_box_adsb.style.display = '';
flight_info_box_modeac.style.display = 'none';
flight_info_box_klick.style.display = 'none';
document.getElementById('fib_reg').innerHTML =
( p.reg && p.reg!= '' && !p.reg.includes('state') )
? '' + p.reg + ''
: p.reg + ' ';
document.getElementById('fib_log').innerHTML = ' Network Info'
document.getElementById('fib_hex').innerHTML = p.hex + ' ';
document.getElementById('fib_typ').innerHTML = (p.typ) ? (p.typ + ' ') : ' ';
document.getElementById('fib_cat').innerHTML = (p.cat) ? (p.cat + ' ') : ' ';
document.getElementById('fib_fli').innerHTML = (p.fli) ? (p.fli + ' ') : ' ';
document.getElementById('fib_squ').innerHTML = (p.squ) ? (p.squ + ' ') : ' ';
/* Flight Details Row 2 */
document.getElementById('fib_org').innerHTML =
(!p.org)
? 'n/a'
: '' + p.org + '
' + resolveAirportById(p.org).label + ' ';
document.getElementById('fib_dst').innerHTML =
(!p.dst)
? 'n/a'
: '' + p.dst + '
' + resolveAirportById(p.dst).label + ' ';
document.getElementById('fib_trk').innerHTML = p.trk + ' °';
document.getElementById('fib_spd').innerHTML = (p.spd) ? (p.spd + ' kt' ) : ' ';
document.getElementById('fib_alt').innerHTML = (p.alt) ? (p.alt + ' ft') : ' ';
document.getElementById('fib_vrt').innerHTML = (p.vrt) ? (p.vrt + ' ft/min') : ' ';
document.getElementById('fib_lat').innerHTML = p.lat + ' °';
document.getElementById('fib_lon').innerHTML = p.lon + ' °';
document.getElementById('fib_dis').innerHTML = p.dis.toFixed(1) + ' ' + DistanceUnit;
document.getElementById('fib_src').innerHTML =
(p.src=='A') ? 'ADS-B'
: (p.src=='M') ? 'MLAT'
: (p.src=='F') ? 'FlAwa'
: (p.src=='O') ? 'OGN'
: (p.src=='L') ? 'Flarm'
: ' ';
document.getElementById('fib_lla' ).innerHTML = p.lla + ' sec';
document.getElementById('fib_pest').innerHTML = p.pest + ' ';
document.getElementById('fib_nof_clnt').innerHTML = 'Current Sensors: ' + p.nocl;
if( rcd_globals.productname=='MLAT_SERVER' ) {
var i = 0;
var clnt = ['','','','',''];
p.clnt.sort().forEach(function(C){ clnt[i%5] += C + '
'; i++; });
document.getElementById('fib_clnt0').innerHTML = clnt[0] + ' ';
document.getElementById('fib_clnt1').innerHTML = clnt[1] + ' ';
document.getElementById('fib_clnt2').innerHTML = clnt[2] + ' ';
document.getElementById('fib_clnt3').innerHTML = clnt[3] + ' ';
document.getElementById('fib_clnt4').innerHTML = clnt[4] + ' ';
}
}
}
else
{
flight_info_box_adsb.style.display = 'none';
flight_info_box_modeac.style.display = 'none';
flight_info_box_klick.style.display = '';
}
}
/********************************************************************************
* Information that is displayed on map together with plane
*******************************************************************************/
function labelDataID(plane) {
if( plane.fli!==null && plane.fli!=="") return plane.fli;
if( plane.reg!==null && plane.reg!=="" && plane.reg!=="(state)" ) return plane.reg;
return plane.hex;
}
function labelDataVerticalRate(plane) {
// Vertical Rate
if (!plane.vrt ) return ' '
else if (plane.vrt < -300) return Math.round(plane.vrt/100);
else if (plane.vrt > 300) return "+" + Math.round(plane.vrt/100);
return '\u00B10'; // unicode plusminus + '0'
}
function labelDataAltitude(plane) {
if (plane.gda == "G") { return "GND"; }
// Altitude
var s = Math.round(plane.alt/100);
if (s < 0) {
s = '-0' + -1*s;
} else {
if (s < 10) s = "00" + s;
else
if (s < 100) s = "0" + s;
}
return s;
}
function labelDataSpeed(plane) {
return Math.round(plane.spd/10);
}
function permanentLabelTable(plane) {
var ret;
if( plane.data_src=='AC' ) {
ret = '
' +
'| ' + plane.ac + ' | ' + plane.uid + ' |
' +
'| ' + labelDataAltitude(plane) + ' | ' + labelDataVerticalRate(plane) + ' | ' + labelDataSpeed(plane) + ' |
' +
'| ' + plane.tsz + ' | ' + plane.lla + ' | ' + plane.pest + ' |
' +
'
';
} else {
ret = '' +
'| ' + labelDataID(plane) + ' | ' + (plane.reg==null?'':plane.reg) + ' |
' +
'| ' + labelDataAltitude(plane) + ' | ' + labelDataVerticalRate(plane) + ' | ' + labelDataSpeed(plane) + ' |
' +
//'| ' + plane.clnt.length + ' | ' + plane.squ + ' | ' + plane.pest + ' |
' +
'
';
}
return ret;
}
function getPopupForPlane(plane) {
if( plane.data_src=='AC' )
{
var ret = '';
plane.achistory.forEach ( function (E) { ret += E[0] + ':' + E[1] + ' '; });
return ret;
}
if( plane.fli.length == 0 ) {
return plane.hex;
} else {
var title = plane.hex + " " + plane.typ;
if ( plane.org!=null && plane.org.length ) { title += '\n' + plane.org; }
if ( plane.dst!=null && plane.dst.length ) { title += '->' + plane.dst; }
if ( plane.squ!=null && plane.squ.length ) { title += '\nSQ:' + plane.squ; }
return title;
}
}
// function labelSetAnchor(plane, marker, offset) {
// if (plane.trk > 315) { marker.set("labelAnchor", new google.maps.Point( 66, -1 + offset)); return; }
// if (plane.trk < 45) { marker.set("labelAnchor", new google.maps.Point( 66, -1 + offset)); return; }
// if (plane.trk < 135) { marker.set("labelAnchor", new google.maps.Point( 37, -1 + 28 + offset)); return; }
// if (plane.trk < 225) { marker.set("labelAnchor", new google.maps.Point(-17, -1 + offset)); return; }
// marker.set("labelAnchor", new google.maps.Point(37, -1 - 28 + offset));
// }
function isInPrio(item) {
var ret=false;
prio_filter_splitted.forEach(function(mask) {
if ( matchRuleShort(item, mask) ) { ret=true; return; };
});
return ret;
}
// https://www.quackit.com/html/codes/color/html_color_chart.cfm
function labelSetStyle(plane, marker) {
if ( Selected.includes(plane.hex) ) {marker.set('labelStyle', { opacity: 0.5, 'background-color': 'greenyellow'}); return; }
if ( plane.squ && plane.squ=="7700") {marker.set('labelStyle', { opacity: 0.5, 'background-color': 'red'}); return; }
if ( plane.spi ) {marker.set('labelStyle', { opacity: 0.5, 'background-color': 'aqua', border: '2px solid darkblue'}); return; }
if ( plane.alr && plane.alr!=0 ) {marker.set('labelStyle', { opacity: 0.5, 'background-color': 'gold', border: '2px solid black' }); return; }
if ( Filters.prio_enable===true ) {
if ( isInPrio(plane.hex) || isInPrio(plane.reg) ) {marker.set('labelStyle', { opacity: 0.8, 'background-color': 'yellow'}); return; }
}
marker.set('labelStyle', { opacity: 0.5, 'background-color': 'gold'});
}
// Shapes: https://developers.google.com/maps/documentation/javascript/shapes
// https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/overlay-symbol-dashed?hl=vi
function drawColoredTrack (data)
{
data.forEach( function(path) {
var hex = path.src=='AC' ? 'AC_'+path.uid : path.icao;
if( !Selected.includes(hex) ) return; // no longer selected
if( flightPath[hex] ) flightPath[hex].forEach( function ( X ) { X.setMap(null); });
flightPath[hex] = [];
if( path.fpth.length<2 ) return;
var trackpoints = [];
var veryFirst = true;
var strokeColor_tm1;
path.fpth.forEach( function (trackpoint, index) {
var strokeColor = getStrokeColor(trackpoint.alt, trackpoint.gnd ? trackpoint.gnd : false ); // AC has no gnd field
var g_tp = { 'lat':trackpoint.lat, 'lng':trackpoint.lon };
trackpoints.push( g_tp );
if( veryFirst )
{
strokeColor_tm1=strokeColor;
veryFirst=false;
}
else
{
if( strokeColor!=strokeColor_tm1 || index==path.fpth.length-1 )
{
var pathSegment = new google.maps.Polyline({
path: trackpoints,
geodesic: true,
strokeColor: '#'+strokeColor_tm1,
strokeOpacity: 1.0,
strokeWeight: 2,
zIndex : 1,
map: Gmap
});
flightPath[hex].push(pathSegment);
trackpoints = [ g_tp ];
strokeColor_tm1 = strokeColor;
}
}
});
});
}
function drawDashedTrack (data)
{
// Define a symbol using SVG path notation, with an opacity of 1.
const lineSymbol = {
path: 'M 0,-1 0,1',
strokeOpacity: 1,
scale: 2
};
data.forEach( function(path) {
var hex = path.src=='AC' ? 'AC_'+path.uid : path.icao;
if( !Selected.includes(hex) ) return; // no longer selected
if( flightPath2[hex] ) flightPath2[hex].forEach( function ( X ) { X.setMap(null); });
flightPath2[hex] = [];
if( path.fpth.length<2 ) return;
var trackpoints = [];
var veryFirst = true;
path.fpth.forEach( function (trackpoint) {
g_tp = { 'lat':trackpoint.lat, 'lng':trackpoint.lon };
trackpoints.push( g_tp );
});
var pathSegment = new google.maps.Polyline({
path: trackpoints,
geodesic: true,
strokeColor: 'black',
strokeOpacity: 0,
icons: [{
icon: lineSymbol,
offset: '0',
repeat: '10px'
}],
zIndex : 10,
map: Gmap
});
flightPath2[hex].push(pathSegment);
});
}
function fetchTrackData()
{
if( Selected.length==0 ) { return; }
var vps = Gmap.getBounds().getSouthWest().lat().toFixed(5);
var vpw = Gmap.getBounds().getSouthWest().lng().toFixed(5);
var vpn = Gmap.getBounds().getNorthEast().lat().toFixed(5);
var vpe = Gmap.getBounds().getNorthEast().lng().toFixed(5);
var tracks_adsb = [];
var tracks_mlat = [];
var tracks_flarm = [];
var tracks_ogn = [];
var tracks_flawa = [];
var tracks_modeac = [];
// sort selected targets by source because flightpath must be called separately
Selected.forEach( function (hex) {
if( Planes[hex].data_src=='AC') { tracks_modeac.push(Planes[hex].uid); }
else if( Planes[hex].src=='A' ) { tracks_adsb.push(hex); }
else if( Planes[hex].src=='M' ) { tracks_mlat.push(hex); }
else if( Planes[hex].src=='L' ) { tracks_flarm.push(hex); }
else if( Planes[hex].src=='F' ) { tracks_flawa.push(hex); }
else if( Planes[hex].src=='O' ) { tracks_ogn.push(hex); }
else console.log('unknown data_src=' + Planes[hex].data_src + ' src=' + Planes[hex].src + ' for flightpath.json' )
});
// colored tracks
[tracks_adsb, tracks_mlat, tracks_flarm, tracks_ogn, tracks_flawa].forEach( function (tracks) {
if( tracks.length ) {
var link = rcd_globals.fcgipath
+ 'flightpath.json?icao=' + tracks.join(',')
+ '&srcpref=' + Planes[tracks[0]].src
+ ( GmapInit.tracklength ? ( '&lookback=' + GmapInit.tracklength ) : '' )
+ '&pixelxy=' + window.innerWidth + ',' + window.innerHeight
+ '&vpn=' + vpn + '&vpe=' + vpe + '&vps=' + vps + '&vpw=' + vpw;
getJSON(link, drawColoredTrack);
}
});
// dashed tracks
tracks_dashed = [];
if( GmapInit.enable_atrack ) { tracks_dashed.push({'src':'A', 'points':tracks_mlat}); }
if( GmapInit.enable_mtrack ) { tracks_dashed.push({'src':'M', 'points':tracks_adsb}); }
tracks_dashed.forEach( function (tracks) {
if( tracks['points'].length ) {
var link = rcd_globals.fcgipath
+ 'flightpath.json?icao=' + tracks['points'].join(',')
+ '&srcpref=' + tracks['src']
+ ( GmapInit.tracklength ? ( '&lookback=' + GmapInit.tracklength ) : '' )
+ '&pixelxy=' + window.innerWidth + ',' + window.innerHeight
+ '&vpn=' + vpn + '&vpe=' + vpe + '&vps=' + vps + '&vpw=' + vpw;
getJSON(link, drawDashedTrack);
}
});
// Mode-A/C tracks
if( tracks_modeac.length ) {
var link = rcd_globals.fcgipath
+ 'modeacpath.json?uid=' + tracks_modeac.join(',')
+ ( GmapInit.tracklength ? ( '&lookback=' + GmapInit.tracklength ) : '' )
+ '&pixelxy=' + window.innerWidth + ',' + window.innerHeight
+ '&vpn=' + vpn + '&vpe=' + vpe + '&vps=' + vps + '&vpw=' + vpw;
getJSON(link, drawColoredTrack);
}
}
// Wrappers because callback cannot have parameter
function updatePlanes (data) {
document.getElementById('num_planesOnMap').innerHTML = ''+data.length+'';
updateObjects ( data, 'ADSB' );
}
function updateModeAC (data) { updateObjects ( data, 'AC' ); }
function updateSensors (data) { updateObjects ( data, 'Sensor' ); }
/* Functionality of this display assumes that all 'hex' codes are unique by the provider */
/* Mode-S, ADS-B and MLAT sources use 0x00000000 - 0x00ffffff hex id */
/* Mode-A/C sources use 0xACxxxxxx identifiers */
/* Sensors should use 0x5Exxxxxx identifiers */
/* and 'hex' is only traditional... */
function updateObjects (data, data_src) {
var stillhere = [];
data.forEach ( function (plane) {
var marker1;
if( data_src=='AC' ) plane.hex = 'AC_'+plane.uid;
stillhere.push(plane.hex);
plane.data_src = data_src;
plane.fli = $.trim(plane.fli);
/* update */
if( Planes[plane.hex] ) {
marker1 = Planes[plane.hex].marker1;
var newpos = new google.maps.LatLng(plane.lat, plane.lon);
/* update flight number */
marker1.setIcon ( getIconForPlane(plane)) ;
marker1.setPosition( newpos );
marker1.set ('labelContent', permanentLabelTable(plane) );
labelSetStyle ( plane, marker1 );
marker1.label.draw();
if( Selected.includes(plane.hex) ) refreshSelectedInfo();
}
else
{
/* new flight */
marker1 = new MarkerWithLabel({
position : new google.maps.LatLng(plane.lat, plane.lon),
draggable : false,
raiseOnDrag : false,
zIndex : 70000,
map : Gmap,
labelContent : permanentLabelTable(plane),
labelAnchor : plane.data_src=='AC' ? new google.maps.Point(0, 32) : new google.maps.Point(0, 0),
labelClass : 'labels',
labelStyle : {},
labelZIndex : 60000,
icon : getIconForPlane(plane)
});
labelSetStyle(plane, marker1);
/* Trap clicks for this marker. */
marker1.addListener('click', selectPlane);
marker1.addListener('rightclick', rightSelectPlane);
}
marker1.hex = plane.hex; /* remember for click */
plane.marker1 = marker1;
Planes[plane.hex] = plane;
/* Popup window when mouse over aircraft icon */
marker1.setTitle( getPopupForPlane(plane) )
});
/* Remove no longer given planes. */
//console.log("Planes["+T+"]\n"+Planes[T]);
//console.log("stillhere\n"+stillhere);
for( var hex in Planes ) {
if( !stillhere.includes(hex) ) {
if( Planes[hex].data_src != data_src ) continue;
if( Selected.includes(hex) ) { deselectPlane(hex); }
Planes[hex].marker1.setMap(null);
delete Planes[hex];
}
}
if( PreSelected ) {
preselectPlane(PreSelected);
PreSelected=undefined;
}
}
function fetchAdsbData() {
var bounds = Gmap.getBounds();
if (bounds == null) { return; } // getBounds might not be defined even if map already is
var link = rcd_globals.fcgipath + 'aircraftlist.json?knownposonly=1';
var vps = Gmap.getBounds().getSouthWest().lat().toFixed(5);
var vpw = Gmap.getBounds().getSouthWest().lng().toFixed(5);
var vpn = Gmap.getBounds().getNorthEast().lat().toFixed(5);
var vpe = Gmap.getBounds().getNorthEast().lng().toFixed(5);
link += '&vpn=' + vpn + '&vpe=' + vpe + '&vps=' + vps + '&vpw=' + vpw;
if( Filters.enable_alt ) {
if( Filters.altitude_min ) { link += '&altitude.from=' + Filters.altitude_min; }
if( Filters.altitude_max ) { link += '&altitude.to=' + Filters.altitude_max; }
}
if (Filters.enable_gnd===false) { link+="&gndexcl=1"; }
if (Filters.enable_spd) {
if( Filters.speed_min ) { link += "&speed.from=" + Filters.speed_min; }
if( Filters.speed_max ) { link += "&speed.to=" + Filters.speed_max; }
}
if (Filters.enable_dis) {
if( Filters.distance_min ) { link += "&distance.from=" + Filters.distance_min; }
if( Filters.distance_max ) { link += "&distance.to=" + Filters.distance_max; }
}
if( SingleAircraft ) { link+= "&icao=" + SingleAircraft; }
else
if( Filters.reg_enable && Filters.reg_filter ) { link += "&icao=" + Filters.reg_filter; }
if( Filters.typ_enable && Filters.typ_filter ) { link += "&type=" + Filters.typ_filter; }
if( Filters.fli_enable && Filters.fli_filter ) { link += "&flight=" + Filters.fli_filter; }
if( Filters.dest_enable && Filters.dest_filter ) { link += "&dest=" + Filters.dest_filter; }
if( Filters.orig_enable && Filters.orig_filter ) { link += "&orig=" + Filters.orig_filter; }
if( Filters.squawk_enable && Filters.squawk_filter) { link += "&squawk=" + Filters.squawk_filter; }
if( Filters.prio_enable && Filters.prio_filter ) { link += "&prio=" + Filters.prio_filter; }
// C++ backend assumes that default is all on, so we only need to send the 'off' condition
if (!Filters.enable_adsb) { link += "&srcexcl=a"; }
if (!Filters.enable_mlat) { link += "&srcexcl=m"; }
if (!Filters.enable_flawa) { link += "&srcexcl=f"; }
if (!Filters.enable_ogn) { link += "&srcexcl=o"; }
if (!Filters.enable_flarm) { link += "&srcexcl=l"; }
if (typeof Filters.pref_source !== "undefined" && Filters.pref_source !== null && Filters.perfsource !== "") {
srcpref = "&srcpref=" + Filters.pref_source;
}
if( SingleSensor ) { link += "&sensor.only=" + SingleSensor; }
else
if( Filters.sensors_enable && Filters.sensors_filter ) { link += "&sensor.only=" + Filters.sensors_filter; }
getJSON( link, updatePlanes );
}
function fetchModeACData() {
if( !Filters.enable_modeac ) {
updateModeAC([]); // empty list clears the list
return;
}
if (Gmap.getBounds() == null) { return; } // getBounds might not be defined even if map already is
var link = rcd_globals.fcgipath + 'modeactargets.json';
getJSON( link, updateModeAC );
}
function FilterControl(controlDiv)
{
var controlUI = document.createElement('div');
controlUI.className = 'filter-button';
controlUI.title = 'Click to modify the filters';
controlDiv.appendChild(controlUI);
var controlText = document.createElement('div');
controlText.className = 'filter-icon';
controlUI.appendChild(controlText);
controlUI.addEventListener('click', function()
{
if (document.getElementById('filter-dialog') === null) {
var filterDialog = document.createElement('div');
filterDialog.id = 'filter-dialog';
filterDialog.innerHTML = '' +
'';
controlDiv.appendChild(filterDialog);
$("#filter-apply").click(function() {
handleChange();
$("#filter-dialog").hide();
});
$('#mapstyle, #enable_night, #refreshtime, #tracklength, #enable_atrack, #enable_mtrack, #show_sensors').change(mapstyleChange);
$('#filter-alt-min, #filter-alt-max, #enable_alt, #enable_gnd, ' +
'#filter-spd-min, #filter-spd-max, #enable_spd, ' +
'#filter-dis-min, #filter-dis-max, #enable_dis, ' +
'#reg_filter, #reg_enable, ' +
'#sensors_filter, #sensors_enable, ' +
'#typ_filter, #typ_enable, ' +
'#fli_filter, #fli_enable, ' +
'#dest_filter, #dest_enable, ' +
'#orig_filter, #orig_enable, ' +
'#squawk_filter, #squawk_enable, ' +
'#prio_filter, #prio_enable, ' +
'#pref_source, ' +
'#enable_adsb, #enable_mlat, #enable_modeac, #enable_flawa, #enable_ogn, #enable_flarm'
).change(handleChange);
$("#filter-dialog .close").click(function() {
$("#filter-dialog").hide();
});
$("#filter-dialog .clear").click(clear_filters);
$("#filter-alt-min").val(Filters.altitude_min);
$("#filter-alt-max").val(Filters.altitude_max);
$("#enable_alt" ).prop("checked",Filters.enable_alt );
$("#enable_gnd" ).prop("checked",Filters.enable_gnd );
$("#filter-spd-min").val(Filters.speed_min);
$("#filter-spd-max").val(Filters.speed_max);
$("#enable_spd" ).prop("checked",Filters.enable_spd );
$("#filter-dis-min").val(Filters.distance_min);
$("#filter-dis-max").val(Filters.distance_max);
$("#enable_dis" ).prop("checked",Filters.enable_dis );
$("#reg_filter" ).val(Filters.reg_filter);
$("#reg_enable" ).prop("checked",Filters.reg_enable );
$("#sensors_filter").val(Filters.sensors_filter);
$("#sensors_enable").prop("checked",Filters.sensors_enable);
$("#typ_enable" ).prop("checked",Filters.typ_enable );
$("#typ_filter" ).val(Filters.typ_filter);
$("#fli_enable" ).prop("checked",Filters.fli_enable );
$("#fli_filter" ).val(Filters.fli_filter);
$("#dest_enable" ).prop("checked",Filters.dest_enable );
$("#dest_filter" ).val(Filters.dest_filter);
$("#orig_enable" ).prop("checked",Filters.orig_enable );
$("#orig_filter" ).val(Filters.orig_filter);
$("#squawk_enable" ).prop("checked",Filters.squawk_enable );
$("#squawk_filter" ).val(Filters.squawk_filter);
$("#prio_enable" ).prop("checked",Filters.prio_enable );
$("#prio_filter" ).val(Filters.prio_filter);
$("#enable_adsb" ).prop("checked",Filters.enable_adsb );
$("#enable_mlat" ).prop("checked",Filters.enable_mlat );
$("#enable_modeac" ).prop("checked",Filters.enable_modeac );
$("#enable_flawa" ).prop("checked",Filters.enable_flawa );
$("#enable_ogn" ).prop("checked",Filters.enable_ogn );
$("#enable_flarm" ).prop("checked",Filters.enable_flarm );
$("#pref_source" ).val(Filters.pref_source);
$("#enable_night" ).prop("checked",GmapInit.enable_night );
$("#enable_atrack" ).prop("checked",GmapInit.enable_atrack );
$("#enable_mtrack" ).prop("checked",GmapInit.enable_mtrack );
$("#tracklength" ).val(GmapInit.tracklength);
$('#mapstyle' ).val(GmapInit.mapstyle);
$('#refreshtime' ).val(GmapInit.refreshtime);
$('#show_sensors' ).val(GmapInit.show_sensors);
function handleChange() {
Filters.altitude_min = $("#filter-alt-min").val() || undefined;
Filters.altitude_max = $("#filter-alt-max").val() || undefined;
Filters.enable_alt = $("#enable_alt" ).prop( "checked" );
Filters.enable_gnd = $("#enable_gnd" ).prop( "checked" );
Filters.speed_min = $("#filter-spd-min").val() || undefined;
Filters.speed_max = $("#filter-spd-max").val() || undefined;
Filters.enable_spd = $("#enable_spd" ).prop( "checked" );
Filters.distance_min = $("#filter-dis-min").val() || undefined;
Filters.distance_max = $("#filter-dis-max").val() || undefined;
Filters.enable_dis = $("#enable_dis" ).prop( "checked" );
Filters.reg_enable = $("#reg_enable" ).prop( "checked" );
Filters.reg_filter = $("#reg_filter" ).val() || undefined;
Filters.sensors_enable= $("#sensors_enable").prop( "checked" );
Filters.sensors_filter= $("#sensors_filter").val() || undefined;
Filters.typ_enable = $("#typ_enable" ).prop( "checked" );
Filters.typ_filter = $("#typ_filter" ).val() || undefined;
Filters.fli_enable = $("#fli_enable" ).prop( "checked" );
Filters.fli_filter = $("#fli_filter" ).val() || undefined;
Filters.dest_enable = $("#dest_enable" ).prop( "checked" );
Filters.dest_filter = $("#dest_filter" ).val() || undefined;
Filters.orig_enable = $("#orig_enable" ).prop( "checked" );
Filters.orig_filter = $("#orig_filter" ).val() || undefined;
Filters.squawk_enable = $("#squawk_enable" ).prop( "checked" );
Filters.squawk_filter = $("#squawk_filter" ).val() || undefined;
Filters.prio_enable = $("#prio_enable" ).prop( "checked" );
Filters.prio_filter = $("#prio_filter" ).val() || undefined;
Filters.enable_adsb = $("#enable_adsb" ).prop( "checked" );
Filters.enable_mlat = $("#enable_mlat" ).prop( "checked" );
Filters.enable_modeac = $("#enable_modeac" ).prop( "checked" );
Filters.enable_flawa = $("#enable_flawa" ).prop( "checked" );
Filters.enable_ogn = $("#enable_ogn" ).prop( "checked" );
Filters.enable_flarm = $("#enable_flarm" ).prop( "checked" );
Filters.pref_source = $("#pref_source" ).val() || undefined;
localStorage.setItem(rcd_globals.ls_filters_id, JSON.stringify(Filters));
if( Filters.prio_filter ) {
prio_filter_splitted = Filters.prio_filter.toUpperCase().split(",");
} else {
prio_filter_splitted = [];
}
fetchAdsbData();
}
function mapstyleChange() {
GmapInit.mapstyle = $('#mapstyle' ).val() || undefined;
GmapInit.enable_night = $('#enable_night' ).prop('checked');
GmapInit.refreshtime = $("#refreshtime" ).val() || undefined;
GmapInit.tracklength = $("#tracklength" ).val() || undefined;
GmapInit.enable_atrack = $("#enable_atrack").prop( "checked" );
GmapInit.enable_mtrack = $("#enable_mtrack").prop( "checked" );
GmapInit.show_sensors = $("#show_sensors" ).prop( "checked" );
Gmap.setMapTypeId(GmapInit.mapstyle);
localStorage.setItem(rcd_globals.ls_gmapinit_id, JSON.stringify(GmapInit));
if( GmapInit.enable_night ) nite.show();
else nite.hide();
fetchAllData();
for( var hex in flightPath2 ) {
flightPath2[hex].forEach( function ( X ) { X.setMap(null); });
delete flightPath2[hex];
}
}
function clear_filters() {
$('#filter-alt-min, #filter-alt-max, ' +
'#filter-spd-min, #filter-spd-max, ' +
'#filter-dis-min, #filter-dis-max, ' +
'#reg_filter, #sensors_filter, #typ_filter, #fli_filter, #dest_filter, ' +
"#orig_filter, #squawk_filter, #prio_filter, #pref_source").val(null);
$('#enable_alt, #enable_dis, #enable_spd, #reg_enable, #sensors_enable, #typ_enable, #fli_enable, #dest_enable, ' +
'#orig_enable, #squawk_enable, #prio_enable, #enable_atrack, #enable_mtrack, #enable_night').prop( 'checked', false );
$('#enable_adsb, #enable_gnd, #enable_mlat, #enable_modeac, #enable_flawa, #enable_ogn, #enable_flarm').prop( 'checked', true );
prio_filter_splitted = [];
handleChange();
};
}
else
{
$("#filter-dialog").show();
}
});
}
/********************************************************************************
* Information that is displayed as permanent label
* Color Table:
* https://www.quackit.com/html/codes/color/html_color_chart.cfm
*******************************************************************************/
function getIconForSensor(sensor)
{
var Scale = 1;
var Border = 'Black';
var Strokew = 1;
if( SingleSensor==sensor.id ) {
Border = 'Red';
Strokew = 3;
Scale = 1.5;
}
else if( Selected.length>0 ) {
var p = Planes[Selected.slice(-1)[0]].hex;
if( Planes[p].clnt.includes(sensor.id) ) {
Border = 'Blue';
Strokew = 3;
Scale = 1.5;
}
}
var Fill = ( sensor.active && sensor.iBy>30 ) ? 'Lime'
: ( sensor.active ) ? 'Orange'
: 'Red';
//http://tutorials.jenkov.com/svg/path-element.html
//var imageObj = {path:"M0,0 6,12 12,0z"}; // Triangle
//var imageObj = {path:"m25,1 6,17h18l-14,11 5,17-15-10-15,10 5-17-14-11h18z"}; // Star
var imageObj = {path:'M 13.6,18.8 L 6.3,18.8 1.1,13.6 1.1,6.3 6.3,1.1 13.6,1.1 18.8,6.3 18.8,13.6 z'}; // Octagon
var object_box = Raphael.pathBBox(imageObj.path);
var offset_x = (object_box.x + object_box.x2) / 2;
var offset_y = (object_box.y + object_box.y2) / 2;
var sensor_symbol = {
strokeColor : Border,
strokeWeight : Strokew,
path : imageObj.path,
scale : Scale,
fillColor : Fill,
fillOpacity : 0.9,
anchor : new google.maps.Point(offset_x, offset_y),
rotation : 180,
zIndex : 10
};
return sensor_symbol;
}
function labelSensorSetStyle(sensor, marker) {
marker.set('labelStyle', { opacity: 0.5, 'background-color': 'LightGreen'});
}
function fetchAllData() {
fetchAdsbData();
fetchTrackData();
fetchMapInfo();
if( rcd_globals.productname=='MLAT_SERVER' ) {
fetchSensors(GmapInit.show_sensors);
fetchModeACData();
}
}
/********************************************************************************
* Main
*******************************************************************************/
$(document).ready(function() {
getJSON( rcd_globals.fcgipath+"param.json?param=ownlat,ownlon,distanceunit", function(params) {
CenterLat = params.ownlat;
CenterLon = params.ownlon;
DistanceUnit = params.distanceunit ? "km" : "nm";
initialize();
});
});
function initialize() {
updateURLPlaneSelection();
/* There is no info_box_table without refreshGeneralInfo() */
loadAirports();
map_box = document.getElementById('map_box');
map_info_box = document.getElementById('map_info_box');
flight_info_box_adsb = document.getElementById('flight_info_box_adsb');
flight_info_box_modeac = document.getElementById('flight_info_box_modeac');
flight_info_box_klick = document.getElementById('flight_info_box_klick');
info_box_table = document.getElementById('info_box_table');
hide_info = document.getElementById('hide_info');
hide_info_btn = document.getElementById('hide_info_btn');
document.getElementById("btn_hide_info_box").onclick = toggleInfoBox;
showInfoBox(); // hideInfoBox();
var LS;
LS = localStorage.getItem(rcd_globals.ls_filters_id);
if( LS ) $.extend(Filters, JSON.parse(LS));
if( Filters.prio_filter ) prio_filter_splitted = Filters.prio_filter.toUpperCase().split(',');
LS = localStorage.getItem(rcd_globals.ls_gmapinit_id);
if( LS ) $.extend(GmapInit, JSON.parse(LS));
Gmap = new google.maps.Map(
map_box,
{
disableDefaultUI : true,
center : new google.maps.LatLng(GmapInit.CenterLat, GmapInit.CenterLon),
zoom : GmapInit.Zoomlevel,
mapTypeId : GmapInit.mapstyle,
zoomControl : true,
zoomControlOptions : { position : google.maps.ControlPosition.RIGHT_BOTTOM },
scaleControl : false,
streetViewControl : false
}
);
var currCenter = new google.maps.LatLng(GmapInit.CenterLat, GmapInit.CenterLon);
Gmap.addListener('click', deselectPlaneAll);
Gmap.addListener('zoom_changed', function() {
GmapInit.Zoomlevel=Gmap.getZoom();
localStorage.setItem(rcd_globals.ls_gmapinit_id, JSON.stringify(GmapInit));
});
Gmap.addListener('dragend', function() {
var currCenter = new google.maps.LatLng;
GmapInit.CenterLat = Gmap.getCenter().lat();
GmapInit.CenterLon = Gmap.getCenter().lng();
localStorage.setItem(rcd_globals.ls_gmapinit_id, JSON.stringify(GmapInit));
});
var filterControlDiv = document.createElement('div');
var filterControl = new FilterControl(filterControlDiv);
filterControlDiv.index = 1;
Gmap.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(filterControlDiv);
/* Setup night/day overlay */
nite.init(Gmap);
window.setInterval(function() { nite.setDate(null); nite.refresh(); }, 20000); // every 20s
if( Filters.enable_night ) nite.show();
else nite.hide();
/* Modify menus */
var MlatServerShow = document.getElementsByClassName('MlatServerShow');
console.log (MlatServerShow);
if( rcd_globals.productname=='MLAT_SERVER' ) {
document.getElementById("StatusHeader").innerHTML = ' MLAT Server Status';
}
var refreshLoop = function() {
return function() {
var timeout = parseFloat(GmapInit.refreshtime);
if( !timeout ) { timeout=1; }
if( rcd_globals.initialized ) {
fetchAllData();
}
timeout = Math.max(1000, 1000 * timeout);
window.setTimeout(refreshLoop, timeout);
}
}();
window.setTimeout(refreshLoop, 1000);
}