2015-02-22 12:51:58 +01:00
|
|
|
// -*- mode: javascript; indent-tabs-mode: nil; c-basic-offset: 8 -*-
|
2015-01-07 18:35:32 +01:00
|
|
|
"use strict";
|
|
|
|
|
2013-05-21 22:28:16 +02:00
|
|
|
// Define our global variables
|
2016-07-02 13:55:38 +02:00
|
|
|
var OLMap = null;
|
|
|
|
var StaticFeatures = new ol.Collection();
|
|
|
|
var PlaneIconFeatures = new ol.Collection();
|
|
|
|
var PlaneTrailFeatures = new ol.Collection();
|
2013-05-21 22:28:16 +02:00
|
|
|
var Planes = {};
|
2015-01-06 21:15:25 +01:00
|
|
|
var PlanesOrdered = [];
|
2013-05-21 22:28:16 +02:00
|
|
|
var SelectedPlane = null;
|
2015-01-22 15:17:12 +01:00
|
|
|
var FollowSelected = false;
|
2013-05-18 23:54:59 +02:00
|
|
|
|
2015-01-07 17:32:58 +01:00
|
|
|
var SpecialSquawks = {
|
2016-03-31 21:09:12 +02:00
|
|
|
'7500' : { cssClass: 'squawk7500', markerColor: 'rgb(255, 85, 85)', text: 'Aircraft Hijacking' },
|
|
|
|
'7600' : { cssClass: 'squawk7600', markerColor: 'rgb(0, 255, 255)', text: 'Radio Failure' },
|
|
|
|
'7700' : { cssClass: 'squawk7700', markerColor: 'rgb(255, 255, 0)', text: 'General Emergency' }
|
2015-01-06 21:15:25 +01:00
|
|
|
};
|
2013-05-09 16:59:26 +02:00
|
|
|
|
2013-05-25 10:56:32 +02:00
|
|
|
// Get current map settings
|
2016-07-02 15:44:14 +02:00
|
|
|
var CenterLat, CenterLon, ZoomLvl;
|
2013-05-25 10:56:32 +02:00
|
|
|
|
2015-01-07 17:32:58 +01:00
|
|
|
var Dump1090Version = "unknown version";
|
|
|
|
var RefreshInterval = 1000;
|
2014-12-10 18:05:22 +01:00
|
|
|
|
2015-01-07 17:32:58 +01:00
|
|
|
var PlaneRowTemplate = null;
|
2015-01-06 21:15:25 +01:00
|
|
|
|
2015-01-07 17:32:58 +01:00
|
|
|
var TrackedAircraft = 0;
|
2015-01-07 18:35:32 +01:00
|
|
|
var TrackedAircraftPositions = 0;
|
|
|
|
var TrackedHistorySize = 0;
|
|
|
|
|
|
|
|
var SitePosition = null;
|
2015-01-06 21:15:25 +01:00
|
|
|
|
2015-01-07 19:39:19 +01:00
|
|
|
var ReceiverClock = null;
|
2015-01-07 19:43:08 +01:00
|
|
|
|
2015-02-11 01:15:48 +01:00
|
|
|
var LastReceiverTimestamp = 0;
|
2015-01-07 19:43:08 +01:00
|
|
|
var StaleReceiverCount = 0;
|
|
|
|
var FetchPending = null;
|
|
|
|
|
2015-01-13 20:10:55 +01:00
|
|
|
var MessageCountHistory = [];
|
2015-02-22 12:51:58 +01:00
|
|
|
var MessageRate = 0;
|
2015-01-13 20:10:55 +01:00
|
|
|
|
2015-01-11 13:02:25 +01:00
|
|
|
var NBSP='\u00a0';
|
|
|
|
|
2015-01-15 22:01:14 +01:00
|
|
|
function processReceiverUpdate(data) {
|
2016-03-31 21:09:12 +02:00
|
|
|
// Loop through all the planes in the data packet
|
|
|
|
var now = data.now;
|
|
|
|
var acs = data.aircraft;
|
|
|
|
|
|
|
|
// Detect stats reset
|
|
|
|
if (MessageCountHistory.length > 0 && MessageCountHistory[MessageCountHistory.length-1].messages > data.messages) {
|
|
|
|
MessageCountHistory = [{'time' : MessageCountHistory[MessageCountHistory.length-1].time,
|
|
|
|
'messages' : 0}];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note the message count in the history
|
|
|
|
MessageCountHistory.push({ 'time' : now, 'messages' : data.messages});
|
|
|
|
// .. and clean up any old values
|
|
|
|
if ((now - MessageCountHistory[0].time) > 30)
|
|
|
|
MessageCountHistory.shift();
|
|
|
|
|
|
|
|
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[hex]) {
|
|
|
|
plane = Planes[hex];
|
|
|
|
} else {
|
|
|
|
plane = new PlaneObject(hex);
|
|
|
|
plane.tr = PlaneRowTemplate.cloneNode(true);
|
|
|
|
|
|
|
|
if (hex[0] === '~') {
|
|
|
|
// Non-ICAO address
|
|
|
|
plane.tr.cells[0].textContent = hex.substring(1);
|
|
|
|
$(plane.tr).css('font-style', 'italic');
|
|
|
|
} else {
|
|
|
|
plane.tr.cells[0].textContent = hex;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set flag image if available
|
|
|
|
if (ShowFlags && plane.icaorange.flag_image !== null) {
|
|
|
|
$('img', plane.tr.cells[1]).attr('src', FlagPath + plane.icaorange.flag_image);
|
|
|
|
$('img', plane.tr.cells[1]).attr('title', plane.icaorange.country);
|
|
|
|
} else {
|
|
|
|
$('img', plane.tr.cells[1]).css('display', 'none');
|
|
|
|
}
|
|
|
|
|
|
|
|
plane.tr.addEventListener('click', selectPlaneByHex.bind(undefined,hex,false));
|
|
|
|
plane.tr.addEventListener('dblclick', selectPlaneByHex.bind(undefined,hex,true));
|
|
|
|
|
|
|
|
Planes[hex] = plane;
|
|
|
|
PlanesOrdered.push(plane);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call the function update
|
|
|
|
plane.updateData(now, ac);
|
|
|
|
}
|
2015-01-15 22:01:14 +01:00
|
|
|
}
|
|
|
|
|
2013-05-21 22:28:16 +02:00
|
|
|
function fetchData() {
|
2016-03-31 21:09:12 +02:00
|
|
|
if (FetchPending !== null && FetchPending.state() == 'pending') {
|
|
|
|
// don't double up on fetches, let the last one resolve
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
FetchPending = $.ajax({ url: 'data/aircraft.json',
|
|
|
|
timeout: 5000,
|
|
|
|
cache: false,
|
|
|
|
dataType: 'json' });
|
|
|
|
FetchPending.done(function(data) {
|
|
|
|
var now = data.now;
|
|
|
|
|
|
|
|
processReceiverUpdate(data);
|
|
|
|
|
|
|
|
// update timestamps, visibility, history track for all planes - not only those updated
|
|
|
|
for (var i = 0; i < PlanesOrdered.length; ++i) {
|
|
|
|
var plane = PlanesOrdered[i];
|
|
|
|
plane.updateTick(now, LastReceiverTimestamp);
|
|
|
|
}
|
|
|
|
|
|
|
|
refreshTableInfo();
|
|
|
|
refreshSelected();
|
|
|
|
|
|
|
|
if (ReceiverClock) {
|
|
|
|
var rcv = new Date(now * 1000);
|
|
|
|
ReceiverClock.render(rcv.getUTCHours(),rcv.getUTCMinutes(),rcv.getUTCSeconds());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for stale receiver data
|
|
|
|
if (LastReceiverTimestamp === now) {
|
|
|
|
StaleReceiverCount++;
|
|
|
|
if (StaleReceiverCount > 5) {
|
|
|
|
$("#update_error_detail").text("The data from dump1090 hasn't been updated in a while. Maybe dump1090 is no longer running?");
|
|
|
|
$("#update_error").css('display','block');
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
StaleReceiverCount = 0;
|
|
|
|
LastReceiverTimestamp = now;
|
|
|
|
$("#update_error").css('display','none');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
FetchPending.fail(function(jqxhr, status, error) {
|
|
|
|
$("#update_error_detail").text("AJAX call failed (" + status + (error ? (": " + error) : "") + "). Maybe dump1090 is no longer running?");
|
|
|
|
$("#update_error").css('display','block');
|
|
|
|
});
|
2013-05-09 16:59:26 +02:00
|
|
|
}
|
|
|
|
|
2015-01-15 22:01:14 +01:00
|
|
|
var PositionHistorySize = 0;
|
2013-05-21 22:28:16 +02:00
|
|
|
function initialize() {
|
2016-03-31 21:09:12 +02:00
|
|
|
// Set page basics
|
|
|
|
document.title = PageName;
|
|
|
|
$("#infoblock_name").text(PageName);
|
|
|
|
|
|
|
|
PlaneRowTemplate = document.getElementById("plane_row_template");
|
|
|
|
|
|
|
|
if (!ShowClocks) {
|
|
|
|
$('#timestamps').css('display','none');
|
|
|
|
} else {
|
|
|
|
// Create the clocks.
|
|
|
|
new CoolClock({
|
|
|
|
canvasId: "utcclock",
|
|
|
|
skinId: "classic",
|
|
|
|
displayRadius: 40,
|
|
|
|
showSecondHand: true,
|
|
|
|
gmtOffset: "0", // this has to be a string!
|
|
|
|
showDigital: false,
|
|
|
|
logClock: false,
|
|
|
|
logClockRev: false
|
|
|
|
});
|
|
|
|
|
|
|
|
ReceiverClock = new CoolClock({
|
|
|
|
canvasId: "receiverclock",
|
|
|
|
skinId: "classic",
|
|
|
|
displayRadius: 40,
|
|
|
|
showSecondHand: true,
|
|
|
|
gmtOffset: null,
|
|
|
|
showDigital: false,
|
|
|
|
logClock: false,
|
|
|
|
logClockRev: false
|
|
|
|
});
|
|
|
|
|
|
|
|
// disable ticking on the receiver clock, we will update it ourselves
|
|
|
|
ReceiverClock.tick = (function(){})
|
|
|
|
}
|
|
|
|
|
|
|
|
$("#loader").removeClass("hidden");
|
|
|
|
|
|
|
|
// Get receiver metadata, reconfigure using it, then continue
|
|
|
|
// with initialization
|
|
|
|
$.ajax({ url: 'data/receiver.json',
|
|
|
|
timeout: 5000,
|
|
|
|
cache: false,
|
|
|
|
dataType: 'json' })
|
|
|
|
|
|
|
|
.done(function(data) {
|
|
|
|
if (typeof data.lat !== "undefined") {
|
|
|
|
SiteShow = true;
|
|
|
|
SiteLat = data.lat;
|
|
|
|
SiteLon = data.lon;
|
|
|
|
DefaultCenterLat = data.lat;
|
|
|
|
DefaultCenterLon = data.lon;
|
|
|
|
}
|
|
|
|
|
|
|
|
Dump1090Version = data.version;
|
|
|
|
RefreshInterval = data.refresh;
|
|
|
|
PositionHistorySize = data.history;
|
|
|
|
})
|
|
|
|
|
|
|
|
.always(function() {
|
|
|
|
initialize_map();
|
|
|
|
start_load_history();
|
|
|
|
});
|
2015-01-15 22:01:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var CurrentHistoryFetch = null;
|
|
|
|
var PositionHistoryBuffer = []
|
|
|
|
function start_load_history() {
|
2016-03-31 21:09:12 +02:00
|
|
|
if (PositionHistorySize > 0) {
|
|
|
|
$("#loader_progress").attr('max',PositionHistorySize);
|
|
|
|
console.log("Starting to load history (" + PositionHistorySize + " items)");
|
|
|
|
load_history_item(0);
|
|
|
|
} else {
|
|
|
|
end_load_history();
|
|
|
|
}
|
2015-01-15 22:01:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function load_history_item(i) {
|
2016-03-31 21:09:12 +02:00
|
|
|
if (i >= PositionHistorySize) {
|
|
|
|
end_load_history();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log("Loading history #" + i);
|
|
|
|
$("#loader_progress").attr('value',i);
|
|
|
|
|
|
|
|
$.ajax({ url: 'data/history_' + i + '.json',
|
|
|
|
timeout: 5000,
|
|
|
|
cache: false,
|
|
|
|
dataType: 'json' })
|
|
|
|
|
|
|
|
.done(function(data) {
|
|
|
|
PositionHistoryBuffer.push(data);
|
|
|
|
load_history_item(i+1);
|
|
|
|
})
|
|
|
|
|
|
|
|
.fail(function(jqxhr, status, error) {
|
|
|
|
// No more history
|
|
|
|
end_load_history();
|
|
|
|
});
|
2015-01-15 22:01:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function end_load_history() {
|
2016-03-31 21:09:12 +02:00
|
|
|
$("#loader").addClass("hidden");
|
2015-01-15 22:01:14 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
console.log("Done loading history");
|
2015-01-15 22:01:14 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
if (PositionHistoryBuffer.length > 0) {
|
|
|
|
var now, last=0;
|
2015-01-15 22:01:14 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
// Sort history by timestamp
|
|
|
|
console.log("Sorting history");
|
|
|
|
PositionHistoryBuffer.sort(function(x,y) { return (x.now - y.now); });
|
2015-01-15 22:01:14 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
// Process history
|
|
|
|
for (var h = 0; h < PositionHistoryBuffer.length; ++h) {
|
|
|
|
now = PositionHistoryBuffer[h].now;
|
|
|
|
console.log("Applying history " + h + "/" + PositionHistoryBuffer.length + " at: " + now);
|
|
|
|
processReceiverUpdate(PositionHistoryBuffer[h]);
|
2015-01-15 22:01:14 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
// update track
|
|
|
|
console.log("Updating tracks at: " + now);
|
|
|
|
for (var i = 0; i < PlanesOrdered.length; ++i) {
|
|
|
|
var plane = PlanesOrdered[i];
|
|
|
|
plane.updateTrack((now - last) + 1);
|
|
|
|
}
|
2015-01-15 22:01:14 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
last = now;
|
|
|
|
}
|
2015-01-15 22:01:14 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
// Final pass to update all planes to their latest state
|
|
|
|
console.log("Final history cleanup pass");
|
|
|
|
for (var i = 0; i < PlanesOrdered.length; ++i) {
|
|
|
|
var plane = PlanesOrdered[i];
|
|
|
|
plane.updateTick(now);
|
|
|
|
}
|
2015-02-11 01:15:48 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
LastReceiverTimestamp = last;
|
|
|
|
}
|
2015-01-15 22:01:14 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
PositionHistoryBuffer = null;
|
2015-01-15 22:01:14 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
console.log("Completing init");
|
2015-01-15 22:01:14 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
refreshTableInfo();
|
|
|
|
refreshSelected();
|
|
|
|
reaper();
|
2015-01-15 22:01:14 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
// Setup our timer to poll from the server.
|
|
|
|
window.setInterval(fetchData, RefreshInterval);
|
|
|
|
window.setInterval(reaper, 60000);
|
2015-01-15 22:01:14 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
// And kick off one refresh immediately.
|
|
|
|
fetchData();
|
2015-02-11 01:15:21 +01:00
|
|
|
|
2014-12-10 18:05:22 +01:00
|
|
|
}
|
|
|
|
|
2015-10-26 13:59:38 +01:00
|
|
|
function generic_gettile(template, coord, zoom) {
|
2016-03-31 21:09:12 +02:00
|
|
|
return template.replace('{x}', coord.x).replace('{y}', coord.y).replace('{z}', zoom)
|
2015-10-26 13:59:38 +01:00
|
|
|
}
|
|
|
|
|
2014-12-10 18:05:22 +01:00
|
|
|
// Initalizes the map and starts up our timers to call various functions
|
2015-01-15 22:01:14 +01:00
|
|
|
function initialize_map() {
|
2016-03-31 21:09:12 +02:00
|
|
|
// Load stored map settings if present
|
|
|
|
CenterLat = Number(localStorage['CenterLat']) || DefaultCenterLat;
|
|
|
|
CenterLon = Number(localStorage['CenterLon']) || DefaultCenterLon;
|
|
|
|
ZoomLvl = Number(localStorage['ZoomLvl']) || DefaultZoomLvl;
|
|
|
|
|
|
|
|
// Set SitePosition, initialize sorting
|
|
|
|
if (SiteShow && (typeof SiteLat !== 'undefined') && (typeof SiteLon !== 'undefined')) {
|
2016-07-02 13:55:38 +02:00
|
|
|
SitePosition = [SiteLon, SiteLat];
|
2016-03-31 21:09:12 +02:00
|
|
|
sortByDistance();
|
|
|
|
} else {
|
|
|
|
SitePosition = null;
|
|
|
|
PlaneRowTemplate.cells[6].style.display = 'none'; // hide distance column
|
|
|
|
document.getElementById("distance").style.display = 'none'; // hide distance header
|
|
|
|
sortByAltitude();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Maybe hide flag info
|
|
|
|
if (!ShowFlags) {
|
|
|
|
PlaneRowTemplate.cells[1].style.display = 'none'; // hide flag column
|
|
|
|
document.getElementById("flag").style.display = 'none'; // hide flag header
|
|
|
|
document.getElementById("infoblock_country").style.display = 'none'; // hide country row
|
|
|
|
}
|
|
|
|
|
2016-07-02 13:55:38 +02:00
|
|
|
// Initialize OL3
|
|
|
|
// TODO map types etc
|
2016-03-31 21:09:12 +02:00
|
|
|
|
2016-07-02 13:55:38 +02:00
|
|
|
var rasterLayer = new ol.layer.Tile({
|
|
|
|
source: new ol.source.OSM()
|
|
|
|
});
|
2016-03-31 21:09:12 +02:00
|
|
|
|
2016-07-02 13:55:38 +02:00
|
|
|
var staticLayer = new ol.layer.Vector({
|
|
|
|
source: new ol.source.Vector({features: StaticFeatures})
|
|
|
|
});
|
2016-03-31 21:09:12 +02:00
|
|
|
|
2016-07-02 13:55:38 +02:00
|
|
|
var trailsLayer = new ol.layer.Vector({
|
|
|
|
source: new ol.source.Vector({features: PlaneTrailFeatures})
|
|
|
|
});
|
|
|
|
|
|
|
|
var iconsLayer = new ol.layer.Vector({
|
|
|
|
source: new ol.source.Vector({features: PlaneIconFeatures})
|
|
|
|
});
|
|
|
|
|
|
|
|
OLMap = new ol.Map({
|
|
|
|
target: 'map_canvas',
|
|
|
|
layers: [rasterLayer, staticLayer, trailsLayer, iconsLayer],
|
|
|
|
view: new ol.View({
|
|
|
|
center: ol.proj.fromLonLat([CenterLon, CenterLat]),
|
|
|
|
zoom: ZoomLvl
|
|
|
|
})
|
|
|
|
});
|
2016-03-31 21:09:12 +02:00
|
|
|
|
|
|
|
// Listeners for newly created Map
|
2016-07-02 13:55:38 +02:00
|
|
|
OLMap.getView().on('change:center', function(event) {
|
|
|
|
var center = ol.proj.toLonLat(OLMap.getView().getCenter(), OLMap.getView().getProjection());
|
|
|
|
localStorage['CenterLon'] = center[0]
|
|
|
|
localStorage['CenterLat'] = center[1]
|
2016-03-31 21:09:12 +02:00
|
|
|
if (FollowSelected) {
|
|
|
|
// On manual navigation, disable follow
|
|
|
|
var selected = Planes[SelectedPlane];
|
2016-07-02 13:55:38 +02:00
|
|
|
if (Math.abs(center[0] - selected.position[0]) > 0.0001 &&
|
|
|
|
Math.abs(center[1] - selected.position[1]) > 0.0001) {
|
2016-03-31 21:09:12 +02:00
|
|
|
FollowSelected = false;
|
|
|
|
refreshSelected();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-07-02 13:55:38 +02:00
|
|
|
OLMap.getView().on('change:resolution', function(event) {
|
|
|
|
localStorage['ZoomLvl'] = OLMap.getView().getZoom();
|
2016-03-31 21:09:12 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
// Add home marker if requested
|
|
|
|
if (SitePosition) {
|
2016-07-02 13:55:38 +02:00
|
|
|
console.log("setting up site position");
|
|
|
|
var markerStyle = new ol.style.Style({
|
|
|
|
image: new ol.style.Circle({
|
|
|
|
radius: 7,
|
|
|
|
snapToPixel: false,
|
|
|
|
fill: new ol.style.Fill({color: 'black'}),
|
|
|
|
stroke: new ol.style.Stroke({
|
|
|
|
color: 'white', width: 2
|
|
|
|
})
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
var feature = new ol.Feature(new ol.geom.Point(ol.proj.fromLonLat(SitePosition)));
|
|
|
|
feature.setStyle(markerStyle);
|
|
|
|
StaticFeatures.push(feature);
|
2016-03-31 21:09:12 +02:00
|
|
|
|
|
|
|
if (SiteCircles) {
|
2016-07-02 13:55:38 +02:00
|
|
|
var circleStyle = new ol.style.Style({
|
|
|
|
fill: null,
|
|
|
|
stroke: new ol.style.Stroke({
|
|
|
|
color: '#000000',
|
|
|
|
width: 1
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
for (var i=0; i < SiteCirclesDistances.length; ++i) {
|
|
|
|
var distance = SiteCirclesDistances[i] * 1000.0;
|
|
|
|
if (!Metric) {
|
|
|
|
distance *= 1.852;
|
|
|
|
}
|
|
|
|
|
|
|
|
var feature = new ol.Feature(new ol.geom.Circle(ol.proj.fromLonLat(SitePosition), distance));
|
|
|
|
feature.setStyle(circleStyle);
|
|
|
|
StaticFeatures.push(feature);
|
2016-03-31 21:09:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add terrain-limit rings. To enable this:
|
|
|
|
//
|
|
|
|
// create a panorama for your receiver location on heywhatsthat.com
|
|
|
|
//
|
|
|
|
// note the "view" value from the URL at the top of the panorama
|
|
|
|
// i.e. the XXXX in http://www.heywhatsthat.com/?view=XXXX
|
|
|
|
//
|
|
|
|
// fetch a json file from the API for the altitudes you want to see:
|
|
|
|
//
|
|
|
|
// wget -O /usr/share/dump1090-mutability/html/upintheair.json \
|
|
|
|
// 'http://www.heywhatsthat.com/api/upintheair.json?id=XXXX&refraction=0.25&alts=3048,9144'
|
|
|
|
//
|
|
|
|
// NB: altitudes are in _meters_, you can specify a list of altitudes
|
|
|
|
|
|
|
|
// kick off an ajax request that will add the rings when it's done
|
|
|
|
var request = $.ajax({ url: 'upintheair.json',
|
|
|
|
timeout: 5000,
|
|
|
|
cache: true,
|
|
|
|
dataType: 'json' });
|
|
|
|
request.done(function(data) {
|
2016-07-02 13:55:38 +02:00
|
|
|
var ringStyle = new ol.style.Style({
|
|
|
|
fill: null,
|
|
|
|
stroke: new ol.style.Stroke({
|
|
|
|
color: '#000000',
|
|
|
|
width: 1
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
for (var i = 0; i < data.rings.length; ++i) {
|
2016-07-02 13:55:38 +02:00
|
|
|
var geom = new ol.geom.LineString();
|
2016-03-31 21:09:12 +02:00
|
|
|
var points = data.rings[i].points;
|
2016-07-02 13:55:38 +02:00
|
|
|
if (points.length > 0) {
|
|
|
|
for (var j = 0; j < points.length; ++j) {
|
|
|
|
geom.appendCoordinate([ points[j][1], points[j][0] ]);
|
|
|
|
}
|
|
|
|
geom.appendCoordinate([ points[0][1], points[0][0] ]);
|
|
|
|
geom.transform('EPSG:4326', 'EPSG:3857');
|
|
|
|
|
|
|
|
var feature = new ol.Feature(geom);
|
|
|
|
feature.setStyle(ringStyle);
|
|
|
|
StaticFeatures.push(feature);
|
2016-03-31 21:09:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
request.fail(function(jqxhr, status, error) {
|
|
|
|
// no rings available, do nothing
|
|
|
|
});
|
2013-05-09 16:59:26 +02:00
|
|
|
}
|
|
|
|
|
2013-05-21 22:28:16 +02:00
|
|
|
// This looks for planes to reap out of the master Planes variable
|
|
|
|
function reaper() {
|
2016-03-31 21:09:12 +02:00
|
|
|
//console.log("Reaping started..");
|
|
|
|
|
|
|
|
// Look for planes where we have seen no messages for >300 seconds
|
|
|
|
var newPlanes = [];
|
|
|
|
for (var i = 0; i < PlanesOrdered.length; ++i) {
|
|
|
|
var plane = PlanesOrdered[i];
|
|
|
|
if (plane.seen > 300) {
|
|
|
|
// Reap it.
|
|
|
|
//console.log("Reaping " + plane.icao);
|
|
|
|
//console.log("parent " + plane.tr.parentNode);
|
|
|
|
plane.tr.parentNode.removeChild(plane.tr);
|
|
|
|
plane.tr = null;
|
|
|
|
delete Planes[plane.icao];
|
|
|
|
plane.destroy();
|
|
|
|
} else {
|
|
|
|
// Keep it.
|
|
|
|
newPlanes.push(plane);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
PlanesOrdered = newPlanes;
|
|
|
|
refreshTableInfo();
|
|
|
|
refreshSelected();
|
2015-01-07 18:18:33 +01:00
|
|
|
}
|
|
|
|
|
2015-02-20 11:42:29 +01:00
|
|
|
// Page Title update function
|
|
|
|
function refreshPageTitle() {
|
2016-03-31 21:09:12 +02:00
|
|
|
if (!PlaneCountInTitle && !MessageRateInTitle)
|
|
|
|
return;
|
2015-02-22 12:51:58 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
var subtitle = "";
|
2015-02-22 12:51:58 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
if (PlaneCountInTitle) {
|
|
|
|
subtitle += TrackedAircraftPositions + '/' + TrackedAircraft;
|
|
|
|
}
|
2015-02-22 12:51:58 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
if (MessageRateInTitle) {
|
|
|
|
if (subtitle) subtitle += ' | ';
|
|
|
|
subtitle += MessageRate.toFixed(1) + '/s';
|
|
|
|
}
|
2015-02-22 12:51:58 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
document.title = PageName + ' - ' + subtitle;
|
2015-02-20 11:42:29 +01:00
|
|
|
}
|
|
|
|
|
2013-05-21 22:28:16 +02:00
|
|
|
// Refresh the detail window about the plane
|
|
|
|
function refreshSelected() {
|
2016-03-31 21:09:12 +02:00
|
|
|
if (MessageCountHistory.length > 1) {
|
|
|
|
var message_time_delta = MessageCountHistory[MessageCountHistory.length-1].time - MessageCountHistory[0].time;
|
|
|
|
var message_count_delta = MessageCountHistory[MessageCountHistory.length-1].messages - MessageCountHistory[0].messages;
|
|
|
|
if (message_time_delta > 0)
|
|
|
|
MessageRate = message_count_delta / message_time_delta;
|
|
|
|
} else {
|
|
|
|
MessageRate = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
refreshPageTitle();
|
|
|
|
|
|
|
|
var selected = false;
|
|
|
|
if (typeof SelectedPlane !== 'undefined' && SelectedPlane != "ICAO" && SelectedPlane != null) {
|
|
|
|
selected = Planes[SelectedPlane];
|
|
|
|
}
|
2015-01-06 21:15:25 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
if (!selected) {
|
|
|
|
$('#selected_infoblock').css('display','none');
|
|
|
|
$('#dump1090_infoblock').css('display','block');
|
|
|
|
$('#dump1090_version').text(Dump1090Version);
|
|
|
|
$('#dump1090_total_ac').text(TrackedAircraft);
|
|
|
|
$('#dump1090_total_ac_positions').text(TrackedAircraftPositions);
|
|
|
|
$('#dump1090_total_history').text(TrackedHistorySize);
|
|
|
|
|
|
|
|
if (MessageRate !== null) {
|
|
|
|
$('#dump1090_message_rate').text(MessageRate.toFixed(1));
|
|
|
|
} else {
|
|
|
|
$('#dump1090_message_rate').text("n/a");
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$('#dump1090_infoblock').css('display','none');
|
|
|
|
$('#selected_infoblock').css('display','block');
|
2016-03-22 20:39:57 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
$('#selected_flightaware_link').attr('href','//flightaware.com/live/modes/'+selected.icao+'/redirect');
|
|
|
|
|
|
|
|
if (selected.flight !== null && selected.flight !== "") {
|
|
|
|
$('#selected_callsign').text(selected.flight);
|
|
|
|
$('#selected_links').css('display','inline');
|
|
|
|
$('#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);
|
2016-03-29 23:50:39 +02:00
|
|
|
$('#selected_planefinder_link').attr('href','https://planefinder.net/flight/'+selected.flight);
|
2016-03-31 21:09:12 +02:00
|
|
|
} else {
|
|
|
|
$('#selected_callsign').text('n/a');
|
|
|
|
$('#selected_links').css('display','none');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selected.registration !== null) {
|
|
|
|
$('#selected_registration').text(selected.registration);
|
|
|
|
} else {
|
|
|
|
$('#selected_registration').text("");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selected.icaotype !== null) {
|
|
|
|
$('#selected_icaotype').text(selected.icaotype);
|
|
|
|
} else {
|
|
|
|
$('#selected_icaotype').text("");
|
|
|
|
}
|
|
|
|
|
|
|
|
var emerg = document.getElementById('selected_emergency');
|
|
|
|
if (selected.squawk in SpecialSquawks) {
|
|
|
|
emerg.className = SpecialSquawks[selected.squawk].cssClass;
|
|
|
|
emerg.textContent = NBSP + 'Squawking: ' + SpecialSquawks[selected.squawk].text + NBSP ;
|
|
|
|
} else {
|
|
|
|
emerg.className = 'hidden';
|
|
|
|
}
|
|
|
|
|
|
|
|
$("#selected_altitude").text(format_altitude_long(selected.altitude, selected.vert_rate));
|
|
|
|
|
|
|
|
if (selected.squawk === null || selected.squawk === '0000') {
|
|
|
|
$('#selected_squawk').text('n/a');
|
|
|
|
} else {
|
|
|
|
$('#selected_squawk').text(selected.squawk);
|
|
|
|
}
|
|
|
|
|
|
|
|
$('#selected_speed').text(format_speed_long(selected.speed));
|
|
|
|
$('#selected_icao').text(selected.icao.toUpperCase());
|
|
|
|
$('#airframes_post_icao').attr('value',selected.icao);
|
|
|
|
$('#selected_track').text(format_track_long(selected.track));
|
|
|
|
|
|
|
|
if (selected.seen <= 1) {
|
|
|
|
$('#selected_seen').text('now');
|
|
|
|
} else {
|
|
|
|
$('#selected_seen').text(selected.seen.toFixed(1) + 's');
|
|
|
|
}
|
|
|
|
|
|
|
|
$('#selected_country').text(selected.icaorange.country);
|
|
|
|
if (ShowFlags && selected.icaorange.flag_image !== null) {
|
|
|
|
$('#selected_flag').removeClass('hidden');
|
|
|
|
$('#selected_flag img').attr('src', FlagPath + selected.icaorange.flag_image);
|
|
|
|
$('#selected_flag img').attr('title', selected.icaorange.country);
|
|
|
|
} else {
|
|
|
|
$('#selected_flag').addClass('hidden');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selected.position === null) {
|
|
|
|
$('#selected_position').text('n/a');
|
|
|
|
$('#selected_follow').addClass('hidden');
|
|
|
|
} else {
|
|
|
|
var mlat_bit = (selected.position_from_mlat ? "MLAT: " : "");
|
|
|
|
if (selected.seen_pos > 1) {
|
|
|
|
$('#selected_position').text(mlat_bit + format_latlng(selected.position) + " (" + selected.seen_pos.toFixed(1) + "s)");
|
|
|
|
} else {
|
|
|
|
$('#selected_position').text(mlat_bit + format_latlng(selected.position));
|
|
|
|
}
|
|
|
|
$('#selected_follow').removeClass('hidden');
|
|
|
|
if (FollowSelected) {
|
|
|
|
$('#selected_follow').css('font-weight', 'bold');
|
2016-07-02 15:44:14 +02:00
|
|
|
OLMap.getView().setCenter(ol.proj.fromLonLat(selected.position));
|
2016-03-31 21:09:12 +02:00
|
|
|
} else {
|
|
|
|
$('#selected_follow').css('font-weight', 'normal');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$('#selected_sitedist').text(format_distance_long(selected.sitedist));
|
|
|
|
$('#selected_rssi').text(selected.rssi.toFixed(1) + ' dBFS');
|
2015-01-06 21:15:25 +01:00
|
|
|
}
|
|
|
|
|
2015-01-07 18:18:33 +01:00
|
|
|
// Refreshes the larger table of all the planes
|
2015-01-06 21:15:25 +01:00
|
|
|
function refreshTableInfo() {
|
2016-03-31 21:09:12 +02:00
|
|
|
var show_squawk_warning = false;
|
|
|
|
|
|
|
|
TrackedAircraft = 0
|
|
|
|
TrackedAircraftPositions = 0
|
|
|
|
TrackedHistorySize = 0
|
|
|
|
|
|
|
|
for (var i = 0; i < PlanesOrdered.length; ++i) {
|
|
|
|
var tableplane = PlanesOrdered[i];
|
|
|
|
TrackedHistorySize += tableplane.history_size;
|
|
|
|
if (!tableplane.visible) {
|
|
|
|
tableplane.tr.className = "plane_table_row hidden";
|
|
|
|
} else {
|
|
|
|
TrackedAircraft++;
|
|
|
|
var classes = "plane_table_row";
|
|
|
|
|
|
|
|
if (tableplane.position !== null && tableplane.seen_pos < 60) {
|
|
|
|
++TrackedAircraftPositions;
|
|
|
|
if (tableplane.position_from_mlat)
|
|
|
|
classes += " mlat";
|
|
|
|
else
|
|
|
|
classes += " vPosition";
|
|
|
|
}
|
|
|
|
if (tableplane.icao == SelectedPlane)
|
|
|
|
classes += " selected";
|
|
|
|
|
|
|
|
if (tableplane.squawk in SpecialSquawks) {
|
|
|
|
classes = classes + " " + SpecialSquawks[tableplane.squawk].cssClass;
|
|
|
|
show_squawk_warning = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ICAO doesn't change
|
|
|
|
tableplane.tr.cells[2].textContent = (tableplane.flight !== null ? tableplane.flight : "");
|
|
|
|
tableplane.tr.cells[3].textContent = (tableplane.squawk !== null ? tableplane.squawk : "");
|
|
|
|
tableplane.tr.cells[4].textContent = format_altitude_brief(tableplane.altitude, tableplane.vert_rate);
|
|
|
|
tableplane.tr.cells[5].textContent = format_speed_brief(tableplane.speed);
|
|
|
|
tableplane.tr.cells[6].textContent = format_distance_brief(tableplane.sitedist);
|
|
|
|
tableplane.tr.cells[7].textContent = format_track_brief(tableplane.track);
|
|
|
|
tableplane.tr.cells[8].textContent = tableplane.messages;
|
|
|
|
tableplane.tr.cells[9].textContent = tableplane.seen.toFixed(0);
|
|
|
|
tableplane.tr.className = classes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (show_squawk_warning) {
|
|
|
|
$("#SpecialSquawkWarning").css('display','block');
|
|
|
|
} else {
|
|
|
|
$("#SpecialSquawkWarning").css('display','none');
|
|
|
|
}
|
|
|
|
|
|
|
|
resortTable();
|
2013-05-18 23:54:59 +02:00
|
|
|
}
|
|
|
|
|
2015-01-06 21:15:25 +01:00
|
|
|
//
|
|
|
|
// ---- table sorting ----
|
|
|
|
//
|
|
|
|
|
2015-01-07 23:16:49 +01:00
|
|
|
function compareAlpha(xa,ya) {
|
2016-03-31 21:09:12 +02:00
|
|
|
if (xa === ya)
|
|
|
|
return 0;
|
|
|
|
if (xa < ya)
|
|
|
|
return -1;
|
|
|
|
return 1;
|
2015-01-06 21:15:25 +01:00
|
|
|
}
|
2015-01-06 00:20:03 +01:00
|
|
|
|
2015-01-07 23:16:49 +01:00
|
|
|
function compareNumeric(xf,yf) {
|
2016-03-31 21:09:12 +02:00
|
|
|
if (Math.abs(xf - yf) < 1e-9)
|
|
|
|
return 0;
|
2015-01-06 21:15:25 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
return xf - yf;
|
2013-05-18 23:54:59 +02:00
|
|
|
}
|
|
|
|
|
2015-01-17 13:27:13 +01:00
|
|
|
function sortByICAO() { sortBy('icao', compareAlpha, function(x) { return x.icao; }); }
|
|
|
|
function sortByFlight() { sortBy('flight', compareAlpha, function(x) { return x.flight; }); }
|
|
|
|
function sortBySquawk() { sortBy('squawk', compareAlpha, function(x) { return x.squawk; }); }
|
|
|
|
function sortByAltitude() { sortBy('altitude',compareNumeric, function(x) { return (x.altitude == "ground" ? -1e9 : x.altitude); }); }
|
|
|
|
function sortBySpeed() { sortBy('speed', compareNumeric, function(x) { return x.speed; }); }
|
|
|
|
function sortByDistance() { sortBy('sitedist',compareNumeric, function(x) { return x.sitedist; }); }
|
|
|
|
function sortByTrack() { sortBy('track', compareNumeric, function(x) { return x.track; }); }
|
|
|
|
function sortByMsgs() { sortBy('msgs', compareNumeric, function(x) { return x.messages; }); }
|
|
|
|
function sortBySeen() { sortBy('seen', compareNumeric, function(x) { return x.seen; }); }
|
2015-09-01 13:19:23 +02:00
|
|
|
function sortByCountry() { sortBy('country', compareAlpha, function(x) { return x.icaorange.country; }); }
|
2015-01-06 21:15:25 +01:00
|
|
|
|
2015-01-17 13:27:13 +01:00
|
|
|
var sortId = '';
|
|
|
|
var sortCompare = null;
|
|
|
|
var sortExtract = null;
|
|
|
|
var sortAscending = true;
|
2015-01-06 21:15:25 +01:00
|
|
|
|
2015-01-17 13:27:13 +01:00
|
|
|
function sortFunction(x,y) {
|
2016-03-31 21:09:12 +02:00
|
|
|
var xv = x._sort_value;
|
|
|
|
var yv = y._sort_value;
|
2013-05-18 23:54:59 +02:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
// always sort missing values at the end, regardless of
|
|
|
|
// ascending/descending sort
|
|
|
|
if (xv == null && yv == null) return x._sort_pos - y._sort_pos;
|
|
|
|
if (xv == null) return 1;
|
|
|
|
if (yv == null) return -1;
|
2015-01-06 21:15:25 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
var c = sortAscending ? sortCompare(xv,yv) : sortCompare(yv,xv);
|
|
|
|
if (c !== 0) return c;
|
2015-01-17 13:27:13 +01:00
|
|
|
|
2016-03-31 21:09:12 +02:00
|
|
|
return x._sort_pos - y._sort_pos;
|
2015-01-17 13:27:13 +01:00
|
|
|
}
|
2015-01-06 21:15:25 +01:00
|
|
|
|
|
|
|
function resortTable() {
|
2016-03-31 21:09:12 +02:00
|
|
|
// number the existing rows so we can do a stable sort
|
|
|
|
// regardless of whether sort() is stable or not.
|
|
|
|
// Also extract the sort comparison value.
|
|
|
|
for (var i = 0; i < PlanesOrdered.length; ++i) {
|
|
|
|
PlanesOrdered[i]._sort_pos = i;
|
|
|
|
PlanesOrdered[i]._sort_value = sortExtract(PlanesOrdered[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
PlanesOrdered.sort(sortFunction);
|
|
|
|
|
|
|
|
var tbody = document.getElementById('tableinfo').tBodies[0];
|
|
|
|
for (var i = 0; i < PlanesOrdered.length; ++i) {
|
|
|
|
tbody.appendChild(PlanesOrdered[i].tr);
|
|
|
|
}
|
2015-01-06 21:15:25 +01:00
|
|
|
}
|
2013-05-18 23:54:59 +02:00
|
|
|
|
2015-01-17 13:27:13 +01:00
|
|
|
function sortBy(id,sc,se) {
|
2016-03-31 21:09:12 +02:00
|
|
|
if (id === sortId) {
|
|
|
|
sortAscending = !sortAscending;
|
|
|
|
PlanesOrdered.reverse(); // this correctly flips the order of rows that compare equal
|
|
|
|
} else {
|
|
|
|
sortAscending = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
sortId = id;
|
|
|
|
sortCompare = sc;
|
|
|
|
sortExtract = se;
|
|
|
|
|
|
|
|
resortTable();
|
2013-05-11 12:15:09 +02:00
|
|
|
}
|
|
|
|
|
2015-01-22 22:28:35 +01:00
|
|
|
function selectPlaneByHex(hex,autofollow) {
|
2016-03-31 21:09:12 +02:00
|
|
|
//console.log("select: " + hex);
|
|
|
|
// If SelectedPlane has something in it, clear out the selected
|
|
|
|
if (SelectedPlane != null) {
|
|
|
|
Planes[SelectedPlane].selected = false;
|
|
|
|
Planes[SelectedPlane].clearLines();
|
|
|
|
Planes[SelectedPlane].updateMarker();
|
|
|
|
$(Planes[SelectedPlane].tr).removeClass("selected");
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we are clicking the same plane, we are deselected it.
|
|
|
|
if (SelectedPlane === hex) {
|
|
|
|
hex = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hex !== null) {
|
|
|
|
// Assign the new selected
|
|
|
|
SelectedPlane = hex;
|
|
|
|
Planes[SelectedPlane].selected = true;
|
|
|
|
Planes[SelectedPlane].updateLines();
|
|
|
|
Planes[SelectedPlane].updateMarker();
|
|
|
|
$(Planes[SelectedPlane].tr).addClass("selected");
|
|
|
|
} else {
|
|
|
|
SelectedPlane = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SelectedPlane !== null && autofollow) {
|
|
|
|
FollowSelected = true;
|
2016-07-02 15:44:14 +02:00
|
|
|
if (OLMap.getView().getZoom() < 8)
|
|
|
|
OLMap.getView().setZoom(8);
|
2016-03-31 21:09:12 +02:00
|
|
|
} else {
|
|
|
|
FollowSelected = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
refreshSelected();
|
2015-01-22 15:17:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function toggleFollowSelected() {
|
2016-03-31 21:09:12 +02:00
|
|
|
FollowSelected = !FollowSelected;
|
2016-07-02 15:44:14 +02:00
|
|
|
if (FollowSelected && OLMap.getView().getZoom() < 8)
|
|
|
|
OLMap.getView().setZoom(8);
|
2016-03-31 21:09:12 +02:00
|
|
|
refreshSelected();
|
2013-05-09 16:59:26 +02:00
|
|
|
}
|
2013-05-30 19:32:36 +02:00
|
|
|
|
|
|
|
function resetMap() {
|
2016-03-31 21:09:12 +02:00
|
|
|
// Reset localStorage values and map settings
|
|
|
|
localStorage['CenterLat'] = CenterLat = DefaultCenterLat;
|
|
|
|
localStorage['CenterLon'] = CenterLon = DefaultCenterLon;
|
|
|
|
localStorage['ZoomLvl'] = ZoomLvl = DefaultZoomLvl;
|
|
|
|
|
|
|
|
// Set and refresh
|
2016-07-02 15:44:14 +02:00
|
|
|
OLMap.getView().setZoom(ZoomLvl);
|
|
|
|
OLMap.getView().setCenter(ol.proj.fromLonLat([CenterLon, CenterLat]));
|
2016-03-31 21:09:12 +02:00
|
|
|
|
|
|
|
selectPlaneByHex(null,false);
|
2013-05-30 19:32:36 +02:00
|
|
|
}
|