From 43d29389f2a7cede0c4598bc5f9203fff1ccf5c1 Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Tue, 6 Jan 2015 20:15:25 +0000 Subject: [PATCH] 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.. --- public_html/config.js | 3 + public_html/gmap.html | 112 ++++++- public_html/planeObject.js | 154 ++++++--- public_html/script.js | 629 ++++++++++++++++++------------------- public_html/style.css | 12 +- 5 files changed, 536 insertions(+), 374 deletions(-) diff --git a/public_html/config.js b/public_html/config.js index f7d8e2e..7077a31 100644 --- a/public_html/config.js +++ b/public_html/config.js @@ -32,3 +32,6 @@ SiteCircles = true; // true or false (Only shown if SiteShow is true) // In nautical miles or km (depending settings value 'Metric') SiteCirclesDistances = new Array(100,150,200); + +// You can disable the clocks if you want here: +ShowClocks = false; diff --git a/public_html/gmap.html b/public_html/gmap.html index 9ab2dc2..1e8422b 100644 --- a/public_html/gmap.html +++ b/public_html/gmap.html @@ -49,16 +49,116 @@ -
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ DUMP1090 + + +
 [GitHub]
(no aircraft selected) 
  
Tracked aircraft (total): n/a
Tracked aircraft (with positions): n/a
+
+ +
-
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
ICAOFlightSquawkAltitudeSpeedDistanceTrackMsgsAge
+
-
- Squak 7x00 is reported and shown.
- This is most likely an error in reciving or decoding.
- Please do not call the local authorities, they already know about it if it is valid squak. +
diff --git a/public_html/planeObject.js b/public_html/planeObject.js index bbd14cc..1c27de4 100644 --- a/public_html/planeObject.js +++ b/public_html/planeObject.js @@ -19,7 +19,6 @@ var planeObject = { // GMap Details marker : null, markerColor : MarkerColor, - lines : [], tracklinesegs : [], last_position_time : null, @@ -39,41 +38,83 @@ var planeObject = { line : null, head_update : this.last_position_time, tail_update : this.last_position_time, - estimated : false }; + estimated : false, + ground : (this.altitude === "ground") + }; this.tracklinesegs.push(newseg); - } else { - 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; - } + 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); + + 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 @@ -161,9 +202,9 @@ var planeObject = { }, // TODO: Trigger actions of a selecting a plane - funcSelectPlane : function(selectedPlane){ - selectPlaneByHex(this.icao); - }, + selectPlane : function(){ + selectPlaneByHex(this.icao); + }, // Update our data funcUpdateData : function(receiver_now,data){ @@ -182,6 +223,7 @@ var planeObject = { if (typeof data.lat !== "undefined") { this.latitude = data.lat; this.longitude = data.lon; + this.seen_pos = data.seen_pos; this.last_position_time = receiver_now - data.seen_pos; } if (typeof data.flight !== "undefined") @@ -197,7 +239,7 @@ var planeObject = { this.marker.setMap(null); this.marker = null; } - this.funcClearLines(); + this.funcClearLine(); if (SelectedPlane == this.icao) { if (this.is_selected) { this.is_selected = false; @@ -216,7 +258,6 @@ var planeObject = { } this.marker = this.funcUpdateMarker(); - PlanesOnMap++; } }, @@ -230,14 +271,14 @@ var planeObject = { position: new google.maps.LatLng(this.latitude, this.longitude), map: GoogleMap, icon: this.funcGetIcon(), - visable: true + visible: true }); // This is so we can match icao address this.marker.icao = this.icao; // 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 @@ -261,12 +302,31 @@ var planeObject = { // console.log(" point " + j + " at " + seg.track.getAt(j).lat() + "," + seg.track.getAt(j).lng()); // } - seg.line = new google.maps.Polyline({ - strokeColor: (seg.estimated ? '#804040' : '#000000'), - strokeOpacity: 1.0, - strokeWeight: (seg.estimated ? 2 : 3), - map: GoogleMap, - path: seg.track }); + 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({ + 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 }); + } } } } diff --git a/public_html/script.js b/public_html/script.js index e5a1767..c71df5a 100644 --- a/public_html/script.js +++ b/public_html/script.js @@ -1,15 +1,14 @@ // Define our global variables var GoogleMap = null; var Planes = {}; -var PlanesOnMap = 0; -var PlanesOnTable = 0; -var PlanesToReap = 0; +var PlanesOrdered = []; var SelectedPlane = null; -var SpecialSquawk = false; -var sortColumn = 3; -var sortAscending = true; -var sortNumeric = true; +var EmergencySquawks = { + '7500' : 'Aircraft Hijacking', + '7600' : 'Radio Failure', + '7700' : 'General Emergency' +}; // Get current map settings CenterLat = Number(localStorage['CenterLat']) || CONST_CENTERLAT; @@ -19,39 +18,54 @@ ZoomLvl = Number(localStorage['ZoomLvl']) || CONST_ZOOMLVL; Dump1090Version = "unknown version"; RefreshInterval = 1000; +PlaneRowTemplate = null; + +TrackedAircraft = 0 +TrackedPositions = 0 + function fetchData() { $.getJSON('data/aircraft.json', function(data) { - PlanesOnMap = 0; - SpecialSquawk = false; - // Loop through all the planes in the data packet var now = data.now; var acs = data.aircraft; for (var j=0; j < acs.length; j++) { var ac = acs[j]; + var hex = ac.hex; + var plane = null; + // Do we already have this plane object in Planes? // If not make it. - if (Planes[ac.hex]) { - var plane = Planes[ac.hex]; + + if (Planes[hex]) { + plane = Planes[hex]; } else { - var plane = jQuery.extend(true, {}, planeObject); - Planes[ac.hex] = plane; + 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); } - // Set SpecialSquawk-value - if (ac.squawk == '7500' || ac.squawk == '7600' || ac.squawk == '7700') { - SpecialSquawk = true; - } - // Call the function update plane.funcUpdateData(now, ac); } - PlanesOnTable = acs.length; + refreshTableInfo(); + refreshSelected(); }); } function initialize() { + PlaneRowTemplate = document.getElementById("plane_row_template"); + + if (!ShowClocks) { + $('#timestamps').addClass('hidden'); + } + // Get receiver metadata, reconfigure using it, then continue // with initialization $.getJSON('data/receiver.json') @@ -72,6 +86,18 @@ function initialize() { // Initalizes the map and starts up our timers to call various functions 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 var mapTypeIds = []; for(var type in google.maps.MapTypeId) { @@ -186,15 +212,14 @@ function initialize_after_config() { }); // Add home marker if requested - if (SiteShow && (typeof SiteLat !== 'undefined' || typeof SiteLon !== 'undefined')) { - var siteMarker = new google.maps.LatLng(SiteLat, SiteLon); + if (SitePosition) { var markerImage = new google.maps.MarkerImage( 'http://maps.google.com/mapfiles/kml/pal4/icon57.png', new google.maps.Size(32, 32), // Image size new google.maps.Point(0, 0), // Origin point of image new google.maps.Point(16, 16)); // Position where marker should point var marker = new google.maps.Marker({ - position: siteMarker, + position: SitePosition, map: GoogleMap, icon: markerImage, title: 'My Radar Site', @@ -220,350 +245,320 @@ function initialize_after_config() { extendedInitalize(); // Setup our timer to poll from the server. - window.setInterval(function() { - fetchData(); - refreshTableInfo(); - refreshSelected(); - reaper(); - extendedPulse(); - }, RefreshInterval); + window.setInterval(fetchData, RefreshInterval); + window.setInterval(reaper, 60000); } // This looks for planes to reap out of the master Planes variable function reaper() { - PlanesToReap = 0; - // When did the reaper start? reaptime = new Date().getTime(); + + console.log("Reaping started.."); + // Loop the planes - for (var reap in Planes) { - // Is this plane possibly reapable? - if (Planes[reap].reapable == true) { - // Has it not been seen for 5 minutes? - // 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. - delete Planes[reap]; - } - PlanesToReap++; + var newPlanes = []; + for (var i = 0; i < PlanesOrdered.length; ++i) { + var plane = PlanesOrdered[i]; + if ((reaptime - plane.updated) > 300000) { + // Reap it. + console.log("Reaped " + plane.icao); + delete Planes[plane.icao]; + } else { + // Keep it. + newPlanes.push(plane); } }; + + PlanesOrdered = newPlanes; + refreshTableInfo(); + refreshSelected(); } // Refresh the detail window about the plane function refreshSelected() { - var selected = false; + var selected = false; if (typeof SelectedPlane !== 'undefined' && SelectedPlane != "ICAO" && SelectedPlane != null) { - selected = Planes[SelectedPlane]; - } - - var columns = 2; - var html = ''; - - if (selected) { - html += ''; - } else { - html += '
'; - } - - // Flight header line including squawk if needed - if (selected && selected.flight == "") { - html += ''; - - if (selected && selected.altitude !== null) { - if (selected.altitude === "ground") - html += ''; - else if (Metric) { - html += ''; - } else { - html += ''; - } - } else { - html += ''; + selected = Planes[SelectedPlane]; + } + + if (!selected) { + $('#dump1090_infoblock').removeClass('hidden'); + $('#dump1090_version').text(Dump1090Version); + $('#dump1090_total_ac').text(TrackedAircraft); + $('#dump1090_total_ac_positions').text(TrackedAircraftPositions); + $('#selected_infoblock').addClass('hidden'); + return; + } + + $('#dump1090_infoblock').addClass('hidden'); + $('#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 { + $('#selected_callsign').text('n/a (' + selected.icao + ')'); + $('#selected_links').addClass('hidden'); + } + + var emerg = document.getElementById('selected_emergency'); + if (selected.squawk in EmergencySquawks) { + emerg.className = 'squawk' + selected.squawk; + emerg.textContent = '\u00a0Squawking: ' + EmergencySquawks[selected.squawk] + '\u00a0'; + } else { + emerg.className = 'hidden'; } - - if (selected && selected.squawk != '0000') { - html += ''; - } else { - html += ''; - } - - html += ''; - - if (selected) { - html += ''; - } else { - html += ''; // Something is wrong if we are here - } - - html += ''; - html += ''; - - // 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) { - dist /= 1000; - } else { - dist /= 1852; - } - dist = (Math.round((dist)*10)/10).toFixed(1); - html += ''; - } // End of SiteShow + if (selected.altitude === null) + $("#selected_altitude").text("n/a"); + else if (selected.altitude === "ground") + $("#selected_altitude").text("on ground"); + else if (Metric) + $("#selected_altitude").text(Math.round(selected.altitude / 3.2828) + ' m'); + else + $("#selected_altitude").text(Math.round(selected.altitude) + ' ft'); + + if (selected.squawk === null || selected.squawk === '0000') { + $('#selected_squawk').text('n/a'); + } else { + $('#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 { - if (SiteShow) { - html += ''; - } else { - html += 'n/a'; - } + $('#selected_speed').text(Math.round(selected.speed) + ' kt'); } - html += '
N/A (' + - selected.icao + ')'; - } else if (selected && selected.flight != "") { - html += '
' + - selected.flight + ''; - } else { - html += '
DUMP1090 ' + Dump1090Version + ' [GitHub]'; - } - - if (selected && selected.squawk == 7500) { // Lets hope we never see this... Aircraft Hijacking - html += '  Squawking: Aircraft Hijacking '; - } else if (selected && selected.squawk == 7600) { // Radio Failure - html += '  Squawking: Radio Failure '; - } else if (selected && selected.squawk == 7700) { // General Emergency - html += '  Squawking: General Emergency '; - } else if (selected && selected.flight != '') { - html += ' [FR24]'; - html += ' [FlightStats]'; - html += ' [FlightAware]'; - } - html += '
Altitude: on ground
Altitude: ' + Math.round(selected.altitude / 3.2828) + ' m
Altitude: ' + selected.altitude + ' ft
Altitude: n/aSquawk: ' + selected.squawk + '
Squawk: n/a
Speed: ' - if (selected) { - if (Metric) { - html += Math.round(selected.speed * 1.852) + ' km/h'; - } else { - html += selected.speed + ' kt'; - } - } else { - html += 'n/a'; - } - html += 'ICAO (hex): ' + selected.icao + '
ICAO (hex): n/a
Track: ' - if (selected && selected.track !== null) { - html += selected.track + '°' + ' (' + trackLongName(selected.track) +')'; - } else { - html += 'n/a'; - } - html += ' 
Lat/Long: '; - if (selected && selected.latitude !== null) { - html += selected.latitude + ', ' + selected.longitude + '
Distance from Site: ' + dist + - (Metric ? ' km' : ' NM') + '
Distance from Site: n/a ' + - (Metric ? ' km' : ' NM') + '
'; - - document.getElementById('plane_detail').innerHTML = html; + $('#selected_icao').text(selected.icao); + + 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) { - var trackIndex = Math.floor((track+22.5) / 45) % 8; - if (trackIndex < 0) - return "n/a"; + var trackIndex = Math.floor((360 + track % 360 + 22.5) / 45) % 8; return ["N","NE","E","SE","S","SW","W","NW"][trackIndex]; } function trackLongName(track) { - var trackIndex = Math.floor((track+22.5) / 45); - if ((trackIndex < 0) || (trackIndex >= 8)) - return "n/a"; + var trackIndex = Math.floor((360 + track % 360 + 22.5) / 45) % 8; return ["North","Northeast","East","Southeast","South","Southwest","West","Northwest"][trackIndex]; } // Refeshes the larger table of all the planes -function refreshTableInfo() { - var html = ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - // Add distance column header to table if site coordinates are provided - if (SiteShow && (typeof SiteLat !== 'undefined' || typeof SiteLon !== 'undefined')) { - html += ''; - } - html += ''; - html += ''; - html += ''; +function format_altitude(alt) { + if (alt === null) + return ""; + else if (alt === "ground") + return "ground"; + else if (Metric) + return Math.round(alt / 3.2828); + else + return Math.round(alt); +} - for (var tablep in Planes) { - var tableplane = Planes[tablep] - if (!tableplane.reapable) { - var specialStyle = ""; - // Is this the plane we selected? - if (tableplane.icao == SelectedPlane) { - specialStyle += " selected"; - } - // Lets hope we never see this... Aircraft Hijacking - if (tableplane.squawk == 7500) { - specialStyle += " squawk7500"; - } - // Radio Failure - if (tableplane.squawk == 7600) { - specialStyle += " squawk7600"; - } - // Emergancy - if (tableplane.squawk == 7700) { - specialStyle += " squawk7700"; +function format_speed(speed) { + if (speed === null) + return ""; + else if (Metric) + return Math.round(speed * 1.852); + else + return Math.round(speed); +} + +function format_distance(dist) { + if (Metric) { + return (Math.round(dist/100) / 10).toFixed(1); + } else { + return (Math.round(dist/185.2) / 10).toFixed(1); + } +} + +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) + 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) - html += ''; - else - html += ''; - - html += ''; + tableplane.tr.className = classes; + // ICAO doesn't change + tableplane.tr.cells[1].textContent = (tableplane.flight !== null ? tableplane.flight : ""); + 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.flight !== null) - html += ''; - else - html += ''; - - if (tableplane.squawk !== null) - html += ''; - else - html += ''; - - if (tableplane.altitude === null) - html += ''; - else if (tableplane.altitude === "ground") - html += ''; - else if (Metric) - html += ''; - else - html += ''; - - if (tableplane.speed === null) - html += ''; - else if (Metric) - html += ''; - else - html += ''; + if (tableplane.latitude !== null) + ++TrackedAircraftPositions; // Add distance column to table if site coordinates are provided - if (SiteShow && (typeof SiteLat !== 'undefined' || typeof SiteLon !== 'undefined')) { - html += ''; + if (SitePosition !== null && tableplane.latitude !== null) { + var planeLatLon = new google.maps.LatLng(tableplane.latitude, tableplane.longitude); + var dist = google.maps.geometry.spherical.computeDistanceBetween (SitePosition, planeLatLon); + tableplane.tr.cells[5].textContent = format_distance(dist); + tableplane.sitedist = dist; + } else { + tableplane.tr.cells[5].textContent = ""; } - html += ''; - html += ''; - html += ''; - html += ''; + tableplane.tr.cells[6].textContent = (tableplane.track !== null ? tableplane.track : ""); + tableplane.tr.cells[7].textContent = tableplane.messages; + tableplane.tr.cells[8].textContent = tableplane.seen; } } - html += '
ICAOFlightSquawkAltitudeSpeedDistanceTrackMsgsSeen
' + tableplane.icao + '' + tableplane.flight + '' + tableplane.squawk + ' ground' + Math.round(tableplane.altitude / 3.2828) + '' + tableplane.altitude + ' ' + Math.round(tableplane.speed * 1.852) + '' + tableplane.speed + ''; - if (tableplane.latitude !== null) { - var siteLatLon = new google.maps.LatLng(SiteLat, SiteLon); - var planeLatLon = new google.maps.LatLng(tableplane.latitude, tableplane.longitude); - var dist = google.maps.geometry.spherical.computeDistanceBetween (siteLatLon, planeLatLon); - if (Metric) { - dist /= 1000; - } else { - dist /= 1852; - } - dist = (Math.round((dist)*10)/10).toFixed(1); - html += dist; - } - html += ''; - if (tableplane.track !== null) - html += tableplane.track; - html += '' + tableplane.messages + '' + tableplane.seen + '
'; - - document.getElementById('planes_table').innerHTML = html; - - if (SpecialSquawk) { - $('#SpecialSquawkWarning').css('display', 'inline'); + + if (show_squawk_warning) { + $("#SpecialSquawkWarning").removeClass('hidden'); } else { - $('#SpecialSquawkWarning').css('display', 'none'); - } - - // 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; - } + $("#SpecialSquawkWarning").addClass('hidden'); } - 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() { - 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) { - //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]); - } +function sortBy(id,sortfunc) { + if (id === sortId) { + sortAscending = !sortAscending; + } else { + sortAscending = true; + } - 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(); - } - } + if (sortAscending) + sortByFunc = sortfunc; + else + sortByFunc = function(x,y){return sortfunc(y,x);} - //rewrite the table rows to the passed table element - for(var i=0,iLen=aStore.length;i