Lots more map work, mostly around switching from "construct a big HTML string"

to working directly with the DOM to update the table / selected plane info.
Seems to speed things up (and deflicker them) a lot.

Also stable sorts, allow disabling the clocks, draw ground tracks in a different
color, put "last seen" info on the selected plane infobox, if position updates
are infrequent then combine them into a single estimated line so that dash
placement works properly, probably a bunch of other things..
This commit is contained in:
Oliver Jowett 2015-01-06 20:15:25 +00:00
parent e8a62293c2
commit 43d29389f2
5 changed files with 536 additions and 374 deletions

View file

@ -32,3 +32,6 @@ SiteCircles = true; // true or false (Only shown if SiteShow is true)
// In nautical miles or km (depending settings value 'Metric') // In nautical miles or km (depending settings value 'Metric')
SiteCirclesDistances = new Array(100,150,200); SiteCirclesDistances = new Array(100,150,200);
// You can disable the clocks if you want here:
ShowClocks = false;

View file

@ -49,16 +49,116 @@
</tr></table> </tr></table>
</div> </div>
<div id="plane_detail"></div> <div id="dump1090_infoblock">
<table width="100%">
<tr class="infoblock_heading">
<td>
<b>DUMP1090</b>
</td>
<td align="right">
<span id="dump1090_version"></span>
</td>
</tr>
<tr class="infoblock_body">
<td width="50%">&nbsp;</td>
<td width="50%" align="right"><a href="https://github.com/mutability/dump1090" target="_blank">[GitHub]</a></td>
</tr>
<tr class="infoblock_body dim">
<td>(no aircraft selected)</td>
<td>&nbsp;</td>
</tr>
<tr class="infoblock_body">
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr class="infoblock_body">
<td colspan="2">Tracked aircraft (total): <span id="dump1090_total_ac">n/a</span></td>
</tr>
<tr class="infoblock_body">
<td colspan="2">Tracked aircraft (with positions): <span id="dump1090_total_ac_positions">n/a</span></td>
</tr>
</table>
</div>
<div id="selected_infoblock" class="hidden">
<table width="100%">
<tr class="infoblock_heading">
<td colspan="2">
<b> <span id="selected_callsign">n/a</span></b>
<span id="selected_emergency"></span>
<span id="selected_links">
<a id="selected_fr24_link" href="" target="_blank">[FR24]</a>
<a id="selected_flightstats_link" href="" target="_blank">[FlightStats]</a>
<a id="selected_flightaware_link" href="" target="_blank">[FlightAware]</a>
</span>
</td>
</tr>
<tr class="infoblock_body">
<td width="50%">Altitude: <span id="selected_altitude"></span></td>
<td width="50%">Squawk: <span id="selected_squawk"></span></td>
</tr>
<tr class="infoblock_body">
<td>Speed: <span id="selected_speed">n/a</span></td>
<td>ICAO: <span id="selected_icao">n/a</span></td>
</tr>
<tr class="infoblock_body">
<td>Track: <span id="selected_track">n/a</span></td>
<td>Last seen: <span id="selected_seen">n/a</span</td>
</tr>
<tr class="infoblock_body">
<td colspan="2">Lat/Long: <span id="selected_position">n/a</span></td>
</tr>
<tr class="infoblock_body">
<td colspan="2">Distance from Site: <span id="selected_sitedist">n/a</span></td>
</tr>
</table>
</div>
<div id="options"></div> <div id="options"></div>
<div id="planes_table"></div> <div id="planes_table">
<table id="tableinfo" width="100%">
<thead style="background-color: #BBBBBB; cursor: pointer;">
<td id="icao" onclick="sortByICAO();">ICAO</td>
<td id="flight" onclick="sortByFlight();">Flight</td>
<td id="squawk" onclick="sortBySquawk();" align="right">Squawk</td>
<td id="altitude" onclick="sortByAltitude();" align="right">Altitude</td>
<td id="speed" onclick="sortBySpeed();" align="right">Speed</td>
<td id="distance" onclick="sortByDistance();" align="right">Distance</td>
<td id="track" onclick="sortByTrack();" align="right">Track</td>
<td id="msgs" onclick="sortByMsgs();" align="right">Msgs</td>
<td id="seen" onclick="sortBySeen();" align="right">Age</td>
</thead>
<tbody>
<tr id="plane_row_template" class="plane_table_row hidden">
<td>ICAO</td>
<td>FLIGHT</td>
<td align="right">SQUAWK</td>
<td align="right">ALTITUDE</td>
<td align="right">SPEED</td>
<td align="right">DISTANCE</td>
<td align="right">TRACK</td>
<td align="right">MSGS</td>
<td align="right">SEEN</td>
</tr>
</tbody>
</table>
</div>
<div id="plane_extension"></div> <div id="plane_extension"></div>
</div> </div>
</div> </div>
<div id="SpecialSquawkWarning"> <div id="SpecialSquawkWarning" class="hidden">
<b>Squak 7x00 is reported and shown.</b><br> <b>Squawk 7x00 is reported and shown.</b><br>
This is most likely an error in reciving or decoding.<br> This is most likely an error in receiving or decoding.<br>
Please do not call the local authorities, they already know about it if it is valid squak. Please do not call the local authorities, they already know about it if it is a valid squawk.
</div> </div>
<div id="container_splitter"></div> <div id="container_splitter"></div>
</body> </body>

View file

@ -19,7 +19,6 @@ var planeObject = {
// GMap Details // GMap Details
marker : null, marker : null,
markerColor : MarkerColor, markerColor : MarkerColor,
lines : [],
tracklinesegs : [], tracklinesegs : [],
last_position_time : null, last_position_time : null,
@ -39,27 +38,71 @@ var planeObject = {
line : null, line : null,
head_update : this.last_position_time, head_update : this.last_position_time,
tail_update : this.last_position_time, tail_update : this.last_position_time,
estimated : false }; estimated : false,
ground : (this.altitude === "ground")
};
this.tracklinesegs.push(newseg); this.tracklinesegs.push(newseg);
} else { return;
}
var lastseg = this.tracklinesegs[this.tracklinesegs.length - 1]; var lastseg = this.tracklinesegs[this.tracklinesegs.length - 1];
var lastpos = lastseg.track.getAt(lastseg.track.getLength() - 1); var lastpos = lastseg.track.getAt(lastseg.track.getLength() - 1);
var elapsed = (this.last_position_time - lastseg.head_update); var elapsed = (this.last_position_time - lastseg.head_update);
if (elapsed > 5) {
// >5s gap in data, put an estimated segment in var new_data = (here !== lastpos);
//console.log(this.icao + " discontinuity seen: " + lastpos.lat() + "," + lastpos.lng() + " -> " + here.lat() + "," + here.lng()); var est_track = (elapsed > 5);
var estseg = { track : new google.maps.MVCArray([lastpos, here]), var ground_track = (this.altitude === "ground");
if (!new_data)
return;
if (est_track) {
if (!lastseg.estimated) {
// >5s gap in data, create a new estimated segment
//console.log(this.icao + " switching to estimated");
this.tracklinesegs.push({ track : new google.maps.MVCArray([lastpos, here]),
line : null, line : null,
estimated : true }; head_update : this.last_position_time,
var newseg = { track : new google.maps.MVCArray([here,here]), estimated : true });
return;
}
// Append to ongoing estimated line
//console.log(this.icao + " extending estimated (" + lastseg.track.getLength() + ")");
lastseg.track.push(here);
lastseg.head_update = this.last_position_time;
return;
}
if (lastseg.estimated) {
// We are back to good data.
//console.log(this.icao + " switching to good track");
this.tracklinesegs.push({ track : new google.maps.MVCArray([lastpos, here]),
line : null, line : null,
head_update : this.last_position_time, head_update : this.last_position_time,
tail_update : this.last_position_time, tail_update : this.last_position_time,
estimated : false }; estimated : false,
this.tracklinesegs.push(estseg); ground : (this.altitude === "ground") });
this.tracklinesegs.push(newseg); return;
} else if (elapsed > 0) { }
// New position data
if ( (lastseg.ground && this.altitude !== "ground") ||
(!lastseg.ground && this.altitude === "ground") ) {
console.log(this.icao + " ground state changed");
// Create a new segment as the ground state changed.
// assume the state changed halfway between the two points
var midpoint = google.maps.geometry.spherical.interpolate(lastpos,here,0.5);
lastseg.track.push(midpoint);
this.tracklinesegs.push({ track : new google.maps.MVCArray([midpoint,here,here]),
line : null,
head_update : this.last_position_time,
tail_update : this.last_position_time,
estimated : false,
ground : (this.altitude === "ground") });
return;
}
// Add more data to the existing track.
// We only retain some historical points, at 5+ second intervals, // We only retain some historical points, at 5+ second intervals,
// plus the most recent point // plus the most recent point
if (this.last_position_time - lastseg.tail_update >= 5) { if (this.last_position_time - lastseg.tail_update >= 5) {
@ -72,8 +115,6 @@ var planeObject = {
lastseg.track.setAt(lastseg.track.getLength()-1, here); lastseg.track.setAt(lastseg.track.getLength()-1, here);
} }
lastseg.head_update = this.last_position_time; lastseg.head_update = this.last_position_time;
}
}
}, },
// This is to remove the line from the screen if we deselect the plane // This is to remove the line from the screen if we deselect the plane
@ -161,7 +202,7 @@ var planeObject = {
}, },
// TODO: Trigger actions of a selecting a plane // TODO: Trigger actions of a selecting a plane
funcSelectPlane : function(selectedPlane){ selectPlane : function(){
selectPlaneByHex(this.icao); selectPlaneByHex(this.icao);
}, },
@ -182,6 +223,7 @@ var planeObject = {
if (typeof data.lat !== "undefined") { if (typeof data.lat !== "undefined") {
this.latitude = data.lat; this.latitude = data.lat;
this.longitude = data.lon; this.longitude = data.lon;
this.seen_pos = data.seen_pos;
this.last_position_time = receiver_now - data.seen_pos; this.last_position_time = receiver_now - data.seen_pos;
} }
if (typeof data.flight !== "undefined") if (typeof data.flight !== "undefined")
@ -197,7 +239,7 @@ var planeObject = {
this.marker.setMap(null); this.marker.setMap(null);
this.marker = null; this.marker = null;
} }
this.funcClearLines(); this.funcClearLine();
if (SelectedPlane == this.icao) { if (SelectedPlane == this.icao) {
if (this.is_selected) { if (this.is_selected) {
this.is_selected = false; this.is_selected = false;
@ -216,7 +258,6 @@ var planeObject = {
} }
this.marker = this.funcUpdateMarker(); this.marker = this.funcUpdateMarker();
PlanesOnMap++;
} }
}, },
@ -230,14 +271,14 @@ var planeObject = {
position: new google.maps.LatLng(this.latitude, this.longitude), position: new google.maps.LatLng(this.latitude, this.longitude),
map: GoogleMap, map: GoogleMap,
icon: this.funcGetIcon(), icon: this.funcGetIcon(),
visable: true visible: true
}); });
// This is so we can match icao address // This is so we can match icao address
this.marker.icao = this.icao; this.marker.icao = this.icao;
// Trap clicks for this marker. // Trap clicks for this marker.
google.maps.event.addListener(this.marker, 'click', this.funcSelectPlane); google.maps.event.addListener(this.marker, 'click', this.selectPlane);
} }
// Setting the marker title // Setting the marker title
@ -261,12 +302,31 @@ var planeObject = {
// console.log(" point " + j + " at " + seg.track.getAt(j).lat() + "," + seg.track.getAt(j).lng()); // console.log(" point " + j + " at " + seg.track.getAt(j).lat() + "," + seg.track.getAt(j).lng());
// } // }
if (seg.estimated) {
var lineSymbol = {
path: 'M 0,-1 0,1',
strokeOpacity : 1,
strokeColor : '#804040',
strokeWeight : 2,
scale: 2
};
seg.line = new google.maps.Polyline({ seg.line = new google.maps.Polyline({
strokeColor: (seg.estimated ? '#804040' : '#000000'), path: seg.track,
strokeOpacity: 0,
icons: [{
icon: lineSymbol,
offset: '0',
repeat: '10px' }],
map : GoogleMap });
} else {
seg.line = new google.maps.Polyline({
path: seg.track,
strokeOpacity: 1.0, strokeOpacity: 1.0,
strokeWeight: (seg.estimated ? 2 : 3), strokeColor: (seg.ground ? '#408040' : '#000000'),
map: GoogleMap, strokeWeight: 3,
path: seg.track }); map: GoogleMap });
}
} }
} }
} }

View file

@ -1,15 +1,14 @@
// Define our global variables // Define our global variables
var GoogleMap = null; var GoogleMap = null;
var Planes = {}; var Planes = {};
var PlanesOnMap = 0; var PlanesOrdered = [];
var PlanesOnTable = 0;
var PlanesToReap = 0;
var SelectedPlane = null; var SelectedPlane = null;
var SpecialSquawk = false;
var sortColumn = 3; var EmergencySquawks = {
var sortAscending = true; '7500' : 'Aircraft Hijacking',
var sortNumeric = true; '7600' : 'Radio Failure',
'7700' : 'General Emergency'
};
// Get current map settings // Get current map settings
CenterLat = Number(localStorage['CenterLat']) || CONST_CENTERLAT; CenterLat = Number(localStorage['CenterLat']) || CONST_CENTERLAT;
@ -19,39 +18,54 @@ ZoomLvl = Number(localStorage['ZoomLvl']) || CONST_ZOOMLVL;
Dump1090Version = "unknown version"; Dump1090Version = "unknown version";
RefreshInterval = 1000; RefreshInterval = 1000;
PlaneRowTemplate = null;
TrackedAircraft = 0
TrackedPositions = 0
function fetchData() { function fetchData() {
$.getJSON('data/aircraft.json', function(data) { $.getJSON('data/aircraft.json', function(data) {
PlanesOnMap = 0;
SpecialSquawk = false;
// Loop through all the planes in the data packet // Loop through all the planes in the data packet
var now = data.now; var now = data.now;
var acs = data.aircraft; var acs = data.aircraft;
for (var j=0; j < acs.length; j++) { for (var j=0; j < acs.length; j++) {
var ac = acs[j]; var ac = acs[j];
var hex = ac.hex;
var plane = null;
// Do we already have this plane object in Planes? // Do we already have this plane object in Planes?
// If not make it. // If not make it.
if (Planes[ac.hex]) {
var plane = Planes[ac.hex];
} else {
var plane = jQuery.extend(true, {}, planeObject);
Planes[ac.hex] = plane;
}
// Set SpecialSquawk-value if (Planes[hex]) {
if (ac.squawk == '7500' || ac.squawk == '7600' || ac.squawk == '7700') { plane = Planes[hex];
SpecialSquawk = true; } else {
plane = jQuery.extend(true, {}, planeObject);
plane.tr = PlaneRowTemplate.cloneNode(true);
plane.tr.cells[0].textContent = hex; // this won't change
plane.tr.addEventListener('click', $.proxy(plane.selectPlane, plane));
plane.sitedist = null;
Planes[hex] = plane;
PlanesOrdered.push(plane);
} }
// Call the function update // Call the function update
plane.funcUpdateData(now, ac); plane.funcUpdateData(now, ac);
} }
PlanesOnTable = acs.length; refreshTableInfo();
refreshSelected();
}); });
} }
function initialize() { function initialize() {
PlaneRowTemplate = document.getElementById("plane_row_template");
if (!ShowClocks) {
$('#timestamps').addClass('hidden');
}
// Get receiver metadata, reconfigure using it, then continue // Get receiver metadata, reconfigure using it, then continue
// with initialization // with initialization
$.getJSON('data/receiver.json') $.getJSON('data/receiver.json')
@ -72,6 +86,18 @@ function initialize() {
// Initalizes the map and starts up our timers to call various functions // Initalizes the map and starts up our timers to call various functions
function initialize_after_config() { function initialize_after_config() {
// Set SitePosition, initialize sorting
if (SiteShow && (typeof SiteLat !== 'undefined') && (typeof SiteLon !== 'undefined')) {
SitePosition = new google.maps.LatLng(SiteLat, SiteLon);
sortByDistance();
} else {
SitePosition = null;
PlaneRowTemplate.cells[5].className = "hidden"; // hide distance column
document.getElementById("distance").className = "hidden"; // hide distance header
sortByAltitude();
}
// Make a list of all the available map IDs // Make a list of all the available map IDs
var mapTypeIds = []; var mapTypeIds = [];
for(var type in google.maps.MapTypeId) { for(var type in google.maps.MapTypeId) {
@ -186,15 +212,14 @@ function initialize_after_config() {
}); });
// Add home marker if requested // Add home marker if requested
if (SiteShow && (typeof SiteLat !== 'undefined' || typeof SiteLon !== 'undefined')) { if (SitePosition) {
var siteMarker = new google.maps.LatLng(SiteLat, SiteLon);
var markerImage = new google.maps.MarkerImage( var markerImage = new google.maps.MarkerImage(
'http://maps.google.com/mapfiles/kml/pal4/icon57.png', 'http://maps.google.com/mapfiles/kml/pal4/icon57.png',
new google.maps.Size(32, 32), // Image size new google.maps.Size(32, 32), // Image size
new google.maps.Point(0, 0), // Origin point of image new google.maps.Point(0, 0), // Origin point of image
new google.maps.Point(16, 16)); // Position where marker should point new google.maps.Point(16, 16)); // Position where marker should point
var marker = new google.maps.Marker({ var marker = new google.maps.Marker({
position: siteMarker, position: SitePosition,
map: GoogleMap, map: GoogleMap,
icon: markerImage, icon: markerImage,
title: 'My Radar Site', title: 'My Radar Site',
@ -220,34 +245,33 @@ function initialize_after_config() {
extendedInitalize(); extendedInitalize();
// Setup our timer to poll from the server. // Setup our timer to poll from the server.
window.setInterval(function() { window.setInterval(fetchData, RefreshInterval);
fetchData(); window.setInterval(reaper, 60000);
refreshTableInfo();
refreshSelected();
reaper();
extendedPulse();
}, RefreshInterval);
} }
// This looks for planes to reap out of the master Planes variable // This looks for planes to reap out of the master Planes variable
function reaper() { function reaper() {
PlanesToReap = 0;
// When did the reaper start?
reaptime = new Date().getTime(); reaptime = new Date().getTime();
console.log("Reaping started..");
// Loop the planes // Loop the planes
for (var reap in Planes) { var newPlanes = [];
// Is this plane possibly reapable? for (var i = 0; i < PlanesOrdered.length; ++i) {
if (Planes[reap].reapable == true) { var plane = PlanesOrdered[i];
// Has it not been seen for 5 minutes? if ((reaptime - plane.updated) > 300000) {
// This way we still have it if it returns before then
// Due to loss of signal or other reasons
if ((reaptime - Planes[reap].updated) > 300000) {
// Reap it. // Reap it.
delete Planes[reap]; console.log("Reaped " + plane.icao);
} delete Planes[plane.icao];
PlanesToReap++; } else {
// Keep it.
newPlanes.push(plane);
} }
}; };
PlanesOrdered = newPlanes;
refreshTableInfo();
refreshSelected();
} }
// Refresh the detail window about the plane // Refresh the detail window about the plane
@ -257,313 +281,284 @@ function refreshSelected() {
selected = Planes[SelectedPlane]; selected = Planes[SelectedPlane];
} }
var columns = 2; if (!selected) {
var html = ''; $('#dump1090_infoblock').removeClass('hidden');
$('#dump1090_version').text(Dump1090Version);
$('#dump1090_total_ac').text(TrackedAircraft);
$('#dump1090_total_ac_positions').text(TrackedAircraftPositions);
$('#selected_infoblock').addClass('hidden');
return;
}
if (selected) { $('#dump1090_infoblock').addClass('hidden');
html += '<table id="selectedinfo" width="100%">'; $('#selected_infoblock').removeClass('hidden');
if (selected.flight !== null && selected.flight !== "") {
$('#selected_callsign').text(selected.flight);
$('#selected_links').removeClass('hidden');
$('#selected_fr24_link').attr('href','http://fr24.com/'+selected.flight);
$('#selected_flightstats_link').attr('href','http://www.flightstats.com/go/FlightStatus/flightStatusByFlight.do?flightNumber='+selected.flight);
$('#selected_flightaware_link').attr('href','http://flightaware.com/live/flight/'+selected.flight);
} else { } else {
html += '<table id="selectedinfo" class="dim" width="100%">'; $('#selected_callsign').text('n/a (' + selected.icao + ')');
$('#selected_links').addClass('hidden');
} }
// Flight header line including squawk if needed var emerg = document.getElementById('selected_emergency');
if (selected && selected.flight == "") { if (selected.squawk in EmergencySquawks) {
html += '<tr><td colspan="' + columns + '" id="selectedinfotitle"><b>N/A (' + emerg.className = 'squawk' + selected.squawk;
selected.icao + ')</b>'; emerg.textContent = '\u00a0Squawking: ' + EmergencySquawks[selected.squawk] + '\u00a0';
} else if (selected && selected.flight != "") {
html += '<tr><td colspan="' + columns + '" id="selectedinfotitle"><b>' +
selected.flight + '</b>';
} else { } else {
html += '<tr><td colspan="' + columns + '" id="selectedinfotitle"><b>DUMP1090 ' + Dump1090Version + '</b>&nbsp;<a href="https://github.com/mutability/dump1090" target="_blank">[GitHub]</a>'; emerg.className = 'hidden';
} }
if (selected && selected.squawk == 7500) { // Lets hope we never see this... Aircraft Hijacking if (selected.altitude === null)
html += '&nbsp;<span class="squawk7500">&nbsp;Squawking: Aircraft Hijacking&nbsp;</span>'; $("#selected_altitude").text("n/a");
} else if (selected && selected.squawk == 7600) { // Radio Failure else if (selected.altitude === "ground")
html += '&nbsp;<span class="squawk7600">&nbsp;Squawking: Radio Failure&nbsp;</span>'; $("#selected_altitude").text("on ground");
} else if (selected && selected.squawk == 7700) { // General Emergency else if (Metric)
html += '&nbsp;<span class="squawk7700">&nbsp;Squawking: General Emergency&nbsp;</span>'; $("#selected_altitude").text(Math.round(selected.altitude / 3.2828) + ' m');
} else if (selected && selected.flight != '') { else
html += '&nbsp;<a href="http://fr24.com/'+selected.flight+'" target="_blank">[FR24]</a>'; $("#selected_altitude").text(Math.round(selected.altitude) + ' ft');
html += '&nbsp;<a href="http://www.flightstats.com/go/FlightStatus/flightStatusByFlight.do?';
html += 'flightNumber='+selected.flight+'" target="_blank">[FlightStats]</a>';
html += '&nbsp;<a href="http://flightaware.com/live/flight/'+selected.flight+'" target="_blank">[FlightAware]</a>';
}
html += '<td></tr>';
if (selected && selected.altitude !== null) { if (selected.squawk === null || selected.squawk === '0000') {
if (selected.altitude === "ground") $('#selected_squawk').text('n/a');
html += '<tr><td>Altitude: on ground</td>';
else if (Metric) {
html += '<tr><td>Altitude: ' + Math.round(selected.altitude / 3.2828) + ' m</td>';
} else { } else {
html += '<tr><td>Altitude: ' + selected.altitude + ' ft</td>'; $('#selected_squawk').text(selected.squawk);
} }
if (selected.speed === null) {
$('#selected_speed').text('n/a');
} else if (Metric) {
$('#selected_speed').text(Math.round(selected.speed * 1.852) + ' km/h');
} else { } else {
html += '<tr><td>Altitude: n/a</td>'; $('#selected_speed').text(Math.round(selected.speed) + ' kt');
} }
if (selected && selected.squawk != '0000') { $('#selected_icao').text(selected.icao);
html += '<td>Squawk: ' + selected.squawk + '</td></tr>';
if (selected.track === null) {
$('#selected_track').text('n/a');
} else { } else {
html += '<td>Squawk: n/a</td></tr>'; $('#selected_track').text(selected.track + '\u00b0' + ' (' + trackLongName(selected.track) + ')');
} }
html += '<tr><td>Speed: ' if (selected.seen <= 1) {
if (selected) { $('#selected_seen').text('now');
if (Metric) {
html += Math.round(selected.speed * 1.852) + ' km/h';
} else { } else {
html += selected.speed + ' kt'; $('#selected_seen').text(selected.seen + 's ago');
} }
if (selected.latitude === null) {
$('#selected_position').text('n/a');
} else { } else {
html += 'n/a'; if (selected.seen_pos > 1) {
} $('#selected_position').text(selected.latitude + ', ' + selected.longitude + " (" + selected.seen_pos + "s ago)");
html += '</td>';
if (selected) {
html += '<td>ICAO (hex): ' + selected.icao + '</td></tr>';
} else { } else {
html += '<td>ICAO (hex): n/a</td></tr>'; // Something is wrong if we are here $('#selected_position').text(selected.latitude + ', ' + selected.longitude);
}
} }
html += '<tr><td>Track: ' if (selected.sitedist !== null) {
if (selected && selected.track !== null) { var dist = selected.sitedist;
html += selected.track + '&deg;' + ' (' + trackLongName(selected.track) +')';
} else {
html += 'n/a';
}
html += '</td><td>&nbsp;</td></tr>';
html += '<tr><td colspan="' + columns + '" align="center">Lat/Long: ';
if (selected && selected.latitude !== null) {
html += selected.latitude + ', ' + selected.longitude + '</td></tr>';
// Let's show some extra data if we have site coordinates
if (SiteShow) {
var siteLatLon = new google.maps.LatLng(SiteLat, SiteLon);
var planeLatLon = new google.maps.LatLng(selected.latitude, selected.longitude);
var dist = google.maps.geometry.spherical.computeDistanceBetween (siteLatLon, planeLatLon);
if (Metric) { if (Metric) {
dist /= 1000; dist /= 1000;
} else { } else {
dist /= 1852; dist /= 1852;
} }
dist = (Math.round((dist)*10)/10).toFixed(1); dist = (Math.round((dist)*10)/10).toFixed(1);
html += '<tr><td colspan="' + columns + '" align="center">Distance from Site: ' + dist +
(Metric ? ' km' : ' NM') + '</td></tr>';
} // End of SiteShow
} else {
if (SiteShow) {
html += '<tr><td colspan="' + columns + '" align="center">Distance from Site: n/a ' +
(Metric ? ' km' : ' NM') + '</td></tr>';
} else {
html += 'n/a</td></tr>';
}
}
html += '</table>'; $('#selected_sitedist').text(dist + (Metric ? ' km' : ' NM'));
} else {
document.getElementById('plane_detail').innerHTML = html; $('#selected_sitedist').text("n/a");
}
} }
function trackShortName(track) { function trackShortName(track) {
var trackIndex = Math.floor((track+22.5) / 45) % 8; var trackIndex = Math.floor((360 + track % 360 + 22.5) / 45) % 8;
if (trackIndex < 0)
return "n/a";
return ["N","NE","E","SE","S","SW","W","NW"][trackIndex]; return ["N","NE","E","SE","S","SW","W","NW"][trackIndex];
} }
function trackLongName(track) { function trackLongName(track) {
var trackIndex = Math.floor((track+22.5) / 45); var trackIndex = Math.floor((360 + track % 360 + 22.5) / 45) % 8;
if ((trackIndex < 0) || (trackIndex >= 8))
return "n/a";
return ["North","Northeast","East","Southeast","South","Southwest","West","Northwest"][trackIndex]; return ["North","Northeast","East","Southeast","South","Southwest","West","Northwest"][trackIndex];
} }
// Refeshes the larger table of all the planes // Refeshes the larger table of all the planes
function refreshTableInfo() { function format_altitude(alt) {
var html = '<table id="tableinfo" width="100%">'; if (alt === null)
html += '<thead style="background-color: #BBBBBB; cursor: pointer;">'; return "";
html += '<td id="icao" onclick="sortBy(\'icao\',false);">ICAO</td>'; else if (alt === "ground")
html += '<td id="flight" onclick="sortBy(\'flight\',false);">Flight</td>'; return "ground";
html += '<td id="squawk" onclick="sortBy(\'squawk\',false);" align="right">Squawk</td>'; else if (Metric)
html += '<td id="altitude" onclick="sortBy(\'altitude\',true);" align="right">Altitude</td>'; return Math.round(alt / 3.2828);
html += '<td id="speed" onclick="sortBy(\'speed\',true);" align="right">Speed</td>'; else
// Add distance column header to table if site coordinates are provided return Math.round(alt);
if (SiteShow && (typeof SiteLat !== 'undefined' || typeof SiteLon !== 'undefined')) {
html += '<td id="distance" onclick="sortBy(\'distance\',true);" align="right">Distance</td>';
} }
html += '<td id="track" onclick="sortBy(\'track\',true);" align="right">Track</td>';
html += '<td id="msgs" onclick="sortBy(\'msgs\',true);" align="right">Msgs</td>';
html += '<td id="seen" onclick="sortBy(\'seen\',true);" align="right">Seen</td></thead><tbody>';
for (var tablep in Planes) { function format_speed(speed) {
var tableplane = Planes[tablep] if (speed === null)
if (!tableplane.reapable) { return "";
var specialStyle = ""; else if (Metric)
// Is this the plane we selected? return Math.round(speed * 1.852);
if (tableplane.icao == SelectedPlane) { else
specialStyle += " selected"; return Math.round(speed);
} }
// Lets hope we never see this... Aircraft Hijacking
if (tableplane.squawk == 7500) { function format_distance(dist) {
specialStyle += " squawk7500"; if (Metric) {
return (Math.round(dist/100) / 10).toFixed(1);
} else {
return (Math.round(dist/185.2) / 10).toFixed(1);
} }
// Radio Failure
if (tableplane.squawk == 7600) {
specialStyle += " squawk7600";
}
// Emergancy
if (tableplane.squawk == 7700) {
specialStyle += " squawk7700";
} }
function refreshTableInfo() {
var show_squawk_warning = false;
TrackedAircraft = 0
TrackedAircraftPositions = 0
for (var i = 0; i < PlanesOrdered.length; ++i) {
var tableplane = PlanesOrdered[i];
if (tableplane.reapable) {
tableplane.tr.className = "hidden";
} else {
TrackedAircraft++;
var classes = "plane_table_row";
if (tableplane.latitude !== null) if (tableplane.latitude !== null)
html += '<tr class="plane_table_row vPosition' + specialStyle + '">'; classes += " vPosition";
else if (tableplane.icao == SelectedPlane)
html += '<tr class="plane_table_row ' + specialStyle + '">'; classes += " selected";
html += '<td>' + tableplane.icao + '</td>'; if (tableplane.squawk in EmergencySquawks) {
classes += ' squawk' + tableplane.squawk;
show_squawk_warning = true;
}
if (tableplane.flight !== null) tableplane.tr.className = classes;
html += '<td>' + tableplane.flight + '</td>'; // ICAO doesn't change
else tableplane.tr.cells[1].textContent = (tableplane.flight !== null ? tableplane.flight : "");
html += '<td></td>'; tableplane.tr.cells[2].textContent = (tableplane.squawk !== null ? tableplane.squawk : "");
tableplane.tr.cells[3].textContent = format_altitude(tableplane.altitude);
tableplane.tr.cells[4].textContent = format_speed(tableplane.speed);
if (tableplane.squawk !== null) if (tableplane.latitude !== null)
html += '<td align="right">' + tableplane.squawk + '</td>'; ++TrackedAircraftPositions;
else
html += '<td align="right"></td>';
if (tableplane.altitude === null)
html += '<td align="right">&nbsp;</td>';
else if (tableplane.altitude === "ground")
html += '<td align="right">ground</td>';
else if (Metric)
html += '<td align="right">' + Math.round(tableplane.altitude / 3.2828) + '</td>';
else
html += '<td align="right">' + tableplane.altitude + '</td>';
if (tableplane.speed === null)
html += '<td align="right">&nbsp;</td>';
else if (Metric)
html += '<td align="right">' + Math.round(tableplane.speed * 1.852) + '</td>';
else
html += '<td align="right">' + tableplane.speed + '</td>';
// Add distance column to table if site coordinates are provided // Add distance column to table if site coordinates are provided
if (SiteShow && (typeof SiteLat !== 'undefined' || typeof SiteLon !== 'undefined')) { if (SitePosition !== null && tableplane.latitude !== null) {
html += '<td align="right">';
if (tableplane.latitude !== null) {
var siteLatLon = new google.maps.LatLng(SiteLat, SiteLon);
var planeLatLon = new google.maps.LatLng(tableplane.latitude, tableplane.longitude); var planeLatLon = new google.maps.LatLng(tableplane.latitude, tableplane.longitude);
var dist = google.maps.geometry.spherical.computeDistanceBetween (siteLatLon, planeLatLon); var dist = google.maps.geometry.spherical.computeDistanceBetween (SitePosition, planeLatLon);
if (Metric) { tableplane.tr.cells[5].textContent = format_distance(dist);
dist /= 1000; tableplane.sitedist = dist;
} else { } else {
dist /= 1852; tableplane.tr.cells[5].textContent = "";
}
dist = (Math.round((dist)*10)/10).toFixed(1);
html += dist;
}
html += '</td>';
} }
html += '<td align="right">'; tableplane.tr.cells[6].textContent = (tableplane.track !== null ? tableplane.track : "");
if (tableplane.track !== null) tableplane.tr.cells[7].textContent = tableplane.messages;
html += tableplane.track; tableplane.tr.cells[8].textContent = tableplane.seen;
html += '</td>';
html += '<td align="right">' + tableplane.messages + '</td>';
html += '<td align="right">' + tableplane.seen + '</td>';
html += '</tr>';
} }
} }
html += '</tbody></table>';
document.getElementById('planes_table').innerHTML = html; if (show_squawk_warning) {
$("#SpecialSquawkWarning").removeClass('hidden');
if (SpecialSquawk) {
$('#SpecialSquawkWarning').css('display', 'inline');
} else { } else {
$('#SpecialSquawkWarning').css('display', 'none'); $("#SpecialSquawkWarning").addClass('hidden');
} }
// Click event for table
$('#planes_table').find('tr').click( function(){
var hex = $(this).find('td:first').text();
if (hex != "ICAO") {
selectPlaneByHex(hex);
refreshTableInfo();
refreshSelected();
}
});
resortTable(); resortTable();
} }
function sortBy(colName,numeric) { //
var header_cells = document.getElementById('tableinfo').tHead.rows[0].cells; // ---- table sorting ----
for (var i = 0; i < header_cells.length; ++i) { //
if (header_cells[i].id === colName) {
if (i == sortColumn) function compareAlpha(xa,xo,ya,yo) {
if (xa === ya)
return xo - yo;
if (xa === null)
return 1;
if (ya === null)
return -1;
if (xa < ya)
return -1;
return 1;
}
function compareNumeric(xf,xo,yf,yo) {
if (xf === null) xf = 1e9;
if (yf === null) yf = 1e9;
if (Math.abs(xf - yf) < 1e-9)
return xo - yo;
return xf - yf;
}
function compareAltitude(xf,xo,yf,yo) {
if (xf === null) xf = 1e9;
else if (xf === "ground") xf = -1e9;
if (yf === null) yf = 1e9;
else if (yf === "ground") yf = -1e9;
if (Math.abs(xf - yf) < 1e-9)
return xo - yo;
return xf - yf;
}
function sortByICAO() { sortBy('icao', function(x,y){return compareAlpha(x.icao, x.sort_pos, y.icao, y.sort_pos)}); }
function sortByFlight() { sortBy('flight', function(x,y){return compareAlpha(x.flight, x.sort_pos, y.flight, y.sort_pos)}); }
function sortBySquawk() { sortBy('squawk', function(x,y){return compareAlpha(x.squawk, x.sort_pos, y.squawk, y.sort_pos)}); }
function sortByAltitude() { sortBy('altitude',function(x,y){return compareAltitude(x.altitude, x.sort_pos, y.altitude, y.sort_pos)}); }
function sortBySpeed() { sortBy('speed', function(x,y){return compareNumeric(x.speed, x.sort_pos, y.speed, y.sort_pos)}); }
function sortByDistance() { sortBy('sitedist',function(x,y){return compareNumeric(x.sitedist, x.sort_pos, y.sitedist, y.sort_pos)}); }
function sortByTrack() { sortBy('track', function(x,y){return compareNumeric(x.track, x.sort_pos, y.track, y.sort_pos)}); }
function sortByMsgs() { sortBy('msgs', function(x,y){return compareNumeric(x.msgs, x.sort_pos, y.msgs, y.sort_pos)}); }
function sortBySeen() { sortBy('seen', function(x,y){return compareNumeric(x.seen, x.sort_pos, y.seen, y.sort_pos)}); }
sortId = '';
sortByFunc = null;
sortAscending = true;
function resortTable() {
// make it a stable sort
for (var i = 0; i < PlanesOrdered.length; ++i) {
PlanesOrdered[i].sort_pos = i;
}
PlanesOrdered.sort(sortByFunc);
var tbody = document.getElementById('tableinfo').tBodies[0];
for (var i = 0; i < PlanesOrdered.length; ++i) {
tbody.appendChild(PlanesOrdered[i].tr);
}
}
function sortBy(id,sortfunc) {
if (id === sortId) {
sortAscending = !sortAscending; sortAscending = !sortAscending;
else { } else {
sortColumn = i;
sortNumeric = numeric;
sortAscending = true; sortAscending = true;
} }
if (sortAscending)
sortByFunc = sortfunc;
else
sortByFunc = function(x,y){return sortfunc(y,x);}
sortId = id;
resortTable(); resortTable();
return;
}
}
console.warn("column not found: " + colName);
}
function resortTable() {
sortTable('tableinfo', sortColumn, sortAscending, sortNumeric);
}
function sortTable(tableId, col, asc, numeric) {
//retrieve passed table element
var oTbl=document.getElementById(tableId).tBodies[0];
var aStore=[];
//loop through the rows, storing each one inro aStore
for (var i=0; i < oTbl.rows.length; ++i){
var oRow=oTbl.rows[i];
var sortKey;
if (numeric) {
sortKey = parseFloat(oRow.cells[col].textContent||oRow.cells[col].innerText);
if (isNaN(sortKey)) {
sortKey = -999999;
}
} else {
sortKey = String(oRow.cells[col].textContent||oRow.cells[col].innerText);
}
aStore.push([sortKey,oRow]);
}
if (numeric) { //numerical sort
aStore.sort(function(x,y){ return sortAscending ? x[0]-y[0] : y[0]-x[0]; });
} else { //alpha sort
aStore.sort();
if (!sortAscending) {
aStore.reverse();
}
}
//rewrite the table rows to the passed table element
for(var i=0,iLen=aStore.length;i<iLen;i++){
oTbl.appendChild(aStore[i][1]);
}
} }
function selectPlaneByHex(hex) { function selectPlaneByHex(hex) {
//console.log("select: " + hex);
// If SelectedPlane has something in it, clear out the selected // If SelectedPlane has something in it, clear out the selected
if (SelectedPlane != null) { if (SelectedPlane != null) {
Planes[SelectedPlane].is_selected = false; Planes[SelectedPlane].is_selected = false;
@ -573,6 +568,7 @@ function selectPlaneByHex(hex) {
if (Planes[SelectedPlane].marker) { if (Planes[SelectedPlane].marker) {
Planes[SelectedPlane].marker.setIcon(Planes[SelectedPlane].funcGetIcon()); Planes[SelectedPlane].marker.setIcon(Planes[SelectedPlane].funcGetIcon());
} }
Planes[SelectedPlane].tr.classList.remove("selected");
} }
// If we are clicking the same plane, we are deselected it. // If we are clicking the same plane, we are deselected it.
@ -585,11 +581,12 @@ function selectPlaneByHex(hex) {
Planes[SelectedPlane].funcUpdateLines(); Planes[SelectedPlane].funcUpdateLines();
Planes[SelectedPlane].marker.setIcon(Planes[SelectedPlane].funcGetIcon()); Planes[SelectedPlane].marker.setIcon(Planes[SelectedPlane].funcGetIcon());
} }
Planes[SelectedPlane].tr.classList.add("selected");
} else { } else {
SelectedPlane = null; SelectedPlane = null;
} }
refreshSelected(); refreshSelected();
refreshTableInfo();
} }
function resetMap() { function resetMap() {

View file

@ -9,7 +9,7 @@ div#sidebar_container { float: left; width: 410px; margin-left: -410px; height:
div#SpecialSquawkWarning { position: absolute; bottom: 25px; right: 430px; border: 2px solid red; div#SpecialSquawkWarning { position: absolute; bottom: 25px; right: 430px; border: 2px solid red;
background-color: #FFFFA3; opacity: 0.75; filter:alpha(opacity=75); padding: 5px; background-color: #FFFFA3; opacity: 0.75; filter:alpha(opacity=75); padding: 5px;
display: none; text-align: center; } text-align: center; }
table#optionsTabs { width: 100%; font-size: small; font-family: monospace; background-color: #ddd; table#optionsTabs { width: 100%; font-size: small; font-family: monospace; background-color: #ddd;
border: 1px; border-color: #000000;} border: 1px; border-color: #000000;}
@ -22,11 +22,13 @@ table#optionsTabs { width: 100%; font-size: small; font-family: monospace; backg
.squawk7700 { font-weight: bold; background-color: #ffff00; } .squawk7700 { font-weight: bold; background-color: #ffff00; }
.selected { background-color: #dddddd; } .selected { background-color: #dddddd; }
.plane_table_row { cursor: pointer; } .plane_table_row { cursor: pointer; }
.hidden { display: none; }
#selectedinfotitle { font-size: larger; } .infoblock_heading { font-size: larger; }
#selectedinfo { font-size: small; } .infoblock_heading a { text-decoration: none; color: blue; font-size: x-small;}
#selectedinfo a { text-decoration: none; color: blue; font-size: x-small;} .infoblock_body { font-size: small; }
#selectedinfo.dim { opacity: 0.3; filter:alpha(opacity=30); /* For IE8 and earlier */ }
.dim { opacity: 0.3; filter:alpha(opacity=30); /* For IE8 and earlier */ }
.pointer { cursor: pointer; } .pointer { cursor: pointer; }