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,41 +38,83 @@ 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 lastpos = lastseg.track.getAt(lastseg.track.getLength() - 1);
var elapsed = (this.last_position_time - lastseg.head_update);
if (elapsed > 5) {
// >5s gap in data, put an estimated segment in
//console.log(this.icao + " discontinuity seen: " + lastpos.lat() + "," + lastpos.lng() + " -> " + here.lat() + "," + here.lng());
var estseg = { track : new google.maps.MVCArray([lastpos, here]),
line : null,
estimated : true };
var newseg = { track : new google.maps.MVCArray([here,here]),
line : null,
head_update : this.last_position_time,
tail_update : this.last_position_time,
estimated : false };
this.tracklinesegs.push(estseg);
this.tracklinesegs.push(newseg);
} else if (elapsed > 0) {
// New position data
// We only retain some historical points, at 5+ second intervals,
// plus the most recent point
if (this.last_position_time - lastseg.tail_update >= 5) {
// enough time has elapsed; retain the last point and add a new one
//console.log(this.icao + " retain last point");
lastseg.track.push(here);
lastseg.tail_update = lastseg.head_update;
} else {
// replace the last point with the current position
lastseg.track.setAt(lastseg.track.getLength()-1, here);
}
lastseg.head_update = this.last_position_time;
}
} }
var lastseg = this.tracklinesegs[this.tracklinesegs.length - 1];
var lastpos = lastseg.track.getAt(lastseg.track.getLength() - 1);
var elapsed = (this.last_position_time - lastseg.head_update);
var new_data = (here !== lastpos);
var est_track = (elapsed > 5);
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,
head_update : this.last_position_time,
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,
head_update : this.last_position_time,
tail_update : this.last_position_time,
estimated : false,
ground : (this.altitude === "ground") });
return;
}
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,
// plus the most recent point
if (this.last_position_time - lastseg.tail_update >= 5) {
// enough time has elapsed; retain the last point and add a new one
//console.log(this.icao + " retain last point");
lastseg.track.push(here);
lastseg.tail_update = lastseg.head_update;
} else {
// replace the last point with the current position
lastseg.track.setAt(lastseg.track.getLength()-1, here);
}
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,9 +202,9 @@ 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);
}, },
// Update our data // Update our data
funcUpdateData : function(receiver_now,data){ funcUpdateData : function(receiver_now,data){
@ -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());
// } // }
seg.line = new google.maps.Polyline({ if (seg.estimated) {
strokeColor: (seg.estimated ? '#804040' : '#000000'), var lineSymbol = {
strokeOpacity: 1.0, path: 'M 0,-1 0,1',
strokeWeight: (seg.estimated ? 2 : 3), strokeOpacity : 1,
map: GoogleMap, strokeColor : '#804040',
path: seg.track }); strokeWeight : 2,
scale: 2
};
seg.line = new google.maps.Polyline({
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,
strokeColor: (seg.ground ? '#408040' : '#000000'),
strokeWeight: 3,
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]; if (Planes[hex]) {
plane = Planes[hex];
} else { } else {
var plane = jQuery.extend(true, {}, planeObject); plane = jQuery.extend(true, {}, planeObject);
Planes[ac.hex] = plane;
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);
} }
// Set SpecialSquawk-value
if (ac.squawk == '7500' || ac.squawk == '7600' || ac.squawk == '7700') {
SpecialSquawk = true;
}
// 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,350 +245,320 @@ 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 // Reap it.
// Due to loss of signal or other reasons console.log("Reaped " + plane.icao);
if ((reaptime - Planes[reap].updated) > 300000) { delete Planes[plane.icao];
// Reap it. } else {
delete Planes[reap]; // Keep it.
} newPlanes.push(plane);
PlanesToReap++;
} }
}; };
PlanesOrdered = newPlanes;
refreshTableInfo();
refreshSelected();
} }
// Refresh the detail window about the plane // Refresh the detail window about the plane
function refreshSelected() { function refreshSelected() {
var selected = false; var selected = false;
if (typeof SelectedPlane !== 'undefined' && SelectedPlane != "ICAO" && SelectedPlane != null) { if (typeof SelectedPlane !== 'undefined' && SelectedPlane != "ICAO" && SelectedPlane != null) {
selected = Planes[SelectedPlane]; selected = Planes[SelectedPlane];
} }
var columns = 2; if (!selected) {
var html = ''; $('#dump1090_infoblock').removeClass('hidden');
$('#dump1090_version').text(Dump1090Version);
if (selected) { $('#dump1090_total_ac').text(TrackedAircraft);
html += '<table id="selectedinfo" width="100%">'; $('#dump1090_total_ac_positions').text(TrackedAircraftPositions);
} else { $('#selected_infoblock').addClass('hidden');
html += '<table id="selectedinfo" class="dim" width="100%">'; return;
} }
// Flight header line including squawk if needed $('#dump1090_infoblock').addClass('hidden');
if (selected && selected.flight == "") { $('#selected_infoblock').removeClass('hidden');
html += '<tr><td colspan="' + columns + '" id="selectedinfotitle"><b>N/A (' +
selected.icao + ')</b>'; if (selected.flight !== null && selected.flight !== "") {
} else if (selected && selected.flight != "") { $('#selected_callsign').text(selected.flight);
html += '<tr><td colspan="' + columns + '" id="selectedinfotitle"><b>' + $('#selected_links').removeClass('hidden');
selected.flight + '</b>'; $('#selected_fr24_link').attr('href','http://fr24.com/'+selected.flight);
} else { $('#selected_flightstats_link').attr('href','http://www.flightstats.com/go/FlightStatus/flightStatusByFlight.do?flightNumber='+selected.flight);
html += '<tr><td colspan="' + columns + '" id="selectedinfotitle"><b>DUMP1090 ' + Dump1090Version + '</b>&nbsp;<a href="https://github.com/mutability/dump1090" target="_blank">[GitHub]</a>'; $('#selected_flightaware_link').attr('href','http://flightaware.com/live/flight/'+selected.flight);
} } else {
$('#selected_callsign').text('n/a (' + selected.icao + ')');
if (selected && selected.squawk == 7500) { // Lets hope we never see this... Aircraft Hijacking $('#selected_links').addClass('hidden');
html += '&nbsp;<span class="squawk7500">&nbsp;Squawking: Aircraft Hijacking&nbsp;</span>'; }
} else if (selected && selected.squawk == 7600) { // Radio Failure
html += '&nbsp;<span class="squawk7600">&nbsp;Squawking: Radio Failure&nbsp;</span>'; var emerg = document.getElementById('selected_emergency');
} else if (selected && selected.squawk == 7700) { // General Emergency if (selected.squawk in EmergencySquawks) {
html += '&nbsp;<span class="squawk7700">&nbsp;Squawking: General Emergency&nbsp;</span>'; emerg.className = 'squawk' + selected.squawk;
} else if (selected && selected.flight != '') { emerg.textContent = '\u00a0Squawking: ' + EmergencySquawks[selected.squawk] + '\u00a0';
html += '&nbsp;<a href="http://fr24.com/'+selected.flight+'" target="_blank">[FR24]</a>'; } else {
html += '&nbsp;<a href="http://www.flightstats.com/go/FlightStatus/flightStatusByFlight.do?'; emerg.className = 'hidden';
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.altitude === "ground")
html += '<tr><td>Altitude: on ground</td>';
else if (Metric) {
html += '<tr><td>Altitude: ' + Math.round(selected.altitude / 3.2828) + ' m</td>';
} else {
html += '<tr><td>Altitude: ' + selected.altitude + ' ft</td>';
}
} else {
html += '<tr><td>Altitude: n/a</td>';
} }
if (selected && selected.squawk != '0000') {
html += '<td>Squawk: ' + selected.squawk + '</td></tr>';
} else {
html += '<td>Squawk: n/a</td></tr>';
}
html += '<tr><td>Speed: '
if (selected) {
if (Metric) {
html += Math.round(selected.speed * 1.852) + ' km/h';
} else {
html += selected.speed + ' kt';
}
} else {
html += 'n/a';
}
html += '</td>';
if (selected) {
html += '<td>ICAO (hex): ' + selected.icao + '</td></tr>';
} else {
html += '<td>ICAO (hex): n/a</td></tr>'; // Something is wrong if we are here
}
html += '<tr><td>Track: '
if (selected && selected.track !== null) {
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.altitude === null)
if (selected && selected.latitude !== null) { $("#selected_altitude").text("n/a");
html += selected.latitude + ', ' + selected.longitude + '</td></tr>'; else if (selected.altitude === "ground")
$("#selected_altitude").text("on ground");
// Let's show some extra data if we have site coordinates else if (Metric)
if (SiteShow) { $("#selected_altitude").text(Math.round(selected.altitude / 3.2828) + ' m');
var siteLatLon = new google.maps.LatLng(SiteLat, SiteLon); else
var planeLatLon = new google.maps.LatLng(selected.latitude, selected.longitude); $("#selected_altitude").text(Math.round(selected.altitude) + ' ft');
var dist = google.maps.geometry.spherical.computeDistanceBetween (siteLatLon, planeLatLon);
if (selected.squawk === null || selected.squawk === '0000') {
if (Metric) { $('#selected_squawk').text('n/a');
dist /= 1000; } else {
} else { $('#selected_squawk').text(selected.squawk);
dist /= 1852; }
}
dist = (Math.round((dist)*10)/10).toFixed(1); if (selected.speed === null) {
html += '<tr><td colspan="' + columns + '" align="center">Distance from Site: ' + dist + $('#selected_speed').text('n/a');
(Metric ? ' km' : ' NM') + '</td></tr>'; } else if (Metric) {
} // End of SiteShow $('#selected_speed').text(Math.round(selected.speed * 1.852) + ' km/h');
} else { } else {
if (SiteShow) { $('#selected_speed').text(Math.round(selected.speed) + ' kt');
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_icao').text(selected.icao);
document.getElementById('plane_detail').innerHTML = html; if (selected.track === null) {
$('#selected_track').text('n/a');
} else {
$('#selected_track').text(selected.track + '\u00b0' + ' (' + trackLongName(selected.track) + ')');
}
if (selected.seen <= 1) {
$('#selected_seen').text('now');
} else {
$('#selected_seen').text(selected.seen + 's ago');
}
if (selected.latitude === null) {
$('#selected_position').text('n/a');
} else {
if (selected.seen_pos > 1) {
$('#selected_position').text(selected.latitude + ', ' + selected.longitude + " (" + selected.seen_pos + "s ago)");
} else {
$('#selected_position').text(selected.latitude + ', ' + selected.longitude);
}
}
if (selected.sitedist !== null) {
var dist = selected.sitedist;
if (Metric) {
dist /= 1000;
} else {
dist /= 1852;
}
dist = (Math.round((dist)*10)/10).toFixed(1);
$('#selected_sitedist').text(dist + (Metric ? ' km' : ' NM'));
} else {
$('#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);
// Radio Failure } else {
if (tableplane.squawk == 7600) { return (Math.round(dist/185.2) / 10).toFixed(1);
specialStyle += " squawk7600"; }
} }
// Emergancy
if (tableplane.squawk == 7700) { function refreshTableInfo() {
specialStyle += " squawk7700"; 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)
classes += " vPosition";
if (tableplane.icao == SelectedPlane)
classes += " selected";
if (tableplane.squawk in EmergencySquawks) {
classes += ' squawk' + tableplane.squawk;
show_squawk_warning = true;
} }
if (tableplane.latitude !== null) tableplane.tr.className = classes;
html += '<tr class="plane_table_row vPosition' + specialStyle + '">'; // ICAO doesn't change
else tableplane.tr.cells[1].textContent = (tableplane.flight !== null ? tableplane.flight : "");
html += '<tr class="plane_table_row ' + specialStyle + '">'; tableplane.tr.cells[2].textContent = (tableplane.squawk !== null ? tableplane.squawk : "");
tableplane.tr.cells[3].textContent = format_altitude(tableplane.altitude);
html += '<td>' + tableplane.icao + '</td>'; tableplane.tr.cells[4].textContent = format_speed(tableplane.speed);
if (tableplane.flight !== null) if (tableplane.latitude !== null)
html += '<td>' + tableplane.flight + '</td>'; ++TrackedAircraftPositions;
else
html += '<td></td>';
if (tableplane.squawk !== null)
html += '<td align="right">' + tableplane.squawk + '</td>';
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">'; var planeLatLon = new google.maps.LatLng(tableplane.latitude, tableplane.longitude);
if (tableplane.latitude !== null) { var dist = google.maps.geometry.spherical.computeDistanceBetween (SitePosition, planeLatLon);
var siteLatLon = new google.maps.LatLng(SiteLat, SiteLon); tableplane.tr.cells[5].textContent = format_distance(dist);
var planeLatLon = new google.maps.LatLng(tableplane.latitude, tableplane.longitude); tableplane.sitedist = dist;
var dist = google.maps.geometry.spherical.computeDistanceBetween (siteLatLon, planeLatLon); } else {
if (Metric) { tableplane.tr.cells[5].textContent = "";
dist /= 1000;
} else {
dist /= 1852;
}
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>';
if (show_squawk_warning) {
document.getElementById('planes_table').innerHTML = html; $("#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();
}
function sortBy(colName,numeric) {
var header_cells = document.getElementById('tableinfo').tHead.rows[0].cells;
for (var i = 0; i < header_cells.length; ++i) {
if (header_cells[i].id === colName) {
if (i == sortColumn)
sortAscending = !sortAscending;
else {
sortColumn = i;
sortNumeric = numeric;
sortAscending = true;
}
resortTable();
return;
}
} }
console.warn("column not found: " + colName); resortTable();
} }
//
// ---- table sorting ----
//
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() { function resortTable() {
sortTable('tableinfo', sortColumn, sortAscending, sortNumeric); // 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 sortTable(tableId, col, asc, numeric) { function sortBy(id,sortfunc) {
//retrieve passed table element if (id === sortId) {
var oTbl=document.getElementById(tableId).tBodies[0]; sortAscending = !sortAscending;
var aStore=[]; } else {
sortAscending = true;
//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 if (sortAscending)
aStore.sort(function(x,y){ return sortAscending ? x[0]-y[0] : y[0]-x[0]; }); sortByFunc = sortfunc;
} else { //alpha sort else
aStore.sort(); sortByFunc = function(x,y){return sortfunc(y,x);}
if (!sortAscending) {
aStore.reverse();
}
}
//rewrite the table rows to the passed table element sortId = id;
for(var i=0,iLen=aStore.length;i<iLen;i++){ resortTable();
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();
refreshTableInfo(); refreshSelected();
} }
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; }