4933 lines
152 KiB
JavaScript
4933 lines
152 KiB
JavaScript
// Some global letiables are defined in early.js
|
||
// early.js takes care of getting some history files while the html page and
|
||
// some javascript libraries are still loading, hopefully speeding up loading
|
||
|
||
"use strict";
|
||
|
||
// Define our global letiables
|
||
let OLMap = null;
|
||
let OLProj = null;
|
||
let StaticFeatures = new ol.source.Vector();
|
||
let PlaneIconFeatures = new ol.source.Vector();
|
||
let trailGroup = new ol.Collection();
|
||
let iconLayer;
|
||
let trailLayers;
|
||
let heatFeatures = [];
|
||
let heatFeaturesSpread = 1024;
|
||
let heatLayers = [];
|
||
let realHeatFeatures = new ol.source.Vector();
|
||
let realHeat;
|
||
let iconCache = {};
|
||
let addToIconCache = [];
|
||
let dotCache = {};
|
||
let useDots = false;
|
||
let lineStyleCache = {};
|
||
let Planes = {};
|
||
let PlanesOrdered = [];
|
||
let PlaneFilter = {};
|
||
let SelectedPlane = null;
|
||
let SelectedAllPlanes = false;
|
||
let HighlightedPlane = null;
|
||
let FollowSelected = false;
|
||
let noPan = false;
|
||
let infoBoxOriginalPosition = {};
|
||
let customAltitudeColors = true;
|
||
let loadtime = "loadtime";
|
||
let loadFinished = false;
|
||
let mapResizeTimeout;
|
||
let pointerMoveTimeout;
|
||
let refresh;
|
||
let scaleFactor;
|
||
let debugTracks = false;
|
||
let debugAll = false;
|
||
let trackLabels = false;
|
||
let fragment;
|
||
let grouptype_checkbox;
|
||
let multiSelect = false;
|
||
let uat_data = null;
|
||
let enableLabels = false;
|
||
let extendedLabels = 0;
|
||
let mapIsVisible = true;
|
||
let columnVis = Array(30).fill(true);
|
||
let emptyStyle = new ol.style.Style({});
|
||
let show_squawk_warning_cache = false;
|
||
let tableInView = false;
|
||
let historyOutdated = false;
|
||
let onlyMLAT = false;
|
||
let onlyMilitary = false;
|
||
let onlyADSB = false;
|
||
let onlySelected = false;
|
||
let onlyDataSource = null;
|
||
let fetchingPf = false;
|
||
let reaping = false;
|
||
let debug = false;
|
||
let debugJump = false;
|
||
let jumpTo = null;
|
||
let noMLAT = false;
|
||
let noVanish = false;
|
||
let sidebarVisible = true;
|
||
let filterTracks = false;
|
||
let refreshId = 0;
|
||
let globeIndexGrid = 0;
|
||
let globeIndexNow = {};
|
||
let globeIndexSpecialTiles;
|
||
let globeSimLoad = 4;
|
||
let globeTableLimit = 80;
|
||
let showGrid = false;
|
||
let lastRealExtent;
|
||
let lastGlobeExtent;
|
||
let lastRenderExtent;
|
||
let globeIndexExtent;
|
||
let PendingFetches = 0;
|
||
let lastRequestFiles = 0;
|
||
let debugCounter = 0;
|
||
let selectedPhotoCache = null;
|
||
let pathName = null;
|
||
let icaoFilter = null;
|
||
let showTrace = false;
|
||
let showTraceExit = false;
|
||
let showTraceWasIsolation = false;
|
||
let traceDate = null;
|
||
let traceDateString = null;
|
||
let traceDay = null;
|
||
let traceOpts = {};
|
||
let icaoParam = null;
|
||
let globalScale = 1;
|
||
let newWidth = lineWidth;
|
||
let SitePosInitialized = false;
|
||
let SiteOverride = false;
|
||
let airport = null;
|
||
let labelFill = null;
|
||
let blackFill = null;
|
||
let labelStroke = null;
|
||
let labelStrokeNarrow = null;
|
||
let bgFill = null;
|
||
let legSel = -1;
|
||
let geoMag = null;
|
||
let globalCompositeTested = false;
|
||
let solidT = false;
|
||
let lastActive = new Date().getTime();
|
||
let inactive = 0;
|
||
let firstFetchDone = false;
|
||
let overrideMapType = null;
|
||
let altitudeChartDisplay;
|
||
|
||
let shareLink = '';
|
||
|
||
let onMobile = false;
|
||
|
||
let SpecialSquawks = {
|
||
'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' }
|
||
};
|
||
|
||
// Get current map settings
|
||
let CenterLat, CenterLon, ZoomLvl, ZoomLvlCache;
|
||
let zoomTimeout;
|
||
let noMovement;
|
||
let checkMoveZoom;
|
||
let checkMoveCenter = [0, 0];
|
||
|
||
|
||
let PlaneRowTemplate = null;
|
||
let tableinfoFragment = null;
|
||
|
||
let TrackedAircraft = 0;
|
||
let globeTrackedAircraft = 0;
|
||
let TrackedAircraftPositions = 0;
|
||
let TrackedHistorySize = 0;
|
||
|
||
let SitePosition = null;
|
||
|
||
// timestamps
|
||
let now = 0;
|
||
let last = 0;
|
||
let uat_now = 0;
|
||
let uat_last = 0;
|
||
let today = 0;
|
||
let FetchPending = [];
|
||
let FetchPendingUAT = null;
|
||
|
||
let MessageCountHistory = [];
|
||
let MessageRate = 0;
|
||
|
||
let layers;
|
||
let layers_group;
|
||
|
||
let estimateStyle = new ol.style.Style({
|
||
stroke: new ol.style.Stroke({
|
||
color: '#808080',
|
||
width: 1.2 * lineWidth,
|
||
})
|
||
});
|
||
let estimateStyleSlim = new ol.style.Style({
|
||
stroke: new ol.style.Stroke({
|
||
color: '#808080',
|
||
width: 0.4 * lineWidth,
|
||
})
|
||
});
|
||
|
||
const nullStyle = new ol.style.Style({});
|
||
|
||
let badLine = new ol.style.Style({
|
||
stroke: new ol.style.Stroke({
|
||
color: '#FF0000',
|
||
width: 2 * lineWidth,
|
||
})
|
||
});
|
||
let badLineMlat = new ol.style.Style({
|
||
stroke: new ol.style.Stroke({
|
||
color: '#FFA500',
|
||
width: 2 * lineWidth,
|
||
})
|
||
});
|
||
|
||
let badDot = new ol.style.Style({
|
||
image: new ol.style.Circle({
|
||
radius: 3.5 * lineWidth,
|
||
fill: new ol.style.Fill({
|
||
color: '#FF0000',
|
||
})
|
||
}),
|
||
});
|
||
let badDotMlat = new ol.style.Style({
|
||
image: new ol.style.Circle({
|
||
radius: 3.5 * lineWidth,
|
||
fill: new ol.style.Fill({
|
||
color: '#FFA500',
|
||
})
|
||
}),
|
||
});
|
||
|
||
|
||
function processAircraft(ac, init, uat) {
|
||
let isArray = Array.isArray(ac);
|
||
let hex = isArray ? ac[0] : ac.hex;
|
||
let plane = null;
|
||
|
||
// Do we already have this plane object in Planes?
|
||
// If not make it.
|
||
|
||
/*
|
||
if ( ac.messages < 2) {
|
||
return;
|
||
}
|
||
*/
|
||
if (icaoFilter && !icaoFilter.includes(hex))
|
||
return;
|
||
|
||
plane = Planes[hex];
|
||
|
||
if (uatNoTISB && uat && ac.type && ac.type.substring(0,4) == "tisb") {
|
||
// drop non ADS-B planes from UAT (TIS-B)
|
||
return;
|
||
}
|
||
|
||
if (!plane) {
|
||
plane = new PlaneObject(hex);
|
||
|
||
Planes[hex] = plane;
|
||
PlanesOrdered.push(plane);
|
||
if (uat) {
|
||
plane.receiver = "uat";
|
||
} else {
|
||
plane.receiver = "1090";
|
||
}
|
||
}
|
||
|
||
if (showTrace)
|
||
return;
|
||
|
||
// Call the function update
|
||
if (globeIndex) {
|
||
if (!onlyMilitary || plane.military)
|
||
plane.updateData(now, last, ac, init);
|
||
else
|
||
plane.last_message_time = now - ac.seen;
|
||
} else if (uat) {
|
||
if (plane.receiver == "uat" || ac.seen_pos < 1.8 || init) {
|
||
let tisb = Array.isArray(ac) ? (ac[7] == "tisb") : (ac.tisb != null && ac.tisb.indexOf("lat") >= 0);
|
||
if (tisb && plane.dataSource == "adsb") {
|
||
// ignore TIS-B data for current ADS-B 1090 planes
|
||
} else {
|
||
plane.receiver = "uat";
|
||
plane.updateData(uat_now, uat_last, ac, init);
|
||
}
|
||
}
|
||
} else {
|
||
if (plane.receiver == "1090"
|
||
|| (ac.seen_pos < 1.8 && (plane.seen_pos > 5 || !(ac.mlat && ac.mlat.indexOf("lat") >= 0)))
|
||
|| init) {
|
||
plane.receiver = "1090";
|
||
plane.updateData(now, last, ac, init);
|
||
}
|
||
}
|
||
}
|
||
|
||
function processReceiverUpdate(data, init) {
|
||
// update now and last
|
||
let uat = data.uat_978;
|
||
if (uat) {
|
||
if (data.now <= uat_now)
|
||
return;
|
||
uat_last = uat_now;
|
||
uat_now = data.now;
|
||
} else {
|
||
if (data.now <= now && !globeIndex)
|
||
return;
|
||
if (data.now > now) {
|
||
last = now;
|
||
now = data.now;
|
||
}
|
||
}
|
||
|
||
if (globeIndex) {
|
||
if (!binCraft && (showGrid || localStorage['globeGrid'] == 'true')
|
||
&& globeIndexNow[data.globeIndex] == null)
|
||
drawTileBorder(data);
|
||
if (binCraft)
|
||
wqi(data);
|
||
globeTrackedAircraft = data.global_ac_count_withpos;
|
||
globeIndexNow[data.globeIndex] = data.now;
|
||
}
|
||
|
||
|
||
if (!uat && !init && !globeIndex && !binCraft)
|
||
updateMessageRate(data);
|
||
|
||
// Loop through all the planes in the data packet
|
||
for (let j=0; j < data.aircraft.length; j++)
|
||
processAircraft(data.aircraft[j], init, uat);
|
||
}
|
||
|
||
function fetchData() {
|
||
if (heatmap)
|
||
return;
|
||
ZoomLvl = OLMap.getView().getZoom();
|
||
let center = ol.proj.toLonLat(OLMap.getView().getCenter());
|
||
localStorage['CenterLon'] = CenterLon = center[0];
|
||
localStorage['CenterLat'] = CenterLat = center[1];
|
||
clearTimeout(refreshId);
|
||
refreshId = setTimeout(fetchData, refreshInt());
|
||
//console.log("fetch");
|
||
if (showTrace)
|
||
return;
|
||
if (PendingFetches > 0)
|
||
return;
|
||
for (let i in FetchPending) {
|
||
if (FetchPending[i] != null && FetchPending[i].state() == 'pending') {
|
||
// don't double up on fetches, let the last one resolve
|
||
return;
|
||
}
|
||
}
|
||
FetchPending = [];
|
||
if (FetchPendingUAT != null) {
|
||
// don't double up on fetches, let the last one resolve
|
||
return;
|
||
}
|
||
|
||
PendingFetches = 1;
|
||
|
||
//console.timeEnd("Starting Fetch");
|
||
//console.time("Starting Fetch");
|
||
|
||
|
||
updateIconCache();
|
||
|
||
if (enable_uat) {
|
||
FetchPendingUAT = $.ajax({ url: 'chunks/978.json',
|
||
dataType: 'json' });
|
||
|
||
FetchPendingUAT.done(function(data) {
|
||
uat_data = data;
|
||
FetchPendingUAT = null;
|
||
});
|
||
FetchPendingUAT.fail(function(jqxhr, status, error) {
|
||
FetchPendingUAT = null;
|
||
});
|
||
}
|
||
buttonActive('#F', FollowSelected);
|
||
|
||
let ac_url = [];
|
||
if (uuid != null) {
|
||
ac_url[0] = 'uuid/?feed=' + encodeURIComponent(uuid);
|
||
$("#lastLeg_checkbox").parent().hide();
|
||
} else if (globeIndex) {
|
||
let indexes = globeIndexes();
|
||
let count = 0;
|
||
indexes.sort(function(x,y) {
|
||
if (!globeIndexNow[x] && !globeIndexNow[y])
|
||
return 0;
|
||
if (globeIndexNow[x] == null)
|
||
return -1;
|
||
if (globeIndexNow[y] == null)
|
||
return 1;
|
||
return (globeIndexNow[x] - globeIndexNow[y]);
|
||
});
|
||
indexes = indexes.slice(0, globeSimLoad);
|
||
let suffix = binCraft ? '.binCraft' : '.json'
|
||
let mid = (binCraft && onlyMilitary) ? 'Mil_' : '_';
|
||
for (let i in indexes) {
|
||
ac_url.push('data/globe' + mid + indexes[i].toString().padStart(4, '0') + suffix);
|
||
}
|
||
} else {
|
||
ac_url[0] = 'data/aircraft.json';
|
||
$("#lastLeg_checkbox").parent().hide();
|
||
}
|
||
lastRequestFiles = ac_url.length;
|
||
PendingFetches = ac_url.length;
|
||
|
||
if (globeIndex) {
|
||
clearTimeout(refreshId);
|
||
refreshId = setTimeout(fetchData, 25000);
|
||
}
|
||
|
||
for (let i in ac_url) {
|
||
//console.log(ac_url[i]);
|
||
let req;
|
||
if (binCraft) {
|
||
let xhrOverride = new XMLHttpRequest();
|
||
xhrOverride.responseType = 'arraybuffer';
|
||
req = $.ajax({
|
||
url: ac_url[i], method: 'GET',
|
||
xhr: function() { return xhrOverride; }
|
||
});
|
||
} else {
|
||
req = $.ajax({ url: ac_url[i], dataType: 'json' });
|
||
}
|
||
FetchPending.push(req);
|
||
|
||
req.done(function(data) {
|
||
if (data == null) {
|
||
return;
|
||
}
|
||
if (binCraft) {
|
||
data = { buffer: data, };
|
||
let ts = new Uint32Array(data.buffer, 0, 2);
|
||
data.now = ts[0] / 1000 + ts[1] * 4294967.296;
|
||
}
|
||
if (data.now >= now || globeIndex) {
|
||
//console.time("Process " + data.globeIndex);
|
||
processReceiverUpdate(data);
|
||
//console.timeEnd("Process " + data.globeIndex);
|
||
}
|
||
if (uat_data && uat_data.now > uat_now) {
|
||
processReceiverUpdate(uat_data);
|
||
uat_data = null;
|
||
}
|
||
|
||
|
||
if (PendingFetches <= 1) {
|
||
//console.time("refreshTable");
|
||
refreshTableInfo();
|
||
//console.timeEnd("refreshTable");
|
||
refreshClock(new Date(now * 1000));
|
||
refreshSelected();
|
||
refreshHighlighted();
|
||
}
|
||
|
||
if (globeIndex) {
|
||
clearTimeout(refreshId);
|
||
refreshId = setTimeout(fetchData, refreshInt());
|
||
}
|
||
PendingFetches--;
|
||
|
||
// Check for stale receiver data
|
||
if (last == now && !globeIndex) {
|
||
StaleReceiverCount++;
|
||
if (StaleReceiverCount > 5) {
|
||
$("#update_error_detail").text("The data from the server hasn't been updated in a while.");
|
||
$("#update_error").css('display','block');
|
||
}
|
||
} else if (StaleReceiverCount > 0){
|
||
StaleReceiverCount = 0;
|
||
$("#update_error").css('display','none');
|
||
}
|
||
|
||
if (!firstFetchDone) {
|
||
firstFetchDone = true;
|
||
if (uuid) {
|
||
followRandomPlane();
|
||
OLMap.getView().setZoom(6);
|
||
}
|
||
}
|
||
});
|
||
|
||
req.fail(function(jqxhr, status, error) {
|
||
$("#update_error_detail").text("AJAX call failed (" + status + (error ? (": " + error) : "") + ").");
|
||
console.log("AJAX call failed (" + status + (error ? (": " + error) : "") + ").");
|
||
console.log(jqxhr);
|
||
console.log(status);
|
||
console.log(error);
|
||
$("#update_error").css('display','block');
|
||
StaleReceiverCount++;
|
||
PendingFetches--;
|
||
clearTimeout(refreshId);
|
||
refreshId = setTimeout(fetchData, refreshInt());
|
||
});
|
||
}
|
||
}
|
||
|
||
|
||
|
||
// this function is called from index.html on body load
|
||
// kicks off the whole rabbit hole
|
||
function initialize() {
|
||
|
||
onMobile = window.mobilecheck();
|
||
|
||
today = new Date().getDate();
|
||
|
||
let largeModeStorage = localStorage['largeMode'];
|
||
if (largeModeStorage != undefined && parseInt(largeModeStorage, 10)) {
|
||
largeMode = parseInt(largeModeStorage, 10);
|
||
}
|
||
|
||
|
||
try {
|
||
const search = new URLSearchParams(window.location.search);
|
||
if (search.has('showGrid'))
|
||
showGrid = true;
|
||
|
||
if (search.has('onlyDataSource'))
|
||
onlyDataSource = search.get('onlyDataSource');
|
||
|
||
if (search.has('outlineWidth')) {
|
||
let tmp = parseInt(search.get('outlineWidth'));
|
||
if (!isNaN(tmp))
|
||
outlineWidth = tmp;
|
||
}
|
||
|
||
if (search.has('kiosk')) {
|
||
tempTrails = true;
|
||
hideButtons = true;
|
||
largeMode = 2;
|
||
}
|
||
|
||
if (search.has('pTracks')) {
|
||
noVanish = true;
|
||
buttonActive('#P', noVanish);
|
||
filterTracks = true;
|
||
selectAllPlanes();
|
||
}
|
||
|
||
if (search.has('largeMode')) {
|
||
let tmp = parseInt(search.get('largeMode'));
|
||
console.log(tmp);
|
||
if (!isNaN(tmp))
|
||
largeMode = tmp;
|
||
}
|
||
|
||
if (search.has('mobile'))
|
||
onMobile = true;
|
||
if (search.has('desktop'))
|
||
onMobile = false;
|
||
|
||
if (search.has('hideSidebar'))
|
||
localStorage['sidebar_visible'] = "false";
|
||
if (search.has('sidebarWidth')) {
|
||
localStorage['sidebar_width'] = search.get('sidebarWidth');
|
||
localStorage['sidebar_visible'] = "true";
|
||
}
|
||
|
||
if (search.has('SiteLat') && search.has('SiteLon')) {
|
||
localStorage['SiteLat'] = search.get('SiteLat');
|
||
localStorage['SiteLon'] = search.get('SiteLon');
|
||
}
|
||
if (localStorage['SiteLat'] != null && localStorage['SiteLon'] != null) {
|
||
if (search.has('SiteClear')
|
||
|| isNaN(parseFloat(localStorage['SiteLat']))
|
||
|| isNaN(parseFloat(localStorage['SiteLat']))) {
|
||
localStorage.removeItem('SiteLat');
|
||
localStorage.removeItem('SiteLon');
|
||
} else {
|
||
SiteLat = CenterLat = DefaultCenterLat = parseFloat(localStorage['SiteLat']);
|
||
SiteLon = CenterLon = DefaultCenterLon = parseFloat(localStorage['SiteLon']);
|
||
SiteOverride = true;
|
||
}
|
||
}
|
||
|
||
if (search.has('tempTrails')) {
|
||
tempTrails = true;
|
||
let tmp = parseInt(search.get('tempTrails'));
|
||
if (tmp > 0)
|
||
tempTrailsTimeout = tmp;
|
||
}
|
||
if (search.has('squareMania')) {
|
||
squareMania = true;
|
||
}
|
||
if (search.has('mapDim')) {
|
||
let dim = parseFloat(search.get('mapDim'));
|
||
if (!isNaN(dim))
|
||
mapDimPercentage = dim;
|
||
} else if (heatmap) {
|
||
mapDimPercentage = 0.6;
|
||
MapDim = true;
|
||
}
|
||
|
||
|
||
if (search.has('mapContrast')) {
|
||
let contrast = parseFloat(search.get('mapContrast'));
|
||
if (!isNaN(contrast))
|
||
mapContrastPercentage = contrast;
|
||
}
|
||
|
||
if (search.has('hideButtons'))
|
||
hideButtons = true;
|
||
|
||
if (search.has('baseMap'))
|
||
overrideMapType = search.get('baseMap');
|
||
|
||
icaoFilter = search.get('icaoFilter');
|
||
if (icaoFilter)
|
||
icaoFilter = icaoFilter.toLowerCase().split(',');
|
||
|
||
if (search.has('filterMaxRange')) {
|
||
let tmp = parseFloat(search.get('filterMaxRange'));
|
||
if (!isNaN(tmp))
|
||
filterMaxRange = tmp;
|
||
}
|
||
filterMaxRange *= 1852; // convert from nmi to meters
|
||
} catch (error) {
|
||
console.log(error);
|
||
}
|
||
|
||
if (onMobile)
|
||
enableMouseover = false;
|
||
|
||
if (false && iOSVersion() <= 12 && !('PointerEvent' in window)) {
|
||
$("#generic_error_detail").text("Enable Settings - Safari - Advanced - Experimental features - Pointer Events");
|
||
$("#generic_error").css('display','block');
|
||
setTimeout(function() {
|
||
$("#generic_error").css('display','none');
|
||
}, 30000);
|
||
}
|
||
|
||
if (document.getElementById('adsense') != null || adsbexchange) {
|
||
if (onMobile || hideButtons) {
|
||
try {
|
||
document.getElementById('adsense').style.display='none';
|
||
} catch (error) {
|
||
console.log(error);
|
||
}
|
||
} else {
|
||
setTimeout(function() {
|
||
try {
|
||
(adsbygoogle = window.adsbygoogle || []).push({});
|
||
} catch (error) {
|
||
console.log(error);
|
||
}
|
||
|
||
let countDown = 20;
|
||
let i = setInterval(function () {
|
||
|
||
let b1 = document.getElementById('waittohide');
|
||
let b2 = document.getElementById('letuserhide');
|
||
|
||
if (!b1 || !b2)
|
||
return;
|
||
|
||
if(countDown === 1) {
|
||
if(b1['style'].display == 'none') {
|
||
b1['style'].display = 'block';
|
||
b2['style'].display = 'none';
|
||
} else {
|
||
b1['style'].display = 'none';
|
||
b2['style'].display = 'block';
|
||
}
|
||
clearInterval(i);
|
||
}
|
||
countDown--;
|
||
b1.innerHTML = 'Hide in ' + countDown + ' seconds';
|
||
|
||
|
||
}, 1000);
|
||
}, 1000);
|
||
}
|
||
setInterval(function(){$.ajax({url:'data/receiver.json',cache:false,});}, 180000);
|
||
}
|
||
|
||
mapOrientation *= (Math.PI/180); // adjust to radians
|
||
|
||
if (localStorage['enableLabels'] == 'true'){
|
||
toggleLabels();
|
||
}
|
||
if (localStorage['extendedLabels']){
|
||
extendedLabels = parseInt(localStorage['extendedLabels']) + 2;
|
||
toggleExtendedLabels();
|
||
}
|
||
if (localStorage['trackLabels'] == "true") {
|
||
toggleTrackLabels();
|
||
}
|
||
if (localStorage['tableInView'] == "true") {
|
||
toggleTableInView(true);
|
||
}
|
||
if (localStorage['debug'] == "true")
|
||
debug = true;
|
||
if (localStorage['debugPosFilter'] == "true")
|
||
debugPosFilter = true;
|
||
if (localStorage['noMLAT'] == "true") {
|
||
// disable remembering this for now
|
||
//noMLAT = true;
|
||
//localStorage['noMLAT'] = "false";
|
||
}
|
||
|
||
if (localStorage['noVanish'] == "true") {
|
||
noVanish = true;
|
||
filterTracks = noVanish;
|
||
//localStorage['noVanish'] = "false";
|
||
buttonActive('#P', noVanish);
|
||
}
|
||
|
||
$.when(configureReceiver, heatmapDefer).done(function() {
|
||
configureReceiver = null;
|
||
|
||
// Initialize stuff
|
||
init_page();
|
||
|
||
// Wait for history item downloads and append them to the buffer
|
||
push_history();
|
||
});
|
||
|
||
let coll = document.getElementsByClassName("collapseButton");
|
||
|
||
for (let i = 0; i < coll.length; i++) {
|
||
coll[i].addEventListener("click", function() {
|
||
this.classList.toggle("active");
|
||
let content = this.nextElementSibling;
|
||
if (content.style.display === "block") {
|
||
content.style.display = "none";
|
||
} else {
|
||
content.style.display = "block";
|
||
}
|
||
});
|
||
}
|
||
|
||
}
|
||
|
||
function init_page() {
|
||
// Set page basics
|
||
document.title = PageName;
|
||
|
||
PlaneRowTemplate = document.getElementById("plane_row_template");
|
||
|
||
$('#clock_div').text(new Date().toLocaleString());
|
||
|
||
if (ExtendedData || window.location.hash == '#extended') {
|
||
$("#extendedData").removeClass("hidden");
|
||
}
|
||
|
||
|
||
// Set up map/sidebar splitter
|
||
$("#sidebar_container").resizable({
|
||
handles: {
|
||
w: '#splitter'
|
||
},
|
||
minWidth: 150,
|
||
maxWidth: ($(window).innerWidth() *0.8),
|
||
});
|
||
$("#splitter").dblclick(function() {
|
||
$('#sidebar_container').width('auto');
|
||
updateMapSize();
|
||
localStorage['sidebar_width'] = $('#sidebar_container').width();
|
||
$('#sidebar_container').width(localStorage['sidebar_width']);
|
||
});
|
||
|
||
if (localStorage['sidebar_width'] != null)
|
||
$('#sidebar_container').width(localStorage['sidebar_width']);
|
||
else
|
||
$('#sidebar_container').width('25%');
|
||
|
||
if ($('#sidebar_container').width() > $(window).innerWidth() *0.8)
|
||
$('#sidebar_container').width('30%');
|
||
|
||
localStorage['sidebar_width'] = $('#sidebar_container').width();
|
||
/*
|
||
// Set up datablock splitter
|
||
$('#selected_infoblock').resizable({
|
||
handles: {
|
||
s: '#splitter-infoblock'
|
||
},
|
||
containment: "#sidebar_container",
|
||
minHeight: 50
|
||
});
|
||
*/
|
||
|
||
$('#infoblock_close').on('click', function () {
|
||
if (SelectedPlane) {
|
||
SelectedPlane.selected = null;
|
||
SelectedPlane.clearLines();
|
||
SelectedPlane.updateMarker();
|
||
SelectedPlane = null;
|
||
refreshSelected();
|
||
refreshHighlighted();
|
||
$('#selected_infoblock').hide();
|
||
refreshTableInfo();
|
||
}
|
||
});
|
||
|
||
/*
|
||
// this is a little hacky, but the best, most consitent way of doing this. change the margin bottom of the table container to the height of the overlay
|
||
$('#selected_infoblock').on('resize', function() {
|
||
$('#sidebar_canvas').css('margin-bottom', $('#selected_infoblock').height() + 'px');
|
||
});
|
||
// look at the window resize to resize the pop-up infoblock so it doesn't float off the bottom or go off the top
|
||
$(window).on('resize', function() {
|
||
let topCalc = ($(window).height() - $('#selected_infoblock').height() - 25);
|
||
// check if the top will be less than zero, which will be overlapping/off the screen, and set the top correctly.
|
||
if (topCalc < 0) {
|
||
topCalc = 0;
|
||
$('#selected_infoblock').css('height', ($(window).height() - 25) +'px');
|
||
}
|
||
$('#selected_infoblock').css('top', topCalc + 'px');
|
||
});
|
||
*/
|
||
|
||
$('#sidebar_container').on('resize', function() {
|
||
localStorage['sidebar_width'] = $('#sidebar_container').width();
|
||
});
|
||
|
||
// Set up event handlers for buttons
|
||
$("#toggle_sidebar_button").click(toggleSidebarVisibility);
|
||
$("#expand_sidebar_button").click(expandSidebar);
|
||
$("#shrink_sidebar_button").click(showMap);
|
||
|
||
$("#large_mode_button").click(toggleLargeMode);
|
||
|
||
// Set initial element visibility
|
||
setColumnVisibility();
|
||
|
||
// Initialize other controls
|
||
initializeUnitsSelector();
|
||
|
||
// Set up altitude filter button event handlers and validation options
|
||
$("#altitude_filter_form").submit(onFilterByAltitude);
|
||
$("#callsign_filter_form").submit(updateCallsignFilter);
|
||
$("#type_filter_form").submit(updateTypeFilter);
|
||
$("#description_filter_form").submit(updateDescriptionFilter);
|
||
$("#icao_filter_form").submit(updateIcaoFilter);
|
||
|
||
$("#search_form").submit(onSearch);
|
||
$("#jump_form").submit(onJump);
|
||
|
||
$("#show_trace").click(toggleShowTrace);
|
||
$("#trace_back_1d").click(function() {shiftTrace(-1)});
|
||
$("#trace_jump_1d").click(function() {shiftTrace(1)});
|
||
|
||
$("#leg_prev").click(function() {legShift(-1)});
|
||
$("#leg_next").click(function() {legShift(1)});
|
||
|
||
|
||
$("#altitude_filter_reset_button").click(onResetAltitudeFilter);
|
||
$("#callsign_filter_reset_button").click(onResetCallsignFilter);
|
||
$("#type_filter_reset_button").click(onResetTypeFilter);
|
||
$("#description_filter_reset_button").click(onResetDescriptionFilter);
|
||
$("#icao_filter_reset_button").click(onResetIcaoFilter);
|
||
|
||
// check if the altitude color values are default to enable the altitude filter
|
||
customAltitudeColors = JSON.stringify(ColorByAlt) !== JSON.stringify(defaultColorByAlt); // should be good enough for compare in our case
|
||
|
||
$('#settingsCog').on('click', function() {
|
||
$('#settings_infoblock').toggle();
|
||
});
|
||
|
||
$('#settings_close').on('click', function() {
|
||
$('#settings_infoblock').hide();
|
||
});
|
||
|
||
$('#groundvehicle_filter').on('click', function() {
|
||
filterGroundVehicles(true);
|
||
refreshSelected();
|
||
refreshHighlighted();
|
||
refreshTableInfo();
|
||
});
|
||
|
||
$('#blockedmlat_filter').on('click', function() {
|
||
filterBlockedMLAT(true);
|
||
refreshSelected();
|
||
refreshHighlighted();
|
||
refreshTableInfo();
|
||
});
|
||
|
||
$('#grouptype_checkbox').on('click', function() {
|
||
if ($('#grouptype_checkbox').hasClass('settingsCheckboxChecked')) {
|
||
sortByDistance();
|
||
} else {
|
||
sortByDataSource();
|
||
}
|
||
|
||
});
|
||
|
||
$('#altitude_checkbox').on('click', function() {
|
||
toggleAltitudeChart(true);
|
||
});
|
||
|
||
$('#lastLeg_checkbox').on('click', function() {
|
||
toggleLastLeg();
|
||
});
|
||
|
||
if (onMobile) {
|
||
$('#large_mode_button').css('width', 'calc( 45px * let(--SCALE))');
|
||
$('#large_mode_button').css('height', 'calc( 45px * let(--SCALE))');
|
||
if (localStorage['largeMode'] == undefined && largeMode == 1)
|
||
largeMode = 2;
|
||
globeTableLimit = 40;
|
||
}
|
||
|
||
largeMode--;
|
||
toggleLargeMode();
|
||
|
||
if (localStorage['lastLeg'] === "true")
|
||
lastLeg = true;
|
||
else if (localStorage['lastLeg'] === "false")
|
||
lastLeg = false;
|
||
|
||
if (lastLeg)
|
||
$('#lastLeg_checkbox').addClass('settingsCheckboxChecked');
|
||
else
|
||
$('#lastLeg_checkbox').removeClass('settingsCheckboxChecked');
|
||
|
||
$('#debugAll_checkbox').on('click', function() {
|
||
toggleDebugAll();
|
||
});
|
||
|
||
if (localStorage['debugAll'] === "true") {
|
||
debugAll = true;
|
||
$('#debugAll_checkbox').addClass('settingsCheckboxChecked');
|
||
} else {
|
||
debugAll = false;
|
||
$('#debugAll_checkbox').removeClass('settingsCheckboxChecked');
|
||
}
|
||
|
||
$('#debug_checkbox').on('click', function() {
|
||
toggleDebugTracks();
|
||
});
|
||
|
||
if (localStorage['debugTracks'] === "true") {
|
||
debugTracks = true;
|
||
$('#debug_checkbox').addClass('settingsCheckboxChecked');
|
||
} else {
|
||
debugTracks = false;
|
||
$('#debug_checkbox').removeClass('settingsCheckboxChecked');
|
||
}
|
||
$('#tStop').on('click', function() { traceOpts.replaySpeed = 0; });
|
||
$('#t1x').on('click', function() { traceOpts.replaySpeed = 1; legShift(); });
|
||
$('#t5x').on('click', function() { traceOpts.replaySpeed = 5; legShift(); });
|
||
$('#t10x').on('click', function() { traceOpts.replaySpeed = 10; legShift(); });
|
||
$('#t20x').on('click', function() { traceOpts.replaySpeed = 20; legShift(); });
|
||
$('#t40x').on('click', function() { traceOpts.replaySpeed = 40; legShift(); });
|
||
|
||
new Toggle("ColoredPlanes", true, function(state) {
|
||
if (state)
|
||
monochromeMarkers = null;
|
||
else
|
||
monochromeMarkers = "#EEEEEE";
|
||
|
||
refreshFeatures();
|
||
});
|
||
new Toggle("ColoredTrails", true, function(state) {
|
||
if (state)
|
||
monochromeTracks = null;
|
||
else
|
||
monochromeTracks = "#000000";
|
||
|
||
remakeTrails();
|
||
});
|
||
|
||
$('#selectall_checkbox').on('click', function() {
|
||
if ($('#selectall_checkbox').hasClass('settingsCheckboxChecked')) {
|
||
deselectAllPlanes();
|
||
} else {
|
||
selectAllPlanes();
|
||
}
|
||
})
|
||
$('#mapdim_checkbox').on('click', function() {
|
||
toggleMapDim();
|
||
});
|
||
|
||
// Force map to redraw if sidebar container is resized - use a timer to debounce
|
||
$("#sidebar_container").on("resize", function() {
|
||
clearTimeout(mapResizeTimeout);
|
||
mapResizeTimeout = setTimeout(updateMapSize, 20);
|
||
});
|
||
|
||
filterGroundVehicles(false);
|
||
filterBlockedMLAT(false);
|
||
toggleAltitudeChart(false);
|
||
}
|
||
|
||
|
||
|
||
function push_history() {
|
||
$("#loader_progress").attr('max',nHistoryItems*2);
|
||
for (let i = 0; i < nHistoryItems; i++) {
|
||
push_history_item(i);
|
||
}
|
||
if (globeIndex) {
|
||
parse_history();
|
||
} else if (!nHistoryItems) {
|
||
parse_history();
|
||
console.log("History loading failed");
|
||
}
|
||
}
|
||
|
||
function push_history_item(i) {
|
||
|
||
$.when(deferHistory[i])
|
||
.done(function(json) {
|
||
|
||
if (HistoryChunks) {
|
||
if (json && json.files) {
|
||
for (let i in json.files) {
|
||
PositionHistoryBuffer.push(json.files[i]);
|
||
}
|
||
} else if (json && json.now) {
|
||
PositionHistoryBuffer.push(json);
|
||
}
|
||
} else {
|
||
PositionHistoryBuffer.push(json);
|
||
}
|
||
|
||
|
||
$("#loader_progress").attr('value',HistoryItemsReturned);
|
||
HistoryItemsReturned++;
|
||
if (HistoryItemsReturned == nHistoryItems) {
|
||
parse_history();
|
||
}
|
||
})
|
||
|
||
.fail(function(jqxhr, status, error) {
|
||
|
||
//Doesn't matter if it failed, we'll just be missing a data point
|
||
$("#loader_progress").attr('value',HistoryItemsReturned);
|
||
//console.log(error);
|
||
HistoryItemsReturned++;
|
||
if (HistoryItemsReturned == nHistoryItems) {
|
||
parse_history();
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
|
||
function parse_history() {
|
||
|
||
if (adsbexchange) {
|
||
$('#adsbexchange_header').show();
|
||
if (window.self != window.top) {
|
||
window.top.location.href = "https://www.adsbexchange.com/"
|
||
return;
|
||
}
|
||
}
|
||
if (nHistoryItems) {
|
||
console.timeEnd("Downloaded History");
|
||
console.time("Loaded aircraft tracks from History");
|
||
}
|
||
|
||
for (let i in deferHistory)
|
||
deferHistory[i] = null;
|
||
|
||
initialize_map();
|
||
|
||
if (PositionHistoryBuffer.length > 0) {
|
||
|
||
// Sort history by timestamp
|
||
console.log("Sorting history: " + PositionHistoryBuffer.length);
|
||
PositionHistoryBuffer.sort(function(x,y) { return (y.now - x.now); });
|
||
|
||
// Process history
|
||
let data;
|
||
let h = 0;
|
||
let pruneInt = Math.floor(PositionHistoryBuffer.length/5);
|
||
let currentTime = new Date().getTime()/1000;
|
||
while (data = PositionHistoryBuffer.pop()) {
|
||
|
||
if (pTracks && currentTime - data.now > pTracks * 3600) {
|
||
continue;
|
||
}
|
||
|
||
// process new data
|
||
if (PositionHistoryBuffer.length < 10) {
|
||
processReceiverUpdate(data, false);
|
||
} else {
|
||
processReceiverUpdate(data, true);
|
||
}
|
||
|
||
// update aircraft tracks
|
||
if (data.uat_978 != "true") {
|
||
for (let i = 0; i < PlanesOrdered.length; ++i) {
|
||
let plane = PlanesOrdered[i];
|
||
if (plane.dataSource == "uat")
|
||
plane.updateTrack(uat_now, uat_last);
|
||
else
|
||
plane.updateTrack(now, last);
|
||
}
|
||
}
|
||
|
||
|
||
if (h==1) {
|
||
console.log("Applied history " + h + " from: "
|
||
+ (new Date(now * 1000)).toLocaleTimeString());
|
||
}
|
||
// prune aircraft list
|
||
if(h++ % pruneInt == pruneInt - 1) {
|
||
|
||
console.log("Applied history " + h + " from: "
|
||
+ (new Date(now * 1000)).toLocaleTimeString());
|
||
|
||
reaper();
|
||
}
|
||
}
|
||
|
||
// Final pass to update all planes to their latest state
|
||
console.log("Final history cleanup pass");
|
||
for (let i in PlanesOrdered) {
|
||
let plane = PlanesOrdered[i];
|
||
|
||
if (plane.position && SitePosition)
|
||
plane.sitedist = ol.sphere.getDistance(SitePosition, plane.position);
|
||
|
||
if (uatNoTISB && plane.receiver == "uat" && plane.type && plane.type.substring(0,4) == "tisb") {
|
||
plane.last_message_time -= 999;
|
||
}
|
||
}
|
||
|
||
refreshFeatures();
|
||
refreshTableInfo();
|
||
}
|
||
|
||
PositionHistoryBuffer = null;
|
||
|
||
if (nHistoryItems)
|
||
console.timeEnd("Loaded aircraft tracks from History");
|
||
|
||
console.log("Completing init");
|
||
|
||
refreshSelected();
|
||
refreshHighlighted();
|
||
|
||
// Setup our timer to poll from the server.
|
||
window.setInterval(reaper, 20000);
|
||
if (tempTrails) {
|
||
window.setInterval(trailReaper, 10000);
|
||
trailReaper(now);
|
||
}
|
||
if (enable_pf_data) {
|
||
window.setInterval(fetchPfData, RefreshInterval*10.314);
|
||
}
|
||
//window.setInterval(refreshTableInfo, 1000);
|
||
//window.setInterval(function() {PendingFetches--;}, 10000);
|
||
setInterval(decrementTraceRate, 1000);
|
||
|
||
pathName = window.location.pathname;
|
||
processURLParams();
|
||
|
||
if (!icaoFilter && globeIndex)
|
||
toggleTableInView(true);
|
||
|
||
changeZoom("init");
|
||
changeCenter("init");
|
||
|
||
if (heatmap)
|
||
setInterval(checkMovement, 250);
|
||
else if (globeIndex)
|
||
setInterval(checkMovement, 80);
|
||
else
|
||
setInterval(checkMovement, 30);
|
||
|
||
// And kick off one refresh immediately.
|
||
if (!heatmap && !pTracks)
|
||
fetchData();
|
||
if (replay) {
|
||
initReplay();
|
||
play(); // kick off first play
|
||
}
|
||
|
||
if (!globeIndex) {
|
||
$('#show_trace').hide();
|
||
}
|
||
if (globeIndex) {
|
||
$('#V').hide();
|
||
$('#uat_legend_2').hide();
|
||
$('#mode_s_legend_2').hide();
|
||
} else {
|
||
$('#unknown_legend_2').hide();
|
||
$('#sat_legend_2').hide();
|
||
}
|
||
|
||
updateMapSize();
|
||
|
||
loadFinished = true;
|
||
|
||
//drawAlt();
|
||
|
||
if (localStorage['sidebar_visible'] == "false")
|
||
toggleSidebarVisibility();
|
||
|
||
if (onMobile && localStorage['sidebar_visible'] == undefined)
|
||
toggleSidebarVisibility();
|
||
|
||
if (hideButtons) {
|
||
$('#large_mode_control').hide();
|
||
$('#header_top').hide();
|
||
$('#header_side').hide();
|
||
$('#splitter').hide();
|
||
$('#jumpSearch').hide();
|
||
$('#filterButton').hide();
|
||
$('.ol-control').hide();
|
||
$('.ol-attribution').show();
|
||
}
|
||
|
||
if (tempTrails)
|
||
selectAllPlanes();
|
||
|
||
geoMag = geoMagFactory(cof2Obj());
|
||
|
||
if (!heatmap)
|
||
$("#loader").addClass("hidden");
|
||
|
||
}
|
||
|
||
// Make a LineString with 'points'-number points
|
||
// that is a closed circle on the sphere such that the
|
||
// great circle distance from 'center' to each point is
|
||
// 'radius' meters
|
||
function make_geodesic_circle(center, radius, points) {
|
||
let angularDistance = radius / 6378137.0;
|
||
let lon1 = center[0] * Math.PI / 180.0;
|
||
let lat1 = center[1] * Math.PI / 180.0;
|
||
let geom;
|
||
for (let i = 0; i <= points; ++i) {
|
||
let bearing = i * 2 * Math.PI / points;
|
||
|
||
let lat2 = Math.asin( Math.sin(lat1)*Math.cos(angularDistance) +
|
||
Math.cos(lat1)*Math.sin(angularDistance)*Math.cos(bearing) );
|
||
let lon2 = lon1 + Math.atan2(Math.sin(bearing)*Math.sin(angularDistance)*Math.cos(lat1),
|
||
Math.cos(angularDistance)-Math.sin(lat1)*Math.sin(lat2));
|
||
|
||
lat2 = lat2 * 180.0 / Math.PI;
|
||
lon2 = lon2 * 180.0 / Math.PI;
|
||
if (!geom)
|
||
geom = new ol.geom.LineString([[lon2, lat2]]);
|
||
else
|
||
geom.appendCoordinate([lon2, lat2]);
|
||
}
|
||
return geom;
|
||
}
|
||
|
||
// Initalizes the map and starts up our timers to call letious functions
|
||
function initialize_map() {
|
||
if (receiverJson && receiverJson.lat != null) {
|
||
SiteLat = receiverJson.lat;
|
||
SiteLon = receiverJson.lon;
|
||
DefaultCenterLat = receiverJson.lat;
|
||
DefaultCenterLon = receiverJson.lon;
|
||
}
|
||
if (receiverJson && receiverJson.globeIndexGrid != null) {
|
||
globeIndexGrid = receiverJson.globeIndexGrid;
|
||
globeIndex = 1;
|
||
globeIndexSpecialTiles = receiverJson.globeIndexSpecialTiles;
|
||
$('#dump1090_total_history_td').hide();
|
||
$('#dump1090_message_rate_td').hide();
|
||
}
|
||
// Load stored map settings if present
|
||
CenterLon = Number(localStorage['CenterLon']) || DefaultCenterLon;
|
||
CenterLat = Number(localStorage['CenterLat']) || DefaultCenterLat;
|
||
ZoomLvl = Number(localStorage['ZoomLvl']) || DefaultZoomLvl;
|
||
ZoomLvlCache = ZoomLvl;
|
||
if (overrideMapType)
|
||
MapType_tar1090 = overrideMapType;
|
||
else if (localStorage['MapType_tar1090']) {
|
||
MapType_tar1090 = localStorage['MapType_tar1090'];
|
||
}
|
||
|
||
// Maybe hide flag info
|
||
if (!ShowFlags) {
|
||
PlaneRowTemplate.cells[1].style.display = 'none'; // hide flag column
|
||
document.getElementById("flag").style.display = 'none'; // hide flag header
|
||
}
|
||
|
||
// Initialize OL3
|
||
|
||
layers_group = createBaseLayers();
|
||
layers = layers_group.getLayers();
|
||
|
||
iconLayer = new ol.layer.Vector({
|
||
name: 'ac_positions',
|
||
type: 'overlay',
|
||
title: 'Aircraft positions',
|
||
source: PlaneIconFeatures,
|
||
declutter: false,
|
||
zIndex: 200,
|
||
renderBuffer: 20,
|
||
});
|
||
|
||
layers.push(
|
||
new ol.layer.Vector({
|
||
name: 'site_pos',
|
||
type: 'overlay',
|
||
title: 'Site position and range rings',
|
||
source: StaticFeatures,
|
||
visible: !adsbexchange,
|
||
zIndex: 100,
|
||
renderOrder: null,
|
||
}));
|
||
|
||
trailLayers = new ol.layer.Group({
|
||
name: 'ac_trail',
|
||
title: 'Aircraft trails',
|
||
type: 'overlay',
|
||
layers: trailGroup,
|
||
zIndex: 150,
|
||
});
|
||
|
||
layers.push(trailLayers);
|
||
|
||
layers.push(iconLayer);
|
||
|
||
let foundType = false;
|
||
let baseCount = 0;
|
||
|
||
const dummyLayer = new ol.layer.Vector({
|
||
name: 'dummy',
|
||
renderOrder: null,
|
||
});
|
||
|
||
trailGroup.push(dummyLayer);
|
||
|
||
ol.control.LayerSwitcher.forEachRecursive(layers_group, function(lyr) {
|
||
if (!lyr.get('name'))
|
||
return;
|
||
|
||
if (lyr.get('type') === 'base') {
|
||
baseCount++;
|
||
if (MapType_tar1090 === lyr.get('name')) {
|
||
foundType = true;
|
||
lyr.setVisible(true);
|
||
} else {
|
||
lyr.setVisible(false);
|
||
}
|
||
|
||
lyr.on('change:visible', function(evt) {
|
||
if (evt.target.getVisible()) {
|
||
MapType_tar1090 = localStorage['MapType_tar1090'] = evt.target.get('name');
|
||
}
|
||
});
|
||
} else if (lyr.get('type') === 'overlay') {
|
||
let visible = localStorage['layer_' + lyr.get('name')];
|
||
if (visible != undefined) {
|
||
// javascript, why must you taunt me with gratuitous type problems
|
||
lyr.setVisible(visible === "true");
|
||
}
|
||
|
||
lyr.on('change:visible', function(evt) {
|
||
localStorage['layer_' + evt.target.get('name')] = evt.target.getVisible();
|
||
});
|
||
}
|
||
})
|
||
|
||
if (!foundType) {
|
||
ol.control.LayerSwitcher.forEachRecursive(layers_group, function(lyr) {
|
||
if (foundType)
|
||
return;
|
||
if (lyr.get('type') === 'base') {
|
||
lyr.setVisible(true);
|
||
foundType = true;
|
||
}
|
||
});
|
||
}
|
||
|
||
OLMap = new ol.Map({
|
||
target: 'map_canvas',
|
||
layers: layers,
|
||
view: new ol.View({
|
||
center: ol.proj.fromLonLat([CenterLon, CenterLat]),
|
||
zoom: ZoomLvl,
|
||
minZoom: 2,
|
||
}),
|
||
controls: [new ol.control.Zoom({delta: 1, duration: 0, target: 'map_container',}),
|
||
new ol.control.Attribution({collapsed: true}),
|
||
new ol.control.ScaleLine({units: DisplayUnits})
|
||
],
|
||
interactions: new ol.interaction.defaults({altShiftDragRotate:false, pinchRotate:false,}),
|
||
});
|
||
OLProj = OLMap.getView().getProjection();
|
||
|
||
OLMap.getView().setRotation(mapOrientation); // adjust orientation
|
||
|
||
if (baseCount > 1) {
|
||
OLMap.addControl(new ol.control.LayerSwitcher({
|
||
groupSelectStyle: 'none',
|
||
target: 'map_container',
|
||
}));
|
||
}
|
||
|
||
/*
|
||
// Listeners for newly created Map
|
||
OLMap.getView().on('change:center', function(event) {
|
||
const center = ol.proj.toLonLat(OLMap.getView().getCenter(), OLMap.getView().getProjection());
|
||
CenterLat = center[1];
|
||
CenterLon = center[0];
|
||
if (FollowSelected) {
|
||
// On manual navigation, disable follow
|
||
if (!SelectedPlane || !SelectedPlane.position ||
|
||
(Math.abs(center[0] - SelectedPlane.position[0]) > 0.0001 &&
|
||
Math.abs(center[1] - SelectedPlane.position[1]) > 0.0001)){
|
||
FollowSelected = false;
|
||
refreshSelected();
|
||
refreshHighlighted();
|
||
}
|
||
}
|
||
});
|
||
*/
|
||
|
||
/*
|
||
OLMap.getView().on('change:resolution', function(event) {
|
||
ZoomLvl = OLMap.getView().getZoom();
|
||
});
|
||
*/
|
||
|
||
OLMap.on(['click', 'dblclick'], function(evt) {
|
||
let res = evt.map.forEachFeatureAtPixel(
|
||
evt.pixel,
|
||
function(feature, layer) {
|
||
if (showTrace)
|
||
return feature.timestamp;
|
||
return feature.hex;
|
||
},
|
||
{
|
||
layerFilter: function(layer) {
|
||
return (layer == iconLayer || layer.get('isTrail') == true);
|
||
},
|
||
hitTolerance: 6 * globalScale,
|
||
}
|
||
);
|
||
if (showTrace && res) {
|
||
gotoTime(res);
|
||
} else if (res) {
|
||
selectPlaneByHex(res, {follow: (evt.type === 'dblclick')});
|
||
} else if (!multiSelect) {
|
||
deselectAllPlanes();
|
||
}
|
||
evt.stopPropagation();
|
||
});
|
||
|
||
|
||
// show the hover box
|
||
if (!globeIndex && ZoomLvl > 5.5 && enableMouseover) {
|
||
OLMap.on('pointermove', onPointermove);
|
||
}
|
||
|
||
// handle the layer settings pane checkboxes
|
||
OLMap.once('postrender', function(e) {
|
||
toggleLayer('#nexrad_checkbox', 'nexrad');
|
||
//toggleLayer('#sitepos_checkbox', 'site_pos');
|
||
toggleLayer('#actrail_checkbox', 'ac_trail');
|
||
toggleLayer('#acpositions_checkbox', 'ac_positions');
|
||
});
|
||
|
||
if (localStorage['MapDim'] === "true" || (MapDim && localStorage['MapDim'] == null)) {
|
||
toggleMapDim(true);
|
||
}
|
||
|
||
window.addEventListener('keydown', function(e) {
|
||
active();
|
||
if (e.defaultPrevented ) {
|
||
return; // Do nothing if the event was already processed
|
||
}
|
||
if (e.target.type == "text") {
|
||
return;
|
||
}
|
||
if (e.srcElement.nodeName == 'INPUT') {
|
||
return;
|
||
}
|
||
|
||
if( e.ctrlKey || e.altKey || e.metaKey) {
|
||
return;
|
||
}
|
||
let oldCenter, extent, newCenter;
|
||
switch (e.key) {
|
||
case "c":
|
||
case "Esc":
|
||
case "Escape":
|
||
deselectAllPlanes();
|
||
break;
|
||
// zoom and movement
|
||
case "q":
|
||
zoomOut();
|
||
break;
|
||
case "e":
|
||
zoomIn();
|
||
break;
|
||
case "w":
|
||
oldCenter = OLMap.getView().getCenter();
|
||
extent = OLMap.getView().calculateExtent(OLMap.getSize());
|
||
newCenter = [oldCenter[0], (oldCenter[1] + extent[3])/2];
|
||
OLMap.getView().setCenter(newCenter);
|
||
toggleFollow(false);
|
||
break;
|
||
case "s":
|
||
oldCenter = OLMap.getView().getCenter();
|
||
extent = OLMap.getView().calculateExtent(OLMap.getSize());
|
||
newCenter = [oldCenter[0], (oldCenter[1] + extent[1])/2];
|
||
OLMap.getView().setCenter(newCenter);
|
||
toggleFollow(false);
|
||
break;
|
||
case "a":
|
||
oldCenter = OLMap.getView().getCenter();
|
||
extent = OLMap.getView().calculateExtent(OLMap.getSize());
|
||
newCenter = [(oldCenter[0] + extent[0])/2, oldCenter[1]];
|
||
OLMap.getView().setCenter(newCenter);
|
||
toggleFollow(false);
|
||
break;
|
||
case "d":
|
||
oldCenter = OLMap.getView().getCenter();
|
||
extent = OLMap.getView().calculateExtent(OLMap.getSize());
|
||
newCenter = [(oldCenter[0] + extent[2])/2, oldCenter[1]];
|
||
OLMap.getView().setCenter(newCenter);
|
||
toggleFollow(false);
|
||
break;
|
||
// misc
|
||
case "b":
|
||
toggleMapDim();
|
||
break;
|
||
case "m":
|
||
toggleMultiSelect();
|
||
break;
|
||
case "v":
|
||
toggleTableInView();
|
||
break;
|
||
case "r":
|
||
if (heatmap && !replay)
|
||
drawHeatmap();
|
||
else
|
||
followRandomPlane();
|
||
break;
|
||
case "R":
|
||
fetchData();
|
||
break;
|
||
case "t":
|
||
selectAllPlanes();
|
||
break;
|
||
case "h":
|
||
resetMap();
|
||
break;
|
||
case "H":
|
||
if (!hideButtons) {
|
||
$('#large_mode_control').hide();
|
||
$('#header_top').hide();
|
||
$('#header_side').hide();
|
||
$('#splitter').hide();
|
||
$('#jumpSearch').hide();
|
||
$('#filterButton').hide();
|
||
$('.ol-control').hide();
|
||
$('.ol-attribution').show();
|
||
} else {
|
||
$('#large_mode_control').show();
|
||
$('#header_top').show();
|
||
$('#header_side').show();
|
||
$('#splitter').show();
|
||
$('#jumpSearch').show();
|
||
$('#filterButton').show();
|
||
$('.ol-control').show();
|
||
$('#expand_sidebar_control').hide();
|
||
toggleSidebarVisibility();
|
||
toggleSidebarVisibility();
|
||
if (altitudeChartDisplay != 'show')
|
||
$('#altitude_chart').hide();
|
||
}
|
||
hideButtons = !hideButtons;
|
||
break;
|
||
case "f":
|
||
toggleFollow();
|
||
break;
|
||
// filters
|
||
case "M":
|
||
onlyMLAT = !onlyMLAT;
|
||
refreshTableInfo();
|
||
break;
|
||
case "T":
|
||
filterTISB = !filterTISB;
|
||
break;
|
||
case "u":
|
||
toggleMilitary();
|
||
break;
|
||
case "A":
|
||
onlyADSB = !onlyADSB;
|
||
break;
|
||
// persistance mode
|
||
case "i":
|
||
toggleIsolation();
|
||
break;
|
||
case "p":
|
||
togglePersistence();
|
||
break;
|
||
// Labels
|
||
case "l":
|
||
toggleLabels();
|
||
break;
|
||
case "o":
|
||
toggleExtendedLabels();
|
||
break;
|
||
case "k":
|
||
toggleTrackLabels();
|
||
break;
|
||
// debug stuff
|
||
case "L":
|
||
toggleLastLeg();
|
||
break;
|
||
case "D":
|
||
debug = !debug;
|
||
localStorage['debug'] = debug;
|
||
console.log('debug = ' + debug);
|
||
break;
|
||
case "P":
|
||
debugPosFilter = !debugPosFilter;
|
||
localStorage['debugPosFilter'] = debugPosFilter;
|
||
console.log('debugPosFilter = ' + debugPosFilter);
|
||
break;
|
||
case "?":
|
||
if (!SelectedPlane) {
|
||
console.log("No plane selected");
|
||
break;
|
||
}
|
||
console.log(SelectedPlane.icao + ": " + SelectedPlane.baseMarkerKey + " " + SelectedPlane.shape);
|
||
console.log(SelectedPlane);
|
||
console.log(SelectedPlane.milRange());
|
||
break;
|
||
case "j":
|
||
selectPlaneByHex(jumpTo, {follow: true});
|
||
break;
|
||
case "J":
|
||
debugJump = !debugJump;
|
||
localStorage['debugJump'] = debugJump;
|
||
console.log('debugJump = ' + debugJump);
|
||
break;
|
||
case "N":
|
||
noMLAT = !noMLAT;
|
||
localStorage['noMLAT'] = noMLAT;
|
||
console.log('noMLAT = ' + noMLAT);
|
||
break;
|
||
}
|
||
}, true);
|
||
|
||
if (globeIndex || uuid || askLocation)
|
||
geoFindMe();
|
||
else {
|
||
initSitePos();
|
||
}
|
||
|
||
}
|
||
|
||
// This looks for planes to reap out of the master Planes letiable
|
||
function reaper(all) {
|
||
//console.log("Reaping started..");
|
||
today = new Date().getDate();
|
||
if (noVanish)
|
||
return;
|
||
reaping = true;
|
||
|
||
// Look for planes where we have seen no messages for >300 seconds
|
||
let plane;
|
||
let length = PlanesOrdered.length;
|
||
for (let i = 0; i < length; i++) {
|
||
plane = PlanesOrdered.shift()
|
||
if (plane == null)
|
||
continue;
|
||
plane.seen = now - plane.last_message_time;
|
||
if ( (!plane.selected || SelectedAllPlanes)
|
||
&& (all || plane.seen > 300)
|
||
&& (plane.dataSource != 'adsc' || plane.seen > 35*60)
|
||
) {
|
||
// Reap it.
|
||
//console.log("Removed " + plane.icao);
|
||
delete Planes[plane.icao];
|
||
plane.destroy();
|
||
} else {
|
||
// Keep it.
|
||
PlanesOrdered.push(plane);
|
||
}
|
||
};
|
||
|
||
//console.log(length - PlanesOrdered.length);
|
||
return (length - PlanesOrdered.length);
|
||
}
|
||
|
||
// Page Title update function
|
||
function refreshPageTitle() {
|
||
if (!PlaneCountInTitle && !MessageRateInTitle) {
|
||
return;
|
||
}
|
||
|
||
let subtitle = "";
|
||
|
||
if (PlaneCountInTitle) {
|
||
if (globeIndex) {
|
||
subtitle += 'tracking ' + globeTrackedAircraft + ' aircraft';
|
||
} else {
|
||
subtitle += TrackedAircraftPositions + '/' + TrackedAircraft;
|
||
}
|
||
}
|
||
|
||
if (MessageRateInTitle && MessageRate != null) {
|
||
if (subtitle) subtitle += ' | ';
|
||
subtitle += MessageRate.toFixed(1) + '/s';
|
||
}
|
||
|
||
document.title = PageName + ' - ' + subtitle;
|
||
}
|
||
|
||
let selCall = null;
|
||
let selIcao = null;
|
||
let selReg = null;
|
||
|
||
// Refresh the detail window about the plane
|
||
function refreshSelected() {
|
||
|
||
buttonActive('#F', FollowSelected);
|
||
|
||
/*
|
||
if (SelectedPlane && SelectedPlane.isFiltered()) {
|
||
SelectedPlane.selected = false;
|
||
SelectedPlane.clearLines();
|
||
SelectedPlane = null;
|
||
}
|
||
*/
|
||
|
||
if (!SelectedPlane) {
|
||
setSelectedInfoBlockVisibility();
|
||
return;
|
||
}
|
||
const selected = SelectedPlane;
|
||
|
||
if (SelectedPlane.position)
|
||
SelectedPlane.updateMarker(true);
|
||
if (selected.flight != selCall) {
|
||
selCall = selected.flight;
|
||
if (selected.flight && selected.flight.trim()) {
|
||
$('#selected_callsign').text(selected.flight);
|
||
} else {
|
||
$('#selected_callsign').text('n/a');
|
||
}
|
||
}
|
||
if (flightawareLinks) {
|
||
$('#selected_flightaware_link').html(getFlightAwareModeSLink(selected.icao, selected.flight, "Visit Flight Page"));
|
||
}
|
||
|
||
if (selected.isNonIcao()) {
|
||
$('#tisb_info').removeClass('hidden');
|
||
$('#reg_info').addClass('hidden');
|
||
} else {
|
||
$('#tisb_info').addClass('hidden');
|
||
$('#reg_info').removeClass('hidden');
|
||
}
|
||
if (selected.registration != selReg) {
|
||
selReg = selected.registration
|
||
if (selected.registration) {
|
||
if (flightawareLinks) {
|
||
$('#selected_registration').html(getFlightAwareIdentLink(selected.registration, selected.registration));
|
||
} else {
|
||
$('#selected_registration').text(selected.registration);
|
||
}
|
||
} else {
|
||
$('#selected_registration').text("n/a");
|
||
}
|
||
}
|
||
|
||
if (selected.icaoType) {
|
||
$('#selected_icaotype').text(selected.icaoType);
|
||
} else {
|
||
$('#selected_icaotype').text("n/a");
|
||
}
|
||
if (selected.typeDescription)
|
||
$('#selected_typedesc').text(selected.typeDescription);
|
||
else
|
||
$('#selected_typedesc').text("n/a");
|
||
|
||
if (selected.typeLong)
|
||
$('#selected_typelong').text(selected.typeLong);
|
||
else
|
||
$('#selected_typelong').text("n/a");
|
||
|
||
if (showPictures && selected.icaoType){
|
||
let new_html = "<img width='150px' src='aircraft_sil/" + selected.icaoType + ".png' />";
|
||
if (new_html != selectedPhotoCache) {
|
||
$('#selected_photo').html(new_html);
|
||
selectedPhotoCache = new_html;
|
||
}
|
||
} else {
|
||
$('#selected_photo').text("");
|
||
}
|
||
|
||
// Not using this logic for the redesigned info panel at the time, but leaving it in if/when adding it back
|
||
// let 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_altitude1").text(format_altitude_long(selected.altitude, selected.vert_rate, DisplayUnits));
|
||
$("#selected_altitude2").text(format_altitude_long(selected.altitude, selected.vert_rate, DisplayUnits));
|
||
|
||
$('#selected_onground').text(format_onground(selected.altitude));
|
||
|
||
if (selected.squawk == null || selected.squawk == '0000') {
|
||
$('#selected_squawk1').text('n/a');
|
||
$('#selected_squawk2').text('n/a');
|
||
} else {
|
||
$('#selected_squawk1').text(selected.squawk);
|
||
$('#selected_squawk2').text(selected.squawk);
|
||
}
|
||
|
||
let magResult = null;
|
||
|
||
if (geoMag && selected.position != null) {
|
||
let lon = selected.position[0];
|
||
let lat = selected.position[1];
|
||
let alt = selected.altitude == "ground" ? 0 : selected.altitude;
|
||
magResult = geoMag(lat, lon, alt);
|
||
$('#selected_mag_declination').text(format_track_brief(magResult.dec));
|
||
} else {
|
||
$('#selected_mag_declination').text('n/a');
|
||
}
|
||
|
||
let crab = null;
|
||
let heading = null;
|
||
let track = selected.track;
|
||
if (selected.true_heading != null && selected.track != null) {
|
||
heading = selected.true_heading;
|
||
} else if (magResult && selected.mag_heading != null && selected.track != null) {
|
||
heading = selected.mag_heading + magResult.dec;
|
||
}
|
||
if (heading != null && heading < 0)
|
||
heading += 360;
|
||
if (heading != null && heading > 360)
|
||
heading -= 360;
|
||
if (heading != null && track != null) {
|
||
crab = heading - track;
|
||
if (crab > 180)
|
||
crab -= 360;
|
||
if (crab < -180)
|
||
crab += 360;
|
||
}
|
||
|
||
$('#selected_mag_heading').text(format_track_brief(selected.mag_heading));
|
||
|
||
if (selected.wd != null && selected.ws != null) {
|
||
$('#selected_wd').text(format_track_brief(selected.wd, true));
|
||
$('#selected_ws').text(format_speed_long(selected.ws, DisplayUnits));
|
||
} else if (!globeIndex && magResult && selected.gs != null && selected.tas != null && selected.track != null && selected.mag_heading != null) {
|
||
|
||
const trk = (Math.PI / 180) * selected.track;
|
||
const hdg = (Math.PI / 180) * heading;
|
||
const tas = selected.tas;
|
||
const gs = selected.gs;
|
||
const ws = Math.round(Math.sqrt(Math.pow(tas - gs, 2) + 4 * tas * gs * Math.pow(Math.sin((hdg - trk) / 2), 2)));
|
||
let wd = trk + Math.atan2(tas * Math.sin(hdg - trk), tas * Math.cos(hdg - trk) - gs);
|
||
if (wd < 0) {
|
||
wd = wd + 2 * Math.PI;
|
||
}
|
||
if (wd > 2 * Math.PI) {
|
||
wd = wd - 2 * Math.PI;
|
||
}
|
||
wd = Math.round((180 / Math.PI) * wd);
|
||
$('#selected_wd').text(format_track_brief(wd, true));
|
||
$('#selected_ws').text(format_speed_long(ws, DisplayUnits));
|
||
} else {
|
||
$('#selected_wd').text('n/a');
|
||
$('#selected_ws').text('n/a');
|
||
}
|
||
|
||
|
||
if (!globeIndex && selected.true_heading == null && heading != null)
|
||
$('#selected_true_heading').text(format_track_brief(heading));
|
||
else
|
||
$('#selected_true_heading').text(format_track_brief(selected.true_heading));
|
||
|
||
/*
|
||
if (globeIndex && selected.true_heading == null)
|
||
crab = null;
|
||
|
||
$('#selected_crab').text(format_track_brief(crab));
|
||
*/
|
||
|
||
|
||
let oat = null;
|
||
let tat = null;
|
||
|
||
if (selected.tat != null && selected.oat != null) {
|
||
oat = selected.oat;
|
||
tat = selected.tat;
|
||
} else if (!globeIndex && selected.mach != null && selected.tas != null && selected.mach > 0.395) {
|
||
oat = Math.pow((selected.tas / 661.47 / selected.mach), 2) * 288.15 - 273.15;
|
||
tat = -273.15 + (oat + 273.15) * (1 + 0.2 * selected.mach * selected.mach);
|
||
}
|
||
|
||
|
||
if (oat != null)
|
||
$('#selected_temp').text(Math.round(tat) + ' / ' + Math.round(oat) + ' °C');
|
||
else
|
||
$('#selected_temp').text('n/a');
|
||
|
||
$('#selected_speed1').text(format_speed_long(selected.gs, DisplayUnits));
|
||
$('#selected_speed2').text(format_speed_long(selected.gs, DisplayUnits));
|
||
$('#selected_ias').text(format_speed_long(selected.ias, DisplayUnits));
|
||
$('#selected_tas').text(format_speed_long(selected.tas, DisplayUnits));
|
||
if (selected.geom_rate != null) {
|
||
$('#selected_vert_rate').text(format_vert_rate_long(selected.geom_rate, DisplayUnits));
|
||
} else {
|
||
$('#selected_vert_rate').text(format_vert_rate_long(selected.baro_rate, DisplayUnits));
|
||
}
|
||
$('#selected_baro_rate').text(format_vert_rate_long(selected.baro_rate, DisplayUnits));
|
||
$('#selected_geom_rate').text(format_vert_rate_long(selected.geom_rate, DisplayUnits));
|
||
if (selected.icao != selIcao) {
|
||
selIcao = selected.icao;
|
||
if (globeIndex) {
|
||
let icao_link = "<a style=\"color: blue\" target=\"_blank\" href=\"" + shareLink + "\">Share</a>";
|
||
icao_link = NBSP +NBSP +NBSP +NBSP +NBSP +NBSP + icao_link;
|
||
$('#selected_icao').html(selected.icao.toUpperCase() + icao_link);
|
||
} else {
|
||
$('#selected_icao').text(selected.icao.toUpperCase());
|
||
}
|
||
}
|
||
$('#selected_pf_info').text((selected.pfRoute ? selected.pfRoute : "") );
|
||
//+" "+ (selected.pfFlightno ? selected.pfFlightno : "")
|
||
$('#airframes_post_icao').attr('value',selected.icao);
|
||
$('#selected_track1').text(format_track_brief(selected.track));
|
||
$('#selected_track2').text(format_track_brief(selected.track));
|
||
|
||
if (selected.seen != null && selected.seen < 1000000) {
|
||
$('#selected_seen').text(format_duration(selected.seen));
|
||
} else {
|
||
$('#selected_seen').text('n/a');
|
||
}
|
||
if (selected.seen_pos != null && selected.seen_pos < 1000000) {
|
||
$('#selected_seen_pos').text(format_duration(selected.seen_pos));
|
||
} else {
|
||
$('#selected_seen_pos').text('n/a');
|
||
}
|
||
|
||
$('#selected_country').text(selected.icaorange.country.replace("special use", "special"));
|
||
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 {
|
||
|
||
if (selected.seen_pos > -1) {
|
||
$('#selected_position').text(format_latlng(selected.position));
|
||
} else {
|
||
$('#selected_position').text(format_latlng(selected.position));
|
||
}
|
||
|
||
checkFollow();
|
||
}
|
||
if (selected.position && SitePosition) {
|
||
selected.sitedist = ol.sphere.getDistance(SitePosition, selected.position);
|
||
}
|
||
$('#selected_source').text(format_data_source(selected.getDataSource()));
|
||
$('#selected_category').text(selected.category ? selected.category : "n/a");
|
||
$('#selected_sitedist1').text(format_distance_long(selected.sitedist, DisplayUnits));
|
||
$('#selected_sitedist2').text(format_distance_long(selected.sitedist, DisplayUnits));
|
||
$('#selected_rssi1').text(selected.rssi != null ? selected.rssi.toFixed(1) : "n/a");
|
||
$('#selected_message_count').text(selected.messages);
|
||
$('#selected_message_rate').text((selected.messageRate != null) ? (selected.messageRate.toFixed(1)) : "n/a");
|
||
$('#selected_photo_link').html(getPhotoLink(selected));
|
||
|
||
$('#selected_altitude_geom').text(format_altitude_long(selected.alt_geom, selected.geom_rate, DisplayUnits));
|
||
$('#selected_ias').text(format_speed_long(selected.ias, DisplayUnits));
|
||
$('#selected_tas').text(format_speed_long(selected.tas, DisplayUnits));
|
||
if (selected.mach == null) {
|
||
$('#selected_mach').text('n/a');
|
||
} else {
|
||
$('#selected_mach').text(selected.mach.toFixed(3));
|
||
}
|
||
if (selected.roll == null) {
|
||
$('#selected_roll').text('n/a');
|
||
} else {
|
||
$('#selected_roll').text(selected.roll.toFixed(1));
|
||
}
|
||
if (selected.track_rate == null) {
|
||
$('#selected_trackrate').text('n/a');
|
||
} else {
|
||
$('#selected_trackrate').text(selected.track_rate.toFixed(2));
|
||
}
|
||
$('#selected_geom_rate').text(format_vert_rate_long(selected.geom_rate, DisplayUnits));
|
||
if (selected.nav_qnh == null) {
|
||
$('#selected_nav_qnh').text("n/a");
|
||
} else {
|
||
$('#selected_nav_qnh').text(selected.nav_qnh.toFixed(1) + " hPa");
|
||
}
|
||
$('#selected_nav_altitude').text(format_altitude_long(selected.nav_altitude, 0, DisplayUnits));
|
||
$('#selected_nav_heading').text(format_track_brief(selected.nav_heading));
|
||
if (selected.nav_modes == null) {
|
||
$('#selected_nav_modes').text("n/a");
|
||
} else {
|
||
$('#selected_nav_modes').text(selected.nav_modes.join());
|
||
}
|
||
if (selected.nic_baro == null) {
|
||
$('#selected_nic_baro').text("n/a");
|
||
} else {
|
||
if (selected.nic_baro == 1) {
|
||
$('#selected_nic_baro').text("cross-checked");
|
||
} else {
|
||
$('#selected_nic_baro').text("not cross-checked");
|
||
}
|
||
}
|
||
|
||
$('#selected_nac_p').text(format_nac_p(selected.nac_p));
|
||
$('#selected_nac_v').text(format_nac_v(selected.nac_v));
|
||
if (selected.rc == null) {
|
||
$('#selected_rc').text("n/a");
|
||
} else if (selected.rc == 0) {
|
||
$('#selected_rc').text("unknown");
|
||
} else {
|
||
$('#selected_rc').text(format_distance_short(selected.rc, DisplayUnits));
|
||
}
|
||
|
||
if (selected.sil == null || selected.sil_type == null) {
|
||
$('#selected_sil').text("n/a");
|
||
} else {
|
||
let sampleRate = "";
|
||
let silDesc = "";
|
||
if (selected.sil_type == "perhour") {
|
||
sampleRate = " per flight hour";
|
||
} else if (selected.sil_type == "persample") {
|
||
sampleRate = " per sample";
|
||
}
|
||
|
||
switch (selected.sil) {
|
||
case 0:
|
||
silDesc = "> 1×10<sup>-3</sup>";
|
||
break;
|
||
case 1:
|
||
silDesc = "≤ 1×10<sup>-3</sup>";
|
||
break;
|
||
case 2:
|
||
silDesc = "≤ 1×10<sup>-5</sup>";
|
||
break;
|
||
case 3:
|
||
silDesc = "≤ 1×10<sup>-7</sup>";
|
||
break;
|
||
default:
|
||
silDesc = "n/a";
|
||
sampleRate = "";
|
||
break;
|
||
}
|
||
$('#selected_sil').html(silDesc + sampleRate);
|
||
}
|
||
|
||
if (selected.version == null) {
|
||
$('#selected_version').text('none');
|
||
} else if (selected.version == 0) {
|
||
$('#selected_version').text('v0 (DO-260)');
|
||
} else if (selected.version == 1) {
|
||
$('#selected_version').text('v1 (DO-260A)');
|
||
} else if (selected.version == 2) {
|
||
$('#selected_version').text('v2 (DO-260B)');
|
||
} else {
|
||
$('#selected_version').text('v' + selected.version);
|
||
}
|
||
|
||
setSelectedInfoBlockVisibility();
|
||
}
|
||
|
||
function refreshHighlighted() {
|
||
// this is following nearly identical logic, etc, as the refreshSelected function, but doing less junk for the highlighted pane
|
||
let highlighted = false;
|
||
|
||
if (!HighlightedPlane || !(highlighted = Planes[HighlightedPlane]) ) {
|
||
$('#highlighted_infoblock').hide();
|
||
return;
|
||
}
|
||
|
||
$('#highlighted_infoblock').show();
|
||
|
||
let infoBox = $('#highlighted_infoblock');
|
||
|
||
|
||
let marker = highlighted.marker;
|
||
let geom;
|
||
let markerCoordinates;
|
||
if (!marker || !(geom = marker.getGeometry()) || !(markerCoordinates = geom.getCoordinates()) ) {
|
||
$('#highlighted_infoblock').hide();
|
||
return;
|
||
}
|
||
let markerPosition = OLMap.getPixelFromCoordinate(markerCoordinates);
|
||
if (!markerPosition)
|
||
return;
|
||
|
||
let mapSize = OLMap.getSize();
|
||
if (markerPosition[0] + 200 < mapSize[0])
|
||
infoBox.css("left", markerPosition[0] + 20);
|
||
else
|
||
infoBox.css("left", markerPosition[0] - 200);
|
||
if (markerPosition[1] + 250 < mapSize[1])
|
||
infoBox.css("top", markerPosition[1] + 50);
|
||
else
|
||
infoBox.css("top", markerPosition[1] - 250);
|
||
|
||
$('#highlighted_callsign').text(highlighted.name);
|
||
|
||
if (highlighted.icaoType !== null) {
|
||
$('#highlighted_icaotype').text(highlighted.icaoType);
|
||
} else {
|
||
$('#highlighted_icaotype').text("n/a");
|
||
}
|
||
|
||
$('#highlighted_source').text(format_data_source(highlighted.getDataSource()));
|
||
|
||
if (highlighted.registration !== null) {
|
||
$('#highlighted_registration').text(highlighted.registration);
|
||
} else {
|
||
$('#highlighted_registration').text("n/a");
|
||
}
|
||
|
||
$('#highlighted_speed').text(format_speed_long(highlighted.gs, DisplayUnits));
|
||
|
||
$("#highlighted_altitude").text(format_altitude_long(highlighted.altitude, highlighted.vert_rate, DisplayUnits));
|
||
|
||
$('#highlighted_pf_route').text((highlighted.pfRoute ? highlighted.pfRoute : highlighted.icao.toUpperCase()));
|
||
|
||
$('#highlighted_rssi').text(highlighted.rssi != null ? highlighted.rssi.toFixed(1) + ' dBFS' : "n/a");
|
||
|
||
}
|
||
|
||
function refreshClock(now_date) {
|
||
let hhmm = now_date.getHours().toString().padStart(2,'0') + ":" + now_date.getMinutes().toString().padStart(2,'0');
|
||
let hms = hhmm + ":" + now_date.getSeconds().toString().padStart(2,'0');
|
||
$('#clock_div').text(hms + " " + now_date.toDateString());
|
||
}
|
||
|
||
function removeHighlight() {
|
||
HighlightedPlane = null;
|
||
refreshHighlighted();
|
||
}
|
||
|
||
function refreshFeatures() {
|
||
for (let i in PlanesOrdered) {
|
||
PlanesOrdered[i].updateTick(true);
|
||
}
|
||
}
|
||
|
||
// Refreshes the larger table of all the planes
|
||
function refreshTableInfo() {
|
||
if (pTracks)
|
||
return;
|
||
refreshPageTitle();
|
||
|
||
resortTable(PlanesOrdered);
|
||
|
||
//$('#dump1090_infoblock').css('display','block');
|
||
$('#dump1090_total_history').text(TrackedHistorySize);
|
||
|
||
if (MessageRate !== null) {
|
||
$('#dump1090_message_rate').text(MessageRate.toFixed(1));
|
||
} else {
|
||
$('#dump1090_message_rate').text("n/a");
|
||
}
|
||
|
||
let show_squawk_warning = false;
|
||
|
||
TrackedAircraft = 0;
|
||
TrackedAircraftPositions = 0;
|
||
TrackedHistorySize = 0;
|
||
let nplanes = 0;
|
||
let nMapPlanes = 0;
|
||
|
||
if (mapIsVisible || lastRealExtent == null) {
|
||
let mapSize = OLMap.getSize();
|
||
|
||
lastRealExtent = myExtent(OLMap.getView().calculateExtent(mapSize));
|
||
|
||
let size = [mapSize[0] * 1.2, mapSize[1] * 1.2];
|
||
lastRenderExtent = myExtent(OLMap.getView().calculateExtent(size));
|
||
}
|
||
|
||
//console.time("updateCells");
|
||
for (let i = 0; i < PlanesOrdered.length; ++i) {
|
||
let plane = PlanesOrdered[i];
|
||
TrackedHistorySize += plane.history_size;
|
||
let classes;
|
||
|
||
|
||
plane.inView = !plane.isFiltered() && inView(plane.position, lastRealExtent);
|
||
|
||
if (globeIndex && !icaoFilter) {
|
||
if (((nMapPlanes < 100 || !onMobile)
|
||
&& (!onMobile || ZoomLvl > 10 || !plane.onGround)
|
||
&& !plane.isFiltered()
|
||
&& inView(plane.position, lastRenderExtent)
|
||
) || (plane.selected && !SelectedAllPlanes)) {
|
||
plane.updateFeatures(now, last);
|
||
} else if (plane.visible) {
|
||
plane.clearMarker();
|
||
plane.clearLines();
|
||
plane.visible = false;
|
||
}
|
||
} else {
|
||
plane.updateTick();
|
||
}
|
||
|
||
|
||
plane.showInTable = false;
|
||
classes = "plane_table_row";
|
||
|
||
if (tableInView && plane.visible &&
|
||
(plane.inView || (plane.selected && !SelectedAllPlanes))
|
||
) {
|
||
plane.showInTable = true;
|
||
++TrackedAircraftPositions;
|
||
nMapPlanes++;
|
||
}
|
||
|
||
if (plane.visible) {
|
||
TrackedAircraft++;
|
||
|
||
if (!tableInView && plane.position != null)
|
||
++TrackedAircraftPositions;
|
||
|
||
if (!tableInView)
|
||
plane.showInTable = true;
|
||
}
|
||
|
||
if (!sidebarVisible || (nplanes > globeTableLimit && mapIsVisible && globeIndex)) {
|
||
plane.showInTable = false;
|
||
continue;
|
||
}
|
||
|
||
|
||
if (plane.showInTable) {
|
||
nplanes++;
|
||
|
||
if (plane.tr == null)
|
||
plane.makeTR();
|
||
|
||
if (plane.dataSource == "uat" || (plane.addrtype && plane.addrtype.substring(0,4) == 'adsr')) {
|
||
classes += " uat";
|
||
} else if (plane.dataSource == "adsb") {
|
||
classes += " vPosition";
|
||
} else if (plane.dataSource == "adsc") {
|
||
classes += " satellite";
|
||
} else if (plane.dataSource == "mode_s") {
|
||
classes += " other";
|
||
} else {
|
||
classes += " ";
|
||
classes += plane.dataSource;
|
||
}
|
||
|
||
if (plane.selected && !SelectedAllPlanes)
|
||
classes += " selected";
|
||
|
||
if (plane.squawk in SpecialSquawks) {
|
||
classes = classes + " " + SpecialSquawks[plane.squawk].cssClass;
|
||
show_squawk_warning = true;
|
||
}
|
||
|
||
// ICAO doesn't change
|
||
if (flightawareLinks) {
|
||
updateCell(plane, 2, getFlightAwareModeSLink(plane.icao, plane.flight, plane.name), true);
|
||
updateCell(plane, 3, getFlightAwareIdentLink(plane.registration, plane.registration), true);
|
||
} else {
|
||
updateCell(plane, 2, plane.name);
|
||
updateCell(plane, 3, plane.registration ? plane.registration : "");
|
||
}
|
||
updateCell(plane, 4, (plane.icaoType != null ? plane.icaoType : ""));
|
||
updateCell(plane, 5, (plane.squawk != null ? plane.squawk : ""));
|
||
updateCell(plane, 6, format_altitude_brief(plane.altitude, plane.vert_rate, DisplayUnits));
|
||
updateCell(plane, 7, format_speed_brief(plane.gs, DisplayUnits));
|
||
updateCell(plane, 8, format_vert_rate_brief(plane.vert_rate, DisplayUnits));
|
||
updateCell(plane, 9, format_distance_brief(plane.sitedist, DisplayUnits));
|
||
updateCell(plane, 10, format_track_brief(plane.track));
|
||
updateCell(plane, 11, plane.messages);
|
||
updateCell(plane, 12, plane.seen.toFixed(0));
|
||
updateCell(plane, 13, (plane.rssi != null ? plane.rssi.toFixed(1) : ""));
|
||
updateCell(plane, 14, (plane.position != null ? plane.position[1].toFixed(4) : ""));
|
||
updateCell(plane, 15, (plane.position != null ? plane.position[0].toFixed(4) : ""));
|
||
updateCell(plane, 16, format_data_source(plane.getDataSource()));
|
||
//updateCell(plane, 17, plane.baseMarkerKey);
|
||
|
||
|
||
}
|
||
if (plane.tr && plane.classesCache != classes) {
|
||
plane.classesCache = classes;
|
||
plane.tr.className = classes;
|
||
}
|
||
}
|
||
//console.timeEnd("updateCells");
|
||
|
||
/*
|
||
if (show_squawk_warning_cache != show_squawk_warning && show_squawk_warning ) {
|
||
$("#SpecialSquawkWarning").css('display','block');
|
||
show_squawk_warning_cache = show_squawk_warning;
|
||
}
|
||
if (show_squawk_warning_cache != show_squawk_warning && !show_squawk_warning ) {
|
||
$("#SpecialSquawkWarning").css('display','none');
|
||
show_squawk_warning_cache = show_squawk_warning;
|
||
}
|
||
*/
|
||
|
||
if (!globeIndex)
|
||
$('#dump1090_total_ac').text(TrackedAircraft);
|
||
else
|
||
$('#dump1090_total_ac').text(globeTrackedAircraft);
|
||
$('#dump1090_total_ac_positions').text(TrackedAircraftPositions);
|
||
|
||
|
||
//console.time("DOM");
|
||
//tableinfoFragment = document.createDocumentFragment();
|
||
let tbody = document.getElementById('tableinfo').tBodies[0];
|
||
for (let i = 0; i < PlanesOrdered.length; ++i) {
|
||
const plane = PlanesOrdered[i];
|
||
if (plane.inTable) {
|
||
tbody.removeChild(plane.tr);
|
||
plane.inTable = false;
|
||
}
|
||
if (plane.showInTable) {
|
||
tbody.appendChild(plane.tr);
|
||
plane.inTable = true;
|
||
}
|
||
}
|
||
//tbody.appendChild(tableinfoFragment);
|
||
//console.timeEnd("DOM");
|
||
//console.log(tableinfo);
|
||
}
|
||
|
||
//
|
||
// ---- table sorting ----
|
||
//
|
||
|
||
function compareAlpha(xa,ya) {
|
||
if (xa === ya)
|
||
return 0;
|
||
if (xa < ya)
|
||
return -1;
|
||
return 1;
|
||
}
|
||
function compareBeta(xa,ya) {
|
||
if (xa === ya)
|
||
return 0;
|
||
if (sortAscending && xa < ya)
|
||
return -1;
|
||
if (!sortAscending && (xa.replace(/ /g, "").split("").reverse().join("") > ya.replace(/ /g, "").split("").reverse().join("")))
|
||
return -1;
|
||
return 1;
|
||
}
|
||
|
||
function compareNumeric(xf,yf) {
|
||
if (Math.abs(xf - yf) < 1e-9)
|
||
return 0;
|
||
|
||
return xf - yf;
|
||
}
|
||
|
||
function sortByICAO() { sortBy('icao', compareAlpha, function(x) { return x.icao; }); }
|
||
function sortByFlight() { sortBy('flight', compareBeta, function(x) { return x.flight ? x.flight : x.registration; }); }
|
||
function sortByRegistration() { sortBy('registration', compareAlpha, function(x) { return x.registration; }); }
|
||
function sortByAircraftType() { sortBy('icaoType', compareAlpha, function(x) { return x.icaoType; }); }
|
||
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.gs; }); }
|
||
function sortByVerticalRate() { sortBy('vert_rate', compareNumeric, function(x) { return x.vert_rate; }); }
|
||
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; }); }
|
||
function sortByCountry() { sortBy('country', compareAlpha, function(x) { return x.icaorange.country; }); }
|
||
function sortByRssi() { sortBy('rssi', compareNumeric, function(x) { return x.rssi; }); }
|
||
function sortByLatitude() { sortBy('lat', compareNumeric, function(x) { return (x.position !== null ? x.position[1] : null); }); }
|
||
function sortByLongitude() { sortBy('lon', compareNumeric, function(x) { return (x.position !== null ? x.position[0] : null); }); }
|
||
function sortByDataSource() { sortBy('data_source', compareNumeric, function(x) { return x.getDataSourceNumber() } ); }
|
||
function sortByBaseMarkerKey() { sortBy('base_marker_key', compareAlpha, function(x) { return x.baseMarkerKey; }); }
|
||
|
||
let sortId = '';
|
||
let sortCompare = null;
|
||
let sortExtract = null;
|
||
let sortAscending = true;
|
||
|
||
function sortFunction(x,y) {
|
||
let xv = x._sort_value;
|
||
let yv = y._sort_value;
|
||
|
||
// 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;
|
||
|
||
let c = sortAscending ? sortCompare(xv,yv) : sortCompare(yv,xv);
|
||
if (c !== 0) return c;
|
||
|
||
return x._sort_pos - y._sort_pos;
|
||
}
|
||
|
||
function resortTable(pList) {
|
||
if (!sortExtract)
|
||
return;
|
||
// presort by dataSource
|
||
if (sortId == "sitedist") {
|
||
for (let i = 0; i < pList.length; ++i) {
|
||
pList[i]._sort_pos = i;
|
||
}
|
||
pList.sort(function(x,y) {
|
||
const a = x.getDataSourceNumber();
|
||
const b = y.getDataSourceNumber();
|
||
if (a == b)
|
||
return (x._sort_pos - y._sort_pos);
|
||
|
||
return (a-b);
|
||
});
|
||
}
|
||
// or distance
|
||
else if (sortId == "data_source") {
|
||
pList.sort(function(x,y) {
|
||
return (x.sitedist - y.sitedist);
|
||
});
|
||
}
|
||
// or longitude
|
||
else {
|
||
pList.sort(function(x,y) {
|
||
const xlon = x.position ? x.position[0] : 500;
|
||
const ylon = y.position ? y.position[0] : 500;
|
||
return (xlon - ylon);
|
||
});
|
||
}
|
||
// 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 (let i = 0; i < pList.length; ++i) {
|
||
pList[i]._sort_pos = i;
|
||
pList[i]._sort_value = sortExtract(pList[i]);
|
||
}
|
||
|
||
pList.sort(sortFunction);
|
||
// Put selected planes on top, do a stable sort!
|
||
// actually that's a bad idea, disable this for now
|
||
if (!SelectedAllPlanes && multiSelect) {
|
||
for (let i = 0; i < pList.length; ++i) {
|
||
pList[i]._sort_pos = i;
|
||
}
|
||
pList.sort(function(x,y) {
|
||
if (x.selected && y.selected) {
|
||
return (x._sort_pos - y._sort_pos);
|
||
}
|
||
if (x.selected)
|
||
return -1;
|
||
if (y.selected)
|
||
return 1;
|
||
|
||
return (x._sort_pos - y._sort_pos);
|
||
});
|
||
}
|
||
|
||
}
|
||
|
||
function sortBy(id,sc,se) {
|
||
if (id != 'data_source' && grouptype_checkbox) {
|
||
$('#grouptype_checkbox').removeClass('settingsCheckboxChecked');
|
||
grouptype_checkbox = false;
|
||
} else if (id == 'data_source' && !grouptype_checkbox) {
|
||
$('#grouptype_checkbox').addClass('settingsCheckboxChecked');
|
||
grouptype_checkbox = true;
|
||
}
|
||
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;
|
||
|
||
refreshTableInfo();
|
||
//resortTable(PlanesTableList);
|
||
}
|
||
|
||
function selectPlaneByHex(hex, options) {
|
||
//console.trace();
|
||
active();
|
||
//console.log("SELECTING", hex, options);
|
||
options = options || {};
|
||
//console.log("select: " + hex);
|
||
// If SelectedPlane has something in it, clear out the selected
|
||
if (SelectedAllPlanes) {
|
||
deselectAllPlanes();
|
||
}
|
||
// already selected plane
|
||
let oldPlane = SelectedPlane;
|
||
// plane to be selected
|
||
let newPlane = Planes[hex];
|
||
|
||
if (newPlane && (showTrace || showTraceExit))
|
||
SelectedPlane = oldPlane = null;
|
||
|
||
// multiSelect deselect
|
||
if (multiSelect && newPlane && newPlane.selected && !options.follow && !onlySelected) {
|
||
newPlane.selected = false;
|
||
newPlane.clearLines();
|
||
newPlane.updateMarker();
|
||
$(newPlane.tr).removeClass("selected");
|
||
if (SelectedPlane == newPlane)
|
||
SelectedPlane = null;
|
||
newPlane = null;
|
||
hex = null;
|
||
}
|
||
// If we are clicking the same plane, we are deselecting it.
|
||
// (unless it was a doubleclick..)
|
||
if (oldPlane == newPlane) {
|
||
if (options.follow || options.noDeselect) {
|
||
oldPlane = null;
|
||
} else {
|
||
newPlane = null;
|
||
hex = null;
|
||
}
|
||
}
|
||
if (!multiSelect && oldPlane) {
|
||
oldPlane.selected = false;
|
||
oldPlane.clearLines();
|
||
oldPlane.updateMarker();
|
||
$(oldPlane.tr).removeClass("selected");
|
||
SelectedPlane = null;
|
||
// scroll the infoblock back to the top for the next plane to be selected
|
||
//$('.infoblock-container').scrollTop(0);
|
||
}
|
||
|
||
if (!options.noFetch && globeIndex && hex)
|
||
newPlane = getTrace(newPlane, hex, options);
|
||
|
||
if (newPlane) {
|
||
// Assign the new selected
|
||
SelectedPlane = newPlane;
|
||
newPlane.selected = true;
|
||
newPlane.updateTick(true);
|
||
$(newPlane.tr).addClass("selected");
|
||
newPlane.logSel(newPlane.history_size);
|
||
//console.log(newPlane.baseMarkerKey);
|
||
}
|
||
|
||
if (newPlane && options.follow) {
|
||
toggleFollow(true);
|
||
if (!options.zoom)
|
||
options.zoom = 'follow';
|
||
} else {
|
||
toggleFollow(false);
|
||
}
|
||
if (newPlane && newPlane.position) {
|
||
newPlane.updateLines();
|
||
newPlane.updateMarker(true);
|
||
}
|
||
|
||
if (options.zoom == 'follow') {
|
||
//if (OLMap.getView().getZoom() < 8)
|
||
// OLMap.getView().setZoom(8);
|
||
} else if (options.zoom) {
|
||
OLMap.getView().setZoom(options.zoom);
|
||
}
|
||
|
||
updateAddressBar();
|
||
refreshSelected();
|
||
refreshTableInfo();
|
||
}
|
||
|
||
|
||
// loop through the planes and mark them as selected to show the paths for all planes
|
||
function selectAllPlanes() {
|
||
HighlightedPlane = null;
|
||
// if all planes are already selected, deselect them all
|
||
if (SelectedAllPlanes) {
|
||
deselectAllPlanes();
|
||
return;
|
||
}
|
||
buttonActive('#T', true);
|
||
// If SelectedPlane has something in it, clear out the selected
|
||
if (SelectedPlane != null) {
|
||
SelectedPlane.selected = false;
|
||
SelectedPlane.clearLines();
|
||
SelectedPlane.updateMarker();
|
||
$(SelectedPlane.tr).removeClass("selected");
|
||
}
|
||
|
||
SelectedPlane = null;
|
||
SelectedAllPlanes = true;
|
||
|
||
refreshFeatures();
|
||
|
||
$('#selectall_checkbox').addClass('settingsCheckboxChecked');
|
||
|
||
refreshSelected();
|
||
refreshHighlighted();
|
||
refreshTableInfo();
|
||
}
|
||
|
||
|
||
// deselect all the planes
|
||
function deselectAllPlanes(keepMain) {
|
||
if (showTrace)
|
||
return;
|
||
if (!multiSelect && SelectedPlane)
|
||
toggleIsolation(false, "off");
|
||
buttonActive('#T', false);
|
||
for(let key in Planes) {
|
||
if (keepMain && Planes[key] == SelectedPlane)
|
||
continue;
|
||
Planes[key].selected = false;
|
||
$(Planes[key].tr).removeClass("selected");
|
||
}
|
||
$('#selectall_checkbox').removeClass('settingsCheckboxChecked');
|
||
SelectedAllPlanes = false;
|
||
if (!keepMain)
|
||
SelectedPlane = null;
|
||
refreshFeatures();
|
||
refreshHighlighted();
|
||
refreshTableInfo();
|
||
|
||
updateAddressBar();
|
||
refreshSelected();
|
||
}
|
||
|
||
function toggleFollow(override) {
|
||
if (override == true)
|
||
FollowSelected = true;
|
||
else if (override == false)
|
||
FollowSelected = false;
|
||
else
|
||
FollowSelected = !FollowSelected;
|
||
|
||
traceOpts.follow = FollowSelected;
|
||
|
||
if (FollowSelected) {
|
||
if (!SelectedPlane || !SelectedPlane.position)
|
||
FollowSelected = false;
|
||
}
|
||
if (FollowSelected) {
|
||
//if (override == undefined && OLMap.getView().getZoom() < 8)
|
||
// OLMap.getView().setZoom(8);
|
||
OLMap.getView().setCenter(ol.proj.fromLonLat(SelectedPlane.position));
|
||
}
|
||
buttonActive('#F', FollowSelected);
|
||
}
|
||
|
||
function resetMap() {
|
||
|
||
// Reset localStorage values and map settings
|
||
localStorage['CenterLat'] = CenterLat = DefaultCenterLat;
|
||
localStorage['CenterLon'] = CenterLon = DefaultCenterLon;
|
||
localStorage['ZoomLvl'] = ZoomLvl = DefaultZoomLvl;
|
||
|
||
// Set and refresh
|
||
OLMap.getView().setZoom(ZoomLvl);
|
||
OLMap.getView().setCenter(ol.proj.fromLonLat([CenterLon, CenterLat]));
|
||
OLMap.getView().setRotation(mapOrientation);
|
||
|
||
//selectPlaneByHex(null,false);
|
||
$("#update_error").css('display','none');
|
||
}
|
||
|
||
function updateMapSize() {
|
||
OLMap.updateSize();
|
||
}
|
||
|
||
function toggleSidebarVisibility(e) {
|
||
if (e)
|
||
e.preventDefault();
|
||
$("#sidebar_container").toggle();
|
||
$("#expand_sidebar_control").toggle();
|
||
$("#toggle_sidebar_button").toggleClass("show_sidebar");
|
||
$("#toggle_sidebar_button").toggleClass("hide_sidebar");
|
||
localStorage['sidebar_visible'] = sidebarVisible = $("#sidebar_container").is(":visible");
|
||
updateMapSize();
|
||
}
|
||
|
||
function expandSidebar(e) {
|
||
e.preventDefault();
|
||
$("#map_container").hide()
|
||
mapIsVisible = false;
|
||
$("#toggle_sidebar_control").hide();
|
||
$("#splitter").hide();
|
||
$("#shrink_sidebar_button").show();
|
||
$("#sidebar_container").width("100%");
|
||
setColumnVisibility();
|
||
clearTimeout(refreshId);
|
||
fetchData();
|
||
refreshTableInfo();
|
||
updateMapSize();
|
||
setSelectedInfoBlockVisibility();
|
||
}
|
||
|
||
function showMap() {
|
||
$('#sidebar_container').width(localStorage['sidebar_width']).css('margin-left', '0');
|
||
$("#map_container").show()
|
||
mapIsVisible = true;
|
||
$("#toggle_sidebar_control").show();
|
||
$("#splitter").show();
|
||
$("#shrink_sidebar_button").hide();
|
||
setColumnVisibility();
|
||
clearTimeout(refreshId);
|
||
fetchData();
|
||
refreshTableInfo();
|
||
updateMapSize();
|
||
}
|
||
|
||
function showColumn(table, columnId, visible) {
|
||
let index = $(columnId).index();
|
||
columnVis[index] = visible;
|
||
if (index >= 0) {
|
||
let cells = $(table).find("td:nth-child(" + (index + 1).toString() + ")");
|
||
if (visible) {
|
||
cells.show();
|
||
} else {
|
||
cells.hide();
|
||
}
|
||
}
|
||
}
|
||
|
||
function setColumnVisibility() {
|
||
let infoTable = $("#tableinfo");
|
||
|
||
let tbody = document.getElementById('tableinfo').tBodies[0];
|
||
for (let i = 0; i < PlanesOrdered.length; ++i) {
|
||
let plane = PlanesOrdered[i];
|
||
if (plane.tr) {
|
||
if (plane.inTable) {
|
||
tbody.removeChild(plane.tr);
|
||
}
|
||
tbody.appendChild(plane.tr);
|
||
plane.inTable = true;
|
||
}
|
||
}
|
||
|
||
for (let col in HideCols) {
|
||
showColumn(infoTable, HideCols[col], !mapIsVisible);
|
||
}
|
||
}
|
||
|
||
function setSelectedInfoBlockVisibility() {
|
||
|
||
if (SelectedPlane) {
|
||
$('#selected_infoblock').show();
|
||
if (!mapIsVisible)
|
||
$("#sidebar_container").css('margin-left', '140pt');
|
||
//$('#sidebar_canvas').css('margin-bottom', $('#selected_infoblock').height() + 'px');
|
||
//
|
||
$('#large_mode_control').css('left', (190 * globalScale) + 'px');
|
||
$('.ol-scale-line').css('left', (180 * globalScale + 8) + 'px');
|
||
}
|
||
else {
|
||
$('#selected_infoblock').hide();
|
||
if (!mapIsVisible)
|
||
$("#sidebar_container").css('margin-left', '0');
|
||
//$('#sidebar_canvas').css('margin-bottom', 0);
|
||
|
||
$('#large_mode_control').css('left', (5 * globalScale) + 'px');
|
||
$('.ol-scale-line').css('left', '8px');
|
||
}
|
||
}
|
||
|
||
function initializeUnitsSelector() {
|
||
// Get display unit preferences from local storage
|
||
if (!localStorage.getItem('displayUnits')) {
|
||
localStorage['displayUnits'] = "nautical";
|
||
}
|
||
let displayUnits = localStorage['displayUnits'];
|
||
DisplayUnits = displayUnits;
|
||
|
||
setAltitudeLegend(displayUnits);
|
||
|
||
// Initialize drop-down
|
||
let unitsSelector = $("#units_selector");
|
||
unitsSelector.val(displayUnits);
|
||
unitsSelector.on("change", onDisplayUnitsChanged);
|
||
|
||
$(".altitudeUnit").text(get_unit_label("altitude", DisplayUnits));
|
||
$(".speedUnit").text(get_unit_label("speed", DisplayUnits));
|
||
$(".distanceUnit").text(get_unit_label("distance", DisplayUnits));
|
||
$(".verticalRateUnit").text(get_unit_label("verticalRate", DisplayUnits));
|
||
}
|
||
|
||
function onDisplayUnitsChanged(e) {
|
||
let displayUnits = e.target.value;
|
||
// Save display units to local storage
|
||
localStorage['displayUnits'] = displayUnits;
|
||
DisplayUnits = displayUnits;
|
||
|
||
setAltitudeLegend(displayUnits);
|
||
|
||
// Update filters
|
||
updateAltFilter();
|
||
|
||
// Refresh data
|
||
refreshFilter();
|
||
|
||
// Redraw range rings
|
||
if (SitePosition != null && SiteCircles) {
|
||
createSiteCircleFeatures();
|
||
}
|
||
|
||
// Reset map scale line units
|
||
OLMap.getControls().forEach(function(control) {
|
||
if (control instanceof ol.control.ScaleLine) {
|
||
control.setUnits(displayUnits);
|
||
}
|
||
});
|
||
|
||
$(".altitudeUnit").text(get_unit_label("altitude", DisplayUnits));
|
||
$(".speedUnit").text(get_unit_label("speed", DisplayUnits));
|
||
$(".distanceUnit").text(get_unit_label("distance", DisplayUnits));
|
||
$(".verticalRateUnit").text(get_unit_label("verticalRate", DisplayUnits));
|
||
}
|
||
|
||
function setAltitudeLegend(units) {
|
||
if (units === 'metric') {
|
||
$('#altitude_chart_button').addClass('altitudeMeters');
|
||
} else {
|
||
$('#altitude_chart_button').removeClass('altitudeMeters');
|
||
}
|
||
}
|
||
|
||
function onFilterByAltitude(e) {
|
||
e.preventDefault();
|
||
$("#altitude_filter_min").blur();
|
||
$("#altitude_filter_max").blur();
|
||
|
||
|
||
if (SelectedPlane && SelectedPlane.isFiltered()) {
|
||
SelectedPlane.selected = false;
|
||
SelectedPlane.clearLines();
|
||
SelectedPlane = null;
|
||
}
|
||
|
||
updateAltFilter();
|
||
refreshFilter();
|
||
}
|
||
|
||
function refreshFilter() {
|
||
|
||
if (filterTracks)
|
||
remakeTrails();
|
||
|
||
refreshFeatures();
|
||
|
||
refreshSelected();
|
||
refreshHighlighted();
|
||
refreshTableInfo();
|
||
|
||
drawHeatmap();
|
||
}
|
||
|
||
function filterGroundVehicles(switchFilter) {
|
||
if (typeof localStorage['groundVehicleFilter'] === 'undefined') {
|
||
localStorage['groundVehicleFilter'] = 'not_filtered';
|
||
}
|
||
let groundFilter = localStorage['groundVehicleFilter'];
|
||
if (switchFilter === true) {
|
||
groundFilter = (groundFilter === 'not_filtered') ? 'filtered' : 'not_filtered';
|
||
}
|
||
if (groundFilter === 'not_filtered') {
|
||
$('#groundvehicle_filter').addClass('settingsCheckboxChecked');
|
||
} else {
|
||
$('#groundvehicle_filter').removeClass('settingsCheckboxChecked');
|
||
}
|
||
localStorage['groundVehicleFilter'] = groundFilter;
|
||
PlaneFilter.groundVehicles = groundFilter;
|
||
}
|
||
|
||
function filterBlockedMLAT(switchFilter) {
|
||
if (typeof localStorage['blockedMLATFilter'] === 'undefined') {
|
||
localStorage['blockedMLATFilter'] = 'not_filtered';
|
||
}
|
||
let blockedMLATFilter = localStorage['blockedMLATFilter'];
|
||
if (switchFilter === true) {
|
||
blockedMLATFilter = (blockedMLATFilter === 'not_filtered') ? 'filtered' : 'not_filtered';
|
||
}
|
||
if (blockedMLATFilter === 'not_filtered') {
|
||
$('#blockedmlat_filter').addClass('settingsCheckboxChecked');
|
||
} else {
|
||
$('#blockedmlat_filter').removeClass('settingsCheckboxChecked');
|
||
}
|
||
localStorage['blockedMLATFilter'] = blockedMLATFilter;
|
||
PlaneFilter.blockedMLAT = blockedMLATFilter;
|
||
}
|
||
|
||
function buttonActive(id, state) {
|
||
if (state) {
|
||
$(id).addClass('activeButton');
|
||
$(id).removeClass('inActiveButton');
|
||
} else {
|
||
$(id).addClass('inActiveButton');
|
||
$(id).removeClass('activeButton');
|
||
}
|
||
}
|
||
|
||
function toggleIsolation(on, off) {
|
||
if (showTrace && !on && !off)
|
||
return;
|
||
onlySelected = !onlySelected;
|
||
if (on)
|
||
onlySelected = true;
|
||
if (off)
|
||
onlySelected = false;
|
||
|
||
buttonActive('#I', onlySelected);
|
||
|
||
refreshFilter();
|
||
}
|
||
|
||
function toggleMilitary() {
|
||
onlyMilitary = !onlyMilitary;
|
||
buttonActive('#U', onlyMilitary);
|
||
|
||
refreshFilter();
|
||
}
|
||
|
||
function togglePersistence() {
|
||
noVanish = !noVanish;
|
||
filterTracks = noVanish;
|
||
|
||
buttonActive('#P', noVanish);
|
||
|
||
remakeTrails();
|
||
|
||
if (!noVanish)
|
||
reaper();
|
||
localStorage['noVanish'] = noVanish;
|
||
console.log('noVanish = ' + noVanish);
|
||
|
||
refreshFilter();
|
||
}
|
||
|
||
function toggleLastLeg() {
|
||
if (!globeIndex)
|
||
return;
|
||
if (lastLeg) {
|
||
lastLeg = false;
|
||
localStorage['lastLeg'] = "false";
|
||
$('#lastLeg_checkbox').removeClass('settingsCheckboxChecked');
|
||
} else {
|
||
lastLeg = true;
|
||
localStorage['lastLeg'] = "true";
|
||
$('#lastLeg_checkbox').addClass('settingsCheckboxChecked');
|
||
}
|
||
if (SelectedPlane && !showTrace)
|
||
SelectedPlane.processTrace();
|
||
}
|
||
|
||
function toggleDebugAll() {
|
||
if (localStorage['debugAll'] === "true") {
|
||
debugAll = false;
|
||
localStorage['debugAll'] = "false";
|
||
$('#debugAll_checkbox').removeClass('settingsCheckboxChecked');
|
||
} else {
|
||
debugAll = true;
|
||
localStorage['debugAll'] = "true";
|
||
$('#debugAll_checkbox').addClass('settingsCheckboxChecked');
|
||
}
|
||
}
|
||
|
||
function toggleDebugTracks() {
|
||
if (localStorage['debugTracks'] === "true") {
|
||
debugTracks = false;
|
||
localStorage['debugTracks'] = "false";
|
||
$('#debug_checkbox').removeClass('settingsCheckboxChecked');
|
||
} else {
|
||
debugTracks = true;
|
||
localStorage['debugTracks'] = "true";
|
||
$('#debug_checkbox').addClass('settingsCheckboxChecked');
|
||
}
|
||
|
||
remakeTrails();
|
||
}
|
||
|
||
function dim(evt) {
|
||
if (!globalCompositeTested) {
|
||
globalCompositeTested = true;
|
||
evt.context.globalCompositeOperation = 'multiply';
|
||
if (evt.context.globalCompositeOperation != 'multiply')
|
||
globalCompositeTested = false;
|
||
evt.context.globalCompositeOperation = 'overlay';
|
||
if (evt.context.globalCompositeOperation != 'overlay')
|
||
globalCompositeTested = false;
|
||
}
|
||
const dim = mapDimPercentage;
|
||
const contrast = mapContrastPercentage;
|
||
if (dim > 0.0001) {
|
||
evt.context.globalCompositeOperation = 'multiply';
|
||
evt.context.fillStyle = 'rgba(0,0,0,'+dim+')';
|
||
evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
|
||
} else if (dim < -0.0001) {
|
||
evt.context.globalCompositeOperation = 'screen';
|
||
console.log(evt.context.globalCompositeOperation);
|
||
evt.context.fillStyle = 'rgba(255, 255, 255,'+(-dim)+')';
|
||
evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
|
||
}
|
||
if (contrast > 0.0001) {
|
||
evt.context.globalCompositeOperation = 'overlay';
|
||
evt.context.fillStyle = 'rgba(0,0,0,'+contrast+')';
|
||
evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
|
||
} else if (contrast < -0.0001) {
|
||
evt.context.globalCompositeOperation = 'overlay';
|
||
evt.context.fillStyle = 'rgba(255, 255, 255,'+ (-contrast)+')';
|
||
evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
|
||
}
|
||
evt.context.globalCompositeOperation = 'source-over';
|
||
}
|
||
|
||
function toggleMapDim(switchOn) {
|
||
if (!switchOn && localStorage['MapDim'] === "true") {
|
||
localStorage['MapDim'] = "false";
|
||
MapDim = false;
|
||
|
||
ol.control.LayerSwitcher.forEachRecursive(layers_group, function(lyr) {
|
||
if (lyr.get('type') != 'base')
|
||
return;
|
||
ol.Observable.unByKey(lyr.dimKey);
|
||
});
|
||
|
||
$('#mapdim_checkbox').removeClass('settingsCheckboxChecked');
|
||
|
||
/*
|
||
$('html').css('background-color', '#F8F8F8');
|
||
$('body').css('background-color', '#F8F8F8');
|
||
$('#selected_infoblock').css('background-color', '#F8F8F8');
|
||
$('#highlighted_infoblock').css('background-color', '#F8F8F8');
|
||
$('.altitudeFilterInput').css('background-color', '#F8F8F8');
|
||
*/
|
||
} else {
|
||
localStorage['MapDim'] = "true";
|
||
MapDim = true;
|
||
|
||
ol.control.LayerSwitcher.forEachRecursive(layers_group, function(lyr) {
|
||
if (lyr.get('type') != 'base')
|
||
return;
|
||
lyr.dimKey = lyr.on('postrender', dim);
|
||
});
|
||
|
||
$('#mapdim_checkbox').addClass('settingsCheckboxChecked');
|
||
|
||
}
|
||
OLMap.render();
|
||
buttonActive('#B', localStorage['MapDim'] == "true");
|
||
}
|
||
|
||
function toggleAltitudeChart(switchToggle) {
|
||
if (localStorage['altitudeChart']) {
|
||
altitudeChartDisplay = localStorage['altitudeChart'];
|
||
} else if (onMobile) {
|
||
altitudeChartDisplay = 'hidden';
|
||
} else {
|
||
altitudeChartDisplay = 'show'
|
||
}
|
||
if (switchToggle === true) {
|
||
altitudeChartDisplay = (altitudeChartDisplay === 'show') ? 'hidden' : 'show';
|
||
localStorage['altitudeChart'] = altitudeChartDisplay;
|
||
}
|
||
// if you're using custom colors always hide the chart
|
||
if (customAltitudeColors === true) {
|
||
altitudeChartDisplay = 'hidden';
|
||
// also hide the control option
|
||
$('#altitude_chart_container').hide();
|
||
}
|
||
if (altitudeChartDisplay === 'show') {
|
||
$('#altitude_checkbox').addClass('settingsCheckboxChecked');
|
||
$('#altitude_chart').show();
|
||
} else {
|
||
$('#altitude_checkbox').removeClass('settingsCheckboxChecked');
|
||
$('#altitude_chart').hide();
|
||
}
|
||
}
|
||
|
||
function followRandomPlane() {
|
||
if (showTrace)
|
||
return;
|
||
let this_one = null;
|
||
let tired = 0;
|
||
do {
|
||
this_one = PlanesOrdered[Math.floor(Math.random()*PlanesOrdered.length)];
|
||
if (!this_one || tired++ > 1000)
|
||
break;
|
||
} while (this_one.isFiltered() || !this_one.position || (now - this_one.position_time > 30));
|
||
//console.log(this_one.icao);
|
||
if (this_one)
|
||
selectPlaneByHex(this_one.icao, {follow: true});
|
||
}
|
||
|
||
function toggleTableInView(switchOn) {
|
||
if (switchOn || (globeIndex && !icaoFilter)) {
|
||
tableInView = true;
|
||
} else {
|
||
tableInView = !tableInView;
|
||
refreshTableInfo();
|
||
}
|
||
localStorage['tableInView'] = tableInView;
|
||
if (tableInView) {
|
||
$('#with_positions').text("On Screen:");
|
||
} else {
|
||
$('#with_positions').text("With Position:");
|
||
}
|
||
buttonActive('#V', tableInView);
|
||
}
|
||
|
||
function toggleLabels(switchOn) {
|
||
enableLabels = !enableLabels;
|
||
localStorage['enableLabels'] = enableLabels;
|
||
for (let key in PlanesOrdered) {
|
||
PlanesOrdered[key].updateMarker(false);
|
||
}
|
||
buttonActive('#L', enableLabels);
|
||
|
||
if (showTrace)
|
||
remakeTrails();
|
||
}
|
||
function toggleExtendedLabels() {
|
||
if (isNaN(extendedLabels))
|
||
extendedLabels = 0;
|
||
|
||
extendedLabels++;
|
||
extendedLabels %= 3;
|
||
console.log(extendedLabels);
|
||
localStorage['extendedLabels'] = extendedLabels;
|
||
for (let key in PlanesOrdered) {
|
||
PlanesOrdered[key].updateMarker(false);
|
||
}
|
||
buttonActive('#O', extendedLabels);
|
||
}
|
||
|
||
function toggleTrackLabels() {
|
||
trackLabels = !trackLabels;
|
||
localStorage['trackLabels'] = trackLabels;
|
||
|
||
remakeTrails();
|
||
|
||
buttonActive('#K', trackLabels);
|
||
}
|
||
|
||
function toggleMultiSelect(newState) {
|
||
multiSelect = !multiSelect;
|
||
|
||
if (newState == "on")
|
||
multiSelect = true;
|
||
if (newState == "off")
|
||
multiSelect = false;
|
||
|
||
if (!multiSelect) {
|
||
if (!SelectedPlane)
|
||
toggleIsolation(false, "off");
|
||
deselectAllPlanes("keepMain");
|
||
}
|
||
|
||
buttonActive('#M', multiSelect);
|
||
}
|
||
|
||
function onJump(e) {
|
||
toggleFollow(false);
|
||
if (e) {
|
||
e.preventDefault();
|
||
airport = $("#jump_input").val().trim().toUpperCase();
|
||
$("#jump_input").val("");
|
||
$("#jump_input").blur();
|
||
}
|
||
if (!_airport_coords_cache) {
|
||
$.getJSON(databaseFolder + "/airport-coords.js")
|
||
.done(function(data) {
|
||
_airport_coords_cache = data;
|
||
onJump();
|
||
});
|
||
} else {
|
||
const coords = _airport_coords_cache[airport];
|
||
if (coords) {
|
||
OLMap.getView().setCenter(ol.proj.fromLonLat([coords[1], coords[0]]));
|
||
|
||
if (ZoomLvl >= 7)
|
||
fetchData();
|
||
|
||
refreshFilter();
|
||
}
|
||
}
|
||
}
|
||
|
||
function onSearch(e) {
|
||
e.preventDefault();
|
||
const searchTerm = $("#search_input").val().trim();
|
||
$("#search_input").val("");
|
||
$("#search_input").blur();
|
||
if (searchTerm)
|
||
findPlanes(searchTerm, "byIcao", "byCallsign", "byReg", "byType");
|
||
return false;
|
||
}
|
||
/*
|
||
function onSearchReg(e) {
|
||
e.preventDefault();
|
||
const searchTerm = $("#search_reg_input").val().trim();
|
||
$("#search_reg_input").val("");
|
||
$("#search_reg_input").blur();
|
||
if (searchTerm)
|
||
findPlanes(searchTerm, false, false, "byReg", false);
|
||
return false;
|
||
}
|
||
*/
|
||
|
||
function onResetCallsignFilter(e) {
|
||
$("#callsign_filter").val("");
|
||
$("#callsign_filter").blur();
|
||
|
||
updateCallsignFilter();
|
||
}
|
||
|
||
function updateCallsignFilter(e) {
|
||
if (e)
|
||
e.preventDefault();
|
||
|
||
$("#callsign_filter").blur();
|
||
|
||
PlaneFilter.callsign = $("#callsign_filter").val().trim().toUpperCase();
|
||
|
||
refreshFilter();
|
||
}
|
||
|
||
|
||
function onResetTypeFilter(e) {
|
||
$("#type_filter").val("");
|
||
$("#type_filter").blur();
|
||
|
||
updateTypeFilter();
|
||
}
|
||
|
||
function updateTypeFilter(e) {
|
||
if (e)
|
||
e.preventDefault();
|
||
|
||
$("#type_filter").blur();
|
||
let type = $("#type_filter").val().trim();
|
||
|
||
PlaneFilter.type = type.toUpperCase();
|
||
|
||
refreshFilter();
|
||
}
|
||
function onResetIcaoFilter(e) {
|
||
$("#icao_filter").val("");
|
||
$("#icao_filter").blur();
|
||
|
||
updateIcaoFilter();
|
||
}
|
||
function updateIcaoFilter(e) {
|
||
if (e)
|
||
e.preventDefault();
|
||
|
||
$("#icao_filter").blur();
|
||
let icao = $("#icao_filter").val().trim();
|
||
|
||
PlaneFilter.icao = icao.toLowerCase();
|
||
|
||
refreshFilter();
|
||
}
|
||
|
||
function onResetDescriptionFilter(e) {
|
||
$("#description_filter").val("");
|
||
$("#description_filter").blur();
|
||
|
||
updateTypeFilter();
|
||
}
|
||
|
||
function updateDescriptionFilter(e) {
|
||
if (e)
|
||
e.preventDefault();
|
||
|
||
$("#description_filter").blur();
|
||
let description = $("#description_filter").val().trim();
|
||
|
||
PlaneFilter.description = description.toUpperCase();
|
||
|
||
refreshFilter();
|
||
}
|
||
function onResetAltitudeFilter(e) {
|
||
$("#altitude_filter_min").val("");
|
||
$("#altitude_filter_max").val("");
|
||
$("#altitude_filter_min").blur();
|
||
$("#altitude_filter_max").blur();
|
||
|
||
updateAltFilter();
|
||
refreshFilter();
|
||
}
|
||
|
||
function updateAltFilter() {
|
||
let minAltitude = parseFloat($("#altitude_filter_min").val().trim());
|
||
let maxAltitude = parseFloat($("#altitude_filter_max").val().trim());
|
||
let enabled = false;
|
||
|
||
if (minAltitude < -1e6 || minAltitude > 1e6 || isNaN(minAltitude))
|
||
minAltitude = -1e6;
|
||
else
|
||
enabled = true;
|
||
if (maxAltitude < -1e6 || maxAltitude > 1e6 || isNaN(maxAltitude))
|
||
maxAltitude = 1e6;
|
||
else
|
||
enabled = true;
|
||
|
||
PlaneFilter.enabled = enabled;
|
||
|
||
if (DisplayUnits == "metric") {
|
||
PlaneFilter.minAltitude = minAltitude * 3.2808;
|
||
PlaneFilter.maxAltitude = maxAltitude * 3.2808;
|
||
} else {
|
||
PlaneFilter.minAltitude = minAltitude;
|
||
PlaneFilter.maxAltitude = maxAltitude;
|
||
}
|
||
|
||
}
|
||
|
||
function getFlightAwareIdentLink(ident, linkText) {
|
||
if (ident !== null && ident !== "") {
|
||
if (!linkText) {
|
||
linkText = ident;
|
||
}
|
||
return "<a target=\"_blank\" href=\"https://flightaware.com/live/flight/" + ident.trim() + "\" rel=\"noreferrer\">" + linkText + "</a>";
|
||
}
|
||
|
||
return "";
|
||
}
|
||
|
||
function getFlightAwareModeSLink(code, ident, linkText) {
|
||
if (code !== null && code.length > 0 && code[0] !== '~' && code !== "000000") {
|
||
if (!linkText) {
|
||
linkText = "FlightAware: " + code.toUpperCase();
|
||
}
|
||
|
||
let linkHtml = "<a target=\"_blank\" href=\"https://flightaware.com/live/modes/" + code ;
|
||
if (ident != null && ident !== "") {
|
||
linkHtml += "/ident/" + ident.trim();
|
||
}
|
||
linkHtml += "/redirect\" rel=\"noreferrer\">" + linkText + "</a>";
|
||
return linkHtml;
|
||
}
|
||
|
||
return "";
|
||
}
|
||
|
||
function getPhotoLink(ac) {
|
||
if (flightawareLinks) {
|
||
if (ac.registration == null || ac.registration == "")
|
||
return "";
|
||
return "<a target=\"_blank\" href=\"https://flightaware.com/photos/aircraft/" + ac.registration.replace(/[^0-9a-z]/ig,'') + "\" rel=\"noreferrer\">See Photos</a>";
|
||
} else {
|
||
return "<a target=\"_blank\" href=\"https://www.planespotters.net/hex/" + ac.icao.toUpperCase() + "\" rel=\"noreferrer\">View on Planespotters</a>";
|
||
}
|
||
}
|
||
|
||
// takes in an elemnt jQuery path and the OL3 layer name and toggles the visibility based on clicking it
|
||
function toggleLayer(element, layer) {
|
||
// set initial checked status
|
||
ol.control.LayerSwitcher.forEachRecursive(layers_group, function(lyr) {
|
||
if (lyr.get('name') === layer && lyr.getVisible()) {
|
||
$(element).addClass('settingsCheckboxChecked');
|
||
}
|
||
});
|
||
$(element).on('click', function() {
|
||
let visible = false;
|
||
if ($(element).hasClass('settingsCheckboxChecked')) {
|
||
visible = true;
|
||
}
|
||
ol.control.LayerSwitcher.forEachRecursive(layers_group, function(lyr) {
|
||
if (lyr.get('name') === layer) {
|
||
if (visible) {
|
||
lyr.setVisible(false);
|
||
$(element).removeClass('settingsCheckboxChecked');
|
||
} else {
|
||
lyr.setVisible(true);
|
||
$(element).addClass('settingsCheckboxChecked');
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function fetchPfData() {
|
||
if (fetchingPf || pTracks)
|
||
return;
|
||
fetchingPf = true;
|
||
for (let i in pf_data) {
|
||
const req = $.ajax({ url: pf_data[i],
|
||
dataType: 'json' });
|
||
$.when(req).done(function(data) {
|
||
for (let i in PlanesOrdered) {
|
||
const plane = PlanesOrdered[i];
|
||
const ac = data.aircraft[plane.icao.toUpperCase()];
|
||
if (!ac) {
|
||
continue;
|
||
}
|
||
plane.pfRoute = ac.route;
|
||
plane.pfMach = ac.mach;
|
||
plane.pfFlightno = ac.flightno;
|
||
if (!plane.registration && ac.reg && ac.reg != "????" && ac.reg != "z.NO-REG")
|
||
plane.registration = ac.reg;
|
||
if (!plane.icaoType && ac.type && ac.type != "????" && ac.type != "ZVEH") {
|
||
plane.icaoType = ac.type;
|
||
plane.setTypeData();
|
||
}
|
||
}
|
||
fetchingPf = false;
|
||
});
|
||
}
|
||
}
|
||
|
||
function solidGoldT() {
|
||
solidT = true;
|
||
let list = [[], [], [], []];
|
||
for (let i = 0; i < PlanesOrdered.length; i++) {
|
||
let plane = PlanesOrdered[i];
|
||
//console.log(plane);
|
||
if (plane.seen_pos && plane.seen_pos < 1200) {
|
||
plane.visible = true;
|
||
list[Math.floor(4*i/PlanesOrdered.length)].push(plane);
|
||
}
|
||
}
|
||
getTrace(null, null, {onlyFull: true, list: list[0],});
|
||
getTrace(null, null, {onlyFull: true, list: list[1],});
|
||
getTrace(null, null, {onlyFull: true, list: list[2],});
|
||
getTrace(null, null, {onlyFull: true, list: list[3],});
|
||
}
|
||
|
||
function bearingFromLonLat(position1, position2) {
|
||
// Positions in format [lon in deg, lat in deg]
|
||
const lon1 = position1[0]*Math.PI/180;
|
||
const lat1 = position1[1]*Math.PI/180;
|
||
const lon2 = position2[0]*Math.PI/180;
|
||
const lat2 = position2[1]*Math.PI/180;
|
||
|
||
const y = Math.sin(lon2-lon1)*Math.cos(lat2);
|
||
const x = Math.cos(lat1)*Math.sin(lat2)
|
||
- Math.sin(lat1)*Math.cos(lat2)*Math.cos(lon2-lon1);
|
||
return (Math.atan2(y, x)* 180 / Math.PI + 360) % 360;
|
||
}
|
||
function zoomIn() {
|
||
const zoom = OLMap.getView().getZoom();
|
||
OLMap.getView().setZoom((zoom+1).toFixed());
|
||
}
|
||
|
||
function zoomOut() {
|
||
const zoom = OLMap.getView().getZoom();
|
||
OLMap.getView().setZoom((zoom-1).toFixed());
|
||
}
|
||
|
||
function changeCenter(init) {
|
||
|
||
const rawCenter = OLMap.getView().getCenter();
|
||
const center = ol.proj.toLonLat(rawCenter);
|
||
|
||
localStorage['CenterLon'] = CenterLon = center[0];
|
||
localStorage['CenterLat'] = CenterLat = center[1];
|
||
|
||
if (!onlySelected)
|
||
refreshTableInfo();
|
||
|
||
if (!init && showTrace)
|
||
updateAddressBar();
|
||
|
||
if (rawCenter[0] < OLProj.extent_[0] || rawCenter[0] > OLProj.extent_[3]) {
|
||
OLMap.getView().setCenter(ol.proj.fromLonLat(center));
|
||
}
|
||
|
||
}
|
||
|
||
function checkMovement() {
|
||
const zoom = OLMap.getView().getZoom();
|
||
const center = ol.proj.toLonLat(OLMap.getView().getCenter());
|
||
|
||
if (
|
||
checkMoveZoom != zoom ||
|
||
checkMoveCenter[0] != center[0] ||
|
||
checkMoveCenter[1] != center[1]
|
||
) {
|
||
noMovement = 0;
|
||
}
|
||
|
||
if (noMovement == 0)
|
||
checkFollow();
|
||
|
||
checkMoveZoom = zoom;
|
||
checkMoveCenter[0] = center[0];
|
||
checkMoveCenter[1] = center[1];
|
||
|
||
if (noMovement++ != 3)
|
||
return;
|
||
|
||
// no zoom/pan inputs for 450 ms after a zoom/pan input
|
||
//
|
||
//console.time("fire!");
|
||
active();
|
||
changeZoom();
|
||
changeCenter();
|
||
|
||
drawHeatmap();
|
||
|
||
//console.timeEnd("fire!");
|
||
}
|
||
|
||
function changeZoom(init) {
|
||
if (!OLMap)
|
||
return;
|
||
|
||
ZoomLvl = OLMap.getView().getZoom();
|
||
|
||
// small zoomstep, no need to change aircraft scaling
|
||
if (!init && Math.abs(ZoomLvl-ZoomLvlCache) < 0.4)
|
||
return;
|
||
|
||
localStorage['ZoomLvl'] = ZoomLvl;
|
||
ZoomLvlCache = ZoomLvl;
|
||
|
||
if (ZoomLvl > markerZoomDivide)
|
||
scaleFactor = markerBig;
|
||
else
|
||
scaleFactor = markerSmall;
|
||
|
||
// scale markers according to global scaling
|
||
scaleFactor *= Math.pow(1.3, globalScale) * globalScale;
|
||
|
||
if (!onlySelected)
|
||
refreshTableInfo();
|
||
|
||
if (!init && showTrace)
|
||
updateAddressBar();
|
||
|
||
if (ZoomLvl > 5.5 && enableMouseover) {
|
||
OLMap.on('pointermove', onPointermove);
|
||
} else {
|
||
OLMap.un('pointermove', onPointermove);
|
||
removeHighlight();
|
||
}
|
||
}
|
||
|
||
function updateCell(plane, cell, newValue, html) {
|
||
if (columnVis[cell] && newValue != plane.trCache[cell]) {
|
||
plane.trCache[cell] = newValue;
|
||
if (html) {
|
||
plane.tr.cells[cell].innerHTML = newValue;
|
||
} else {
|
||
plane.tr.cells[cell].textContent = newValue;
|
||
}
|
||
}
|
||
}
|
||
|
||
function onPointermove(evt) {
|
||
//clearTimeout(pointerMoveTimeout);
|
||
//pointerMoveTimeout = setTimeout(highlight(evt), 100);
|
||
highlight(evt);
|
||
}
|
||
|
||
function highlight(evt) {
|
||
const hex = evt.map.forEachFeatureAtPixel(evt.pixel,
|
||
function(feature, layer) {
|
||
return feature.hex;
|
||
},
|
||
{
|
||
layerFilter: function(layer) {
|
||
return (layer == iconLayer);
|
||
},
|
||
hitTolerance:5,
|
||
}
|
||
);
|
||
|
||
clearTimeout(pointerMoveTimeout);
|
||
if (hex) {
|
||
HighlightedPlane = hex;
|
||
pointerMoveTimeout = setTimeout(refreshHighlighted(), 300);
|
||
} else {
|
||
HighlightedPlane = null;
|
||
pointerMoveTimeout = setTimeout(removeHighlight(), 300);
|
||
}
|
||
}
|
||
|
||
function processURLParams(){
|
||
try {
|
||
const search = new URLSearchParams(window.location.search);
|
||
|
||
let icaos = [];
|
||
let valid = [];
|
||
let icao = null;
|
||
if (search.has('icao')) {
|
||
icaos = search.get('icao').toLowerCase().split(',');
|
||
for (let i = 0; i < icaos.length; i++) {
|
||
icao = icaos[i].toLowerCase();
|
||
if (icao && (icao.length == 7 || icao.length == 6) && icao.toLowerCase().match(/[a-f,0-9]{6}/)) {
|
||
valid.push(icao);
|
||
if (i == 0)
|
||
icaoParam = icao;
|
||
}
|
||
}
|
||
}
|
||
|
||
icaos = valid;
|
||
|
||
traceDateString = search.get('showTrace');
|
||
const callsign = search.get('callsign');
|
||
let zoom = null;
|
||
let follow = true;
|
||
if (search.get("zoom")) {
|
||
try {
|
||
zoom = parseFloat(search.get("zoom"));
|
||
if (zoom === 0)
|
||
zoom = 8;
|
||
} catch (error) {
|
||
console.log("Error parsing zoom:", error);
|
||
}
|
||
}
|
||
|
||
if (search.get("lat") && search.get("lon")) {
|
||
try {
|
||
const lat = parseFloat(search.get("lat"));
|
||
const lon = parseFloat(search.get("lon"));
|
||
OLMap.getView().setCenter(ol.proj.fromLonLat([lon, lat]));
|
||
follow = false;
|
||
noPan = true;
|
||
}
|
||
catch (error) {
|
||
console.log("Error parsing lat/lon:", error);
|
||
}
|
||
}
|
||
|
||
if (icaos.length > 0) {
|
||
if (!search.has('noIsolation'))
|
||
toggleIsolation("on", false);
|
||
if (icaos.length > 1)
|
||
toggleMultiSelect("on");
|
||
for (let i = 0; i < icaos.length; i++) {
|
||
icao = icaos[i];
|
||
if (Planes[icao] || globeIndex) {
|
||
console.log('Selected ICAO id: '+ icao);
|
||
let selectOptions = {follow: follow};
|
||
if (traceDateString != null) {
|
||
toggleShowTrace();
|
||
if (!zoom)
|
||
zoom = 5;
|
||
} else {
|
||
if (!zoom)
|
||
zoom = 7;
|
||
selectPlaneByHex(icao, selectOptions)
|
||
}
|
||
} else {
|
||
console.log('ICAO id not found: ' + icao);
|
||
}
|
||
}
|
||
} else if (callsign != null) {
|
||
findPlanes(callsign, false, true, false, false);
|
||
}
|
||
|
||
if (zoom) {
|
||
OLMap.getView().setZoom(zoom);
|
||
}
|
||
|
||
if (search.has('mil'))
|
||
toggleMilitary();
|
||
|
||
if (search.has('airport')) {
|
||
airport = search.get('airport').trim().toUpperCase();
|
||
onJump();
|
||
}
|
||
|
||
if (search.has('leg')) {
|
||
legSel = parseInt(search.get('leg'), 10);
|
||
if (isNaN(legSel) || legSel < -1)
|
||
legSel = -1;
|
||
else
|
||
legSel--;
|
||
}
|
||
|
||
let tracks = search.get('monochromeTracks');
|
||
if (tracks != undefined) {
|
||
if (tracks.length == 6)
|
||
monochromeTracks = '#' + tracks;
|
||
else
|
||
monochromeTracks = "#000000";
|
||
}
|
||
|
||
let markers = search.get('monochromeMarkers');
|
||
if (markers != undefined) {
|
||
if (markers.length == 6)
|
||
monochromeMarkers = '#' + markers;
|
||
else
|
||
monochromeMarkers = "#FFFFFF";
|
||
}
|
||
|
||
let outlineColor = search.get('outlineColor');
|
||
if (outlineColor != undefined) {
|
||
if (outlineColor.length == 6)
|
||
OutlineADSBColor = '#' + outlineColor;
|
||
else
|
||
OutlineADSBColor = "#000000";
|
||
}
|
||
|
||
if (search.has('centerReceiver')) {
|
||
OLMap.getView().setCenter(ol.proj.fromLonLat([SiteLon, SiteLat]));
|
||
}
|
||
|
||
} catch (error) {
|
||
console.log(error);
|
||
}
|
||
}
|
||
|
||
function findPlanes(query, byIcao, byCallsign, byReg, byType) {
|
||
if (query == null)
|
||
return;
|
||
query = query.toLowerCase();
|
||
let results = [];
|
||
if (byReg) {
|
||
if (regCache) {
|
||
if (regCache[query.toUpperCase()]) {
|
||
selectPlaneByHex(regCache[query.toUpperCase()].toLowerCase(), {follow: true});
|
||
return;
|
||
}
|
||
} else {
|
||
let req_url = databaseFolder + "/regIcao.js";
|
||
let req = $.ajax({ url: req_url,
|
||
cache: true,
|
||
timeout: 10000,
|
||
dataType : 'json'
|
||
});
|
||
req.done(function(data) {
|
||
regCache = data;
|
||
if (regCache[query.toUpperCase()]) {
|
||
selectPlaneByHex(regCache[query.toUpperCase()].toLowerCase(), {follow: true});
|
||
return;
|
||
}
|
||
});
|
||
}
|
||
}
|
||
for (let i in PlanesOrdered) {
|
||
const plane = PlanesOrdered[i];
|
||
if (
|
||
(byCallsign && plane.flight != null && plane.flight.toLowerCase().match(query))
|
||
|| (byIcao && plane.icao.toLowerCase().match(query))
|
||
|| (byReg && plane.registration != null && plane.registration.toLowerCase().match(query))
|
||
|| (byType && plane.icaoType != null && plane.icaoType.toLowerCase().match(query))
|
||
) {
|
||
if (plane.checkVisible())
|
||
results.push(plane);
|
||
}
|
||
}
|
||
if (results.length > 1) {
|
||
toggleMultiSelect("on");
|
||
for (let i in results) {
|
||
results[i].selected = true;
|
||
results[i].updateTick(true);
|
||
}
|
||
} else if (results.length == 1) {
|
||
selectPlaneByHex(results[0].icao, {follow: true});
|
||
console.log("query selected: " + query);
|
||
} else {
|
||
console.log("No match found for query: " + query);
|
||
if (globeIndex && query.length == 6 && query.toLowerCase().match(/[a-f,0-9]{6}/)) {
|
||
console.log("maybe it's an icao, let's try to fetch the history for it!");
|
||
selectPlaneByHex(query, {follow: true})
|
||
}
|
||
}
|
||
}
|
||
|
||
function trailReaper() {
|
||
for (let i in PlanesOrdered) {
|
||
PlanesOrdered[i].reapTrail();
|
||
}
|
||
}
|
||
|
||
function globeIndexes() {
|
||
if (mapIsVisible || lastGlobeExtent == null) {
|
||
let mapSize = OLMap.getSize();
|
||
let size = [mapSize[0] * 1.1, mapSize[1] * 1.1];
|
||
lastGlobeExtent = myExtent(OLMap.getView().calculateExtent(size));
|
||
}
|
||
let extent = lastGlobeExtent.extent;
|
||
const bottomLeft = ol.proj.toLonLat([extent[0], extent[1]]);
|
||
const topRight = ol.proj.toLonLat([extent[2], extent[3]]);
|
||
let x1 = bottomLeft[0];
|
||
let y1 = bottomLeft[1];
|
||
let x2 = topRight[0];
|
||
let y2 = topRight[1];
|
||
if (Math.abs(extent[2] - extent[0]) > 40075016) {
|
||
// all longtitudes in view, only check latitude
|
||
x1 = -180;
|
||
x2 = 180;
|
||
}
|
||
if (y1 < -90)
|
||
y1 = -90;
|
||
if (y2 > 90)
|
||
y2 = 90;
|
||
let indexes = [];
|
||
//console.log(x1 + ' ' + x2);
|
||
let grid = globeIndexGrid;
|
||
|
||
let x3 = (x1 < x2) ? x2 : 300;
|
||
let count = 0;
|
||
|
||
for (let lon = x1; lon < x3 + grid; lon += grid) {
|
||
if (x1 >= x2 && lon > 180) {
|
||
lon -= 360;
|
||
x3 = x2;
|
||
}
|
||
if (lon > x3)
|
||
lon = x3 + 0.01;
|
||
for (let lat = y1; lat < y2 + grid; lat += grid) {
|
||
if (lat > y2)
|
||
lat = y2 + 0.01;
|
||
if (count++ > 2000) {
|
||
console.log("globeIndexes fail, lon: " + lon + ", lat: " + lat);
|
||
break;
|
||
}
|
||
if (lat > 90)
|
||
break;
|
||
let index = globe_index(lat, lon);
|
||
//console.log(lat + ' ' + lon + ' ' + index);
|
||
if (!indexes.includes(index)) {
|
||
indexes.push(index);
|
||
}
|
||
}
|
||
}
|
||
return indexes;
|
||
}
|
||
function globe_index(lat, lon) {
|
||
let grid = globeIndexGrid;
|
||
|
||
lat = grid * Math.floor((lat + 90) / grid) - 90;
|
||
lon = grid * Math.floor((lon + 180) / grid) - 180;
|
||
|
||
for (let i = 0; i < globeIndexSpecialTiles.length; i++) {
|
||
let tile = globeIndexSpecialTiles[i];
|
||
if (lat >= tile.south && lat < tile.north) {
|
||
if (tile.west < tile.east && lon >= tile.west && lon < tile.east) {
|
||
return i;
|
||
}
|
||
if (tile.west > tile.east && (lon >= tile.west || lon < tile.east)) {
|
||
return i;
|
||
}
|
||
}
|
||
}
|
||
|
||
let i = Math.floor((lat+90) / grid);
|
||
let j = Math.floor((lon+180) / grid);
|
||
|
||
let lat_multiplier = Math.floor(360 / grid + 1);
|
||
return (i * lat_multiplier + j + 1000);
|
||
}
|
||
|
||
function myExtent(extent) {
|
||
let bottomLeft = ol.proj.toLonLat([extent[0], extent[1]]);
|
||
let topRight = ol.proj.toLonLat([extent[2], extent[3]]);
|
||
return {
|
||
extent: extent,
|
||
minLon: bottomLeft[0],
|
||
maxLon: topRight[0],
|
||
minLat: bottomLeft[1],
|
||
maxLat: topRight[1],
|
||
}
|
||
}
|
||
|
||
function inView(pos, ex) {
|
||
|
||
if (pos == null)
|
||
return false;
|
||
|
||
if (solidT)
|
||
return true;
|
||
|
||
let extent = ex.extent;
|
||
let lon = pos[0];
|
||
let lat = pos[1];
|
||
|
||
//console.log((currExtent[2]-currExtent[0])/40075016);
|
||
//console.log([bottomLeft[0], topRight[0]]);
|
||
//console.log([bottomLeft[1], topRight[1]]);
|
||
//sidebarVisible = $("#sidebar_container").is(":visible");
|
||
//const proj = ol.proj.fromLonLat(pos);
|
||
if (lat < ex.minLat || lat > ex.maxLat)
|
||
return false;
|
||
|
||
if (extent[2] - extent[0] > 40075016) {
|
||
// all longtitudes in view, only check latitude
|
||
return true;
|
||
} else if (ex.minLon < ex.maxLon) {
|
||
// no wraparound: view not crossing 179 to -180 transition line
|
||
return (lon > ex.minLon && lon < ex.maxLon);
|
||
} else {
|
||
// wraparound: view crossing 179 to -180 transition line
|
||
return (lon > ex.minLon || lon < ex.maxLon);
|
||
}
|
||
}
|
||
function updateAddressBar() {
|
||
if (heatmap)
|
||
return;
|
||
let posString = 'lat=' + CenterLat.toFixed(3) + '&lon=' + CenterLon.toFixed(3) + '&zoom=' + ZoomLvl.toFixed(1);
|
||
let string;
|
||
if (showTrace) {
|
||
if (SelectedPlane)
|
||
posString = "&" + posString;
|
||
else
|
||
posString = "?" + posString;
|
||
} else {
|
||
posString = ""
|
||
}
|
||
|
||
let planes = [];
|
||
if (multiSelect && !SelectedAllPlanes) {
|
||
for (let i = 0; i < PlanesOrdered.length; ++i) {
|
||
let plane = PlanesOrdered[i];
|
||
if (plane.selected)
|
||
planes.push(plane);
|
||
}
|
||
} else if (SelectedPlane) {
|
||
planes.push(SelectedPlane);
|
||
}
|
||
|
||
string = pathName;
|
||
if (planes.length > 0) {
|
||
string += '?icao=';
|
||
for (let i = 0; i < planes.length; i++) {
|
||
string += planes[i].icao;
|
||
if (i < planes.length - 1)
|
||
string += ',';
|
||
}
|
||
}
|
||
|
||
string += posString;
|
||
|
||
if (SelectedPlane && showTrace) {
|
||
string += '&showTrace=' + traceDateString;
|
||
if (legSel != -1)
|
||
string += '&leg=' + (legSel + 1);
|
||
}
|
||
|
||
shareLink = string;
|
||
|
||
if (uuid)
|
||
return;
|
||
if (icaoFilter)
|
||
return;
|
||
|
||
if (SelectedPlane && globeIndex) {
|
||
let icao_link = "<a style=\"color: blue\" target=\"_blank\" href=\"" + shareLink + "\">Share</a>";
|
||
icao_link = NBSP +NBSP +NBSP +NBSP +NBSP +NBSP + icao_link;
|
||
$('#selected_icao').html(SelectedPlane.icao.toUpperCase() + icao_link);
|
||
}
|
||
|
||
window.history && window.history.replaceState && window.history.replaceState("object or string", "Title", string);
|
||
}
|
||
|
||
function refreshInt() {
|
||
let refresh = RefreshInterval;
|
||
if (!globeIndex)
|
||
return refresh;
|
||
|
||
if (adsbexchange && refresh < 2500)
|
||
refresh = 2500;
|
||
|
||
inactiveUpdate();
|
||
|
||
if (inactive < 70)
|
||
inactive = 70;
|
||
if (inactive > 400)
|
||
inactive = 400;
|
||
|
||
if (document[hidden])
|
||
refresh = 24 * 3600 * 1000; // hidden tab, don't refresh to avoid freeze when the tab is switched to again.
|
||
else
|
||
refresh *= inactive / 70;
|
||
|
||
if (!mapIsVisible)
|
||
refresh *= 2;
|
||
|
||
if (onMobile)
|
||
refresh *= 1.5;
|
||
|
||
return refresh;
|
||
}
|
||
|
||
function toggleLargeMode() {
|
||
|
||
largeMode++;
|
||
if (!(largeMode >= 1 && largeMode <= 4))
|
||
largeMode = 1;
|
||
|
||
let root = document.documentElement;
|
||
|
||
const base = 1.2;
|
||
globalScale = Math.pow(base, largeMode) / base;
|
||
root.style.setProperty("--SCALE", globalScale);
|
||
|
||
labelFont = "bold " + (12 * globalScale) + "px/" + (14 * globalScale) + "px Tahoma, Verdana, Helvetica, sans-serif";
|
||
|
||
localStorage['largeMode'] = largeMode;
|
||
|
||
changeZoom("init");
|
||
setLineWidth();
|
||
refreshFeatures();
|
||
refreshSelected();
|
||
remakeTrails();
|
||
}
|
||
|
||
function toggleShowTrace() {
|
||
if (!showTrace) {
|
||
dbServer = false; // temporary until i get the traces supplemented with registration data
|
||
toggleMultiSelect("off");
|
||
showTrace = true;
|
||
toggleFollow(false);
|
||
showTraceWasIsolation = onlySelected;
|
||
toggleIsolation("on", null);
|
||
shiftTrace();
|
||
$('#history_collapse')[0].style.display = "block";
|
||
$('#show_trace').addClass("active");
|
||
} else {
|
||
showTrace = false;
|
||
legSel = -1;
|
||
$('#leg_sel').text('Legs: All');
|
||
if (!showTraceWasIsolation)
|
||
toggleIsolation(null, "off");
|
||
//let string = pathName + '?icao=' + SelectedPlane.icao;
|
||
//window.history.replaceState("object or string", "Title", string);
|
||
//shareLink = string;
|
||
updateAddressBar();
|
||
$('#history_collapse')[0].style.display = "none";
|
||
$('#show_trace').removeClass("active");
|
||
const hex = SelectedPlane.icao;
|
||
SelectedPlane = null;
|
||
showTraceExit = true;
|
||
selectPlaneByHex(hex, {follow: true, zoom: ZoomLvl,});
|
||
}
|
||
}
|
||
|
||
function legShift(offset) {
|
||
if(!offset)
|
||
offset = 0;
|
||
|
||
legSel += offset;
|
||
|
||
if (offset != 0)
|
||
traceOpts.showTime = null;
|
||
|
||
if (!SelectedPlane.fullTrace) {
|
||
$('#leg_sel').text('No Data available for\n' + traceDateString);
|
||
$('#trace_time').text('UTC:\n');
|
||
SelectedPlane.processTrace();
|
||
return;
|
||
}
|
||
|
||
let trace = SelectedPlane.fullTrace.trace;
|
||
let legStart = null;
|
||
let legEnd = null;
|
||
let count = 0;
|
||
let timeZero = SelectedPlane.fullTrace.timestamp;
|
||
|
||
for (let i = 1; i < trace.length; i++) {
|
||
let timestamp = timeZero + trace[i][0];
|
||
if (traceOpts.startStamp != null && timestamp < traceOpts.startStamp) {
|
||
continue;
|
||
}
|
||
if (traceOpts.endStamp != null && timestamp > traceOpts.endStamp)
|
||
break;
|
||
if (legStart == null) {
|
||
legStart = i;
|
||
i++;
|
||
if (i >= trace.length)
|
||
break;
|
||
}
|
||
if (trace[i][6] & 2) {
|
||
count++;
|
||
}
|
||
}
|
||
if (legSel < -1)
|
||
legSel = count;
|
||
if (legSel > count)
|
||
legSel = -1;
|
||
|
||
if (legSel == -1) {
|
||
$('#leg_sel').text('Legs: All');
|
||
traceOpts.legStart = null;
|
||
traceOpts.legEnd = null;
|
||
SelectedPlane.processTrace();
|
||
updateAddressBar();
|
||
return;
|
||
}
|
||
|
||
count = 0;
|
||
for (let i = legStart + 1; i < trace.length; i++) {
|
||
let timestamp = timeZero + trace[i][0];
|
||
if (traceOpts.endStamp != null && timestamp > traceOpts.endStamp)
|
||
break;
|
||
if (trace[i][6] & 2) {
|
||
if (count == legSel - 1)
|
||
legStart = i;
|
||
if (count == legSel)
|
||
legEnd = i; // exclusive
|
||
count++;
|
||
}
|
||
}
|
||
$('#leg_sel').text('Leg: ' + (legSel + 1));
|
||
traceOpts.legStart = legStart;
|
||
traceOpts.legEnd = legEnd;
|
||
SelectedPlane.processTrace();
|
||
|
||
updateAddressBar();
|
||
}
|
||
|
||
function shiftTrace(offset) {
|
||
if (traceRate > 180) {
|
||
$('#leg_sel').text('Slow down! ...');
|
||
return;
|
||
}
|
||
$('#leg_sel').text('Loading ...');
|
||
if (traceDateString && !traceDate) {
|
||
let numbers = traceDateString.split('-');
|
||
traceDate = new Date();
|
||
traceDate.setUTCFullYear(numbers[0]);
|
||
traceDate.setUTCMonth(numbers[1] - 1);
|
||
traceDate.setUTCDate(numbers[2]);
|
||
}
|
||
if (!traceDate || offset == "today") {
|
||
traceDate = new Date();
|
||
} else if (offset) {
|
||
let sinceEpoch = traceDate.getTime();
|
||
traceDate.setTime(sinceEpoch + offset * 86400 * 1000);
|
||
}
|
||
traceDate.setUTCHours(0);
|
||
traceDate.setUTCMinutes(0);
|
||
traceDate.setUTCSeconds(0);
|
||
|
||
traceDay = traceDate.getUTCDate();
|
||
|
||
traceDateString = zDateString(traceDate);
|
||
|
||
$('#trace_date').text('UTC day:\n' + traceDateString);
|
||
|
||
let hex = SelectedPlane ? SelectedPlane.icao : icaoParam;
|
||
|
||
let selectOptions = {noDeselect: true, zoom: ZoomLvl};
|
||
selectPlaneByHex(hex, selectOptions);
|
||
|
||
updateAddressBar();
|
||
}
|
||
|
||
function setLineWidth() {
|
||
|
||
newWidth = lineWidth * Math.pow(2, globalScale) / 2 * globalScale
|
||
estimateStyle = new ol.style.Style({
|
||
stroke: new ol.style.Stroke({
|
||
color: '#808080',
|
||
width: 1.2 * newWidth,
|
||
})
|
||
});
|
||
estimateStyleSlim = new ol.style.Style({
|
||
stroke: new ol.style.Stroke({
|
||
color: '#808080',
|
||
width: 0.4 * newWidth,
|
||
})
|
||
});
|
||
|
||
badLine = new ol.style.Style({
|
||
stroke: new ol.style.Stroke({
|
||
color: '#FF0000',
|
||
width: 2 * newWidth,
|
||
})
|
||
});
|
||
badLineMlat = new ol.style.Style({
|
||
stroke: new ol.style.Stroke({
|
||
color: '#FFA500',
|
||
width: 2 * newWidth,
|
||
})
|
||
});
|
||
|
||
badDot = new ol.style.Style({
|
||
image: new ol.style.Circle({
|
||
radius: 3.5 * newWidth,
|
||
fill: new ol.style.Fill({
|
||
color: '#FF0000',
|
||
})
|
||
}),
|
||
});
|
||
badDotMlat = new ol.style.Style({
|
||
image: new ol.style.Circle({
|
||
radius: 3.5 * newWidth,
|
||
fill: new ol.style.Fill({
|
||
color: '#FFA500',
|
||
})
|
||
}),
|
||
});
|
||
|
||
labelFill = new ol.style.Fill({color: 'white' });
|
||
blackFill = new ol.style.Fill({color: 'black' });
|
||
labelStroke = new ol.style.Stroke({color: 'rgba(0,0,0,0.7', width: 4 * globalScale});
|
||
labelStrokeNarrow = new ol.style.Stroke({color: 'rgba(0,0,0,0.7', width: 2.5 * globalScale});
|
||
bgFill = new ol.style.Stroke({color: 'rgba(0,0,0,0.25'});
|
||
}
|
||
|
||
function geoFindMe() {
|
||
|
||
function success(position) {
|
||
if (!SiteOverride) {
|
||
SiteLat = CenterLat = DefaultCenterLat = position.coords.latitude;
|
||
SiteLon = CenterLon = DefaultCenterLon = position.coords.longitude;
|
||
}
|
||
if (localStorage['geoFindMeFirstVisit'] == undefined) {
|
||
OLMap.getView().setCenter(ol.proj.fromLonLat([CenterLon, CenterLat]));
|
||
localStorage['geoFindMeFirstVisit'] = 'no';
|
||
}
|
||
initSitePos();
|
||
}
|
||
|
||
function error() {
|
||
console.log("Unable to query location.");
|
||
initSitePos();
|
||
}
|
||
|
||
if (!navigator.geolocation) {
|
||
console.log('Geolocation is not supported by your browser');
|
||
} else {
|
||
console.log('Locating…');
|
||
navigator.geolocation.getCurrentPosition(success, error);
|
||
}
|
||
|
||
}
|
||
|
||
window.mobilecheck = function() {
|
||
let check = false;
|
||
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
|
||
return check;
|
||
};
|
||
|
||
function initSitePos() {
|
||
if (SitePosInitialized)
|
||
return;
|
||
|
||
// Set SitePosition
|
||
if (SiteLat != null && SiteLon != null) {
|
||
SitePosition = [SiteLon, SiteLat];
|
||
// Add home marker if requested
|
||
createSiteCircleFeatures();
|
||
} else {
|
||
SitePosition = null;
|
||
HideCols.push("#distance");
|
||
setColumnVisibility();
|
||
}
|
||
|
||
if (SitePosition && !onMobile) {
|
||
sortByDistance();
|
||
} else {
|
||
sortByAltitude();
|
||
sortByAltitude();
|
||
}
|
||
}
|
||
|
||
function drawAlt() {
|
||
processAircraft({hex: 'c0ffee', });
|
||
let plane = Planes['c0ffee'];
|
||
newWidth = 4;
|
||
for (let i = 0; i <= 50000; i += 500) {
|
||
plane.position = [i/10000, 0];
|
||
plane.altitude = i;
|
||
plane.alt_rounded = calcAltitudeRounded(plane.altitude);
|
||
plane.updateTrack(now - i, now - i - 5000, { serverTrack: true });
|
||
}
|
||
}
|
||
|
||
function remakeTrails() {
|
||
for (let i in PlanesOrdered) {
|
||
PlanesOrdered[i].remakeTrail();
|
||
PlanesOrdered[i].updateTick(true);
|
||
}
|
||
}
|
||
|
||
function createSiteCircleFeatures() {
|
||
StaticFeatures.clear();
|
||
drawUpintheair();
|
||
|
||
// Clear existing circles first
|
||
if (!SitePosition)
|
||
return;
|
||
|
||
if (SiteShow) {
|
||
let 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
|
||
})
|
||
})
|
||
});
|
||
|
||
let feature = new ol.Feature(new ol.geom.Point(ol.proj.fromLonLat(SitePosition)));
|
||
feature.setStyle(markerStyle);
|
||
StaticFeatures.addFeature(feature);
|
||
}
|
||
|
||
if (!SiteCircles)
|
||
return;
|
||
|
||
let circleColor = '#000000';
|
||
|
||
for (let i = 0; i < SiteCirclesDistances.length; i++) {
|
||
circleColor = i < SiteCirclesColors.length ? SiteCirclesColors[i] : circleColor;
|
||
|
||
let conversionFactor = 1000.0;
|
||
if (DisplayUnits === "nautical") {
|
||
conversionFactor = 1852.0;
|
||
} else if (DisplayUnits === "imperial") {
|
||
conversionFactor = 1609.0;
|
||
}
|
||
|
||
let distance = SiteCirclesDistances[i] * conversionFactor;
|
||
let circle = make_geodesic_circle(SitePosition, distance, 180);
|
||
circle.transform('EPSG:4326', 'EPSG:3857');
|
||
let feature = new ol.Feature(circle);
|
||
|
||
let circleStyle = new ol.style.Style({
|
||
fill: null,
|
||
stroke: new ol.style.Stroke({
|
||
color: circleColor,
|
||
lineDash: SiteCirclesLineDash,
|
||
width: globalScale,
|
||
}),
|
||
text: new ol.style.Text({
|
||
font: ((10 * globalScale) + 'px Helvetica Neue, Helvetica, Tahoma, Verdana, sans-serif'),
|
||
fill: new ol.style.Fill({ color: '#000' }),
|
||
offsetY: -8,
|
||
text: format_distance_long(distance, DisplayUnits, 0),
|
||
})
|
||
});
|
||
|
||
feature.setStyle(circleStyle);
|
||
StaticFeatures.addFeature(feature);
|
||
}
|
||
}
|
||
|
||
function drawUpintheair() {
|
||
// 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/local/share/tar1090/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
|
||
if (!globeIndex) {
|
||
let request = $.ajax({ url: 'upintheair.json',
|
||
cache: true,
|
||
dataType: 'json' });
|
||
request.done(function(data) {
|
||
for (let i = 0; i < data.rings.length; ++i) {
|
||
let geom = null;
|
||
let points = data.rings[i].points;
|
||
let altitude = (3.28084 * data.rings[i].alt).toFixed(0);
|
||
let color = range_outline_color;
|
||
if (range_outline_colored_by_altitude) {
|
||
let colorArr = altitudeColor(altitude);
|
||
color = 'hsl(' + colorArr[0].toFixed(0) + ',' + colorArr[1].toFixed(0) + '%,' + colorArr[2].toFixed(0) + '%)';
|
||
}
|
||
let ringStyle = new ol.style.Style({
|
||
fill: null,
|
||
stroke: new ol.style.Stroke({
|
||
color: color,
|
||
width: range_outline_width,
|
||
})
|
||
});
|
||
if (points.length > 0) {
|
||
geom = new ol.geom.LineString([[ points[0][1], points[0][0] ]]);
|
||
for (let 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');
|
||
|
||
let feature = new ol.Feature(geom);
|
||
feature.setStyle(ringStyle);
|
||
StaticFeatures.addFeature(feature);
|
||
}
|
||
}
|
||
});
|
||
|
||
request.fail(function(jqxhr, status, error) {
|
||
// no rings available, do nothing
|
||
});
|
||
}
|
||
}
|
||
|
||
function gotoTime(timestamp) {
|
||
clearTimeout(traceOpts.showTimeout);
|
||
if (timestamp) {
|
||
traceOpts.showTime = timestamp;
|
||
traceOpts.animate = false;
|
||
}
|
||
if (!traceOpts.animate) {
|
||
legShift(0);
|
||
} else {
|
||
if (SelectedPlane.marker) {
|
||
|
||
traceOpts.animateFromLon += (traceOpts.animateToLon - traceOpts.animateFromLon) / traceOpts.animateSteps;
|
||
traceOpts.animateFromLat += (traceOpts.animateToLat - traceOpts.animateFromLat) / traceOpts.animateSteps;
|
||
|
||
let animatePos = [traceOpts.animateFromLon, traceOpts.animateFromLat];
|
||
SelectedPlane.marker.setGeometry(new ol.geom.Point(animatePos));
|
||
|
||
//console.log('int: ', animatePos);
|
||
if (FollowSelected)
|
||
OLMap.getView().setCenter(animatePos);
|
||
}
|
||
if (--traceOpts.animateSteps == 1)
|
||
traceOpts.animate = false;
|
||
traceOpts.showTimeout = setTimeout(gotoTime, traceOpts.animateInterval);
|
||
}
|
||
}
|
||
|
||
function checkFollow() {
|
||
if (!FollowSelected || traceOpts.showTime)
|
||
return;
|
||
if (!SelectedPlane || !SelectedPlane.position) {
|
||
toggleFollow(false);
|
||
return;
|
||
}
|
||
const center = ol.proj.toLonLat(OLMap.getView().getCenter());
|
||
if (Math.abs(center[0] - SelectedPlane.position[0]) > 0.001 ||
|
||
Math.abs(center[1] - SelectedPlane.position[1]) > 0.001) {
|
||
toggleFollow(false);
|
||
} else {
|
||
toggleFollow(true);
|
||
}
|
||
}
|
||
|
||
function decrementTraceRate() {
|
||
if (traceRate > 0)
|
||
traceRate = traceRate * 0.985 - 1;
|
||
}
|
||
|
||
function getTrace(newPlane, hex, options) {
|
||
|
||
if (options.list) {
|
||
newPlane = options.list.pop()
|
||
if (!newPlane) {
|
||
return;
|
||
}
|
||
hex = newPlane.icao;
|
||
}
|
||
|
||
let now = new Date().getTime();
|
||
let backoff = 200;
|
||
if (!showTrace && traceRate > 140 && now < lastTraceGet + backoff) {
|
||
setTimeout(getTrace, lastTraceGet + backoff + 20 - now, newPlane, hex, options);
|
||
return;
|
||
}
|
||
|
||
lastTraceGet = now;
|
||
|
||
let URL1 = 'data/traces/'+ hex.slice(-2) + '/trace_recent_' + hex + '.json';
|
||
let URL2 = 'data/traces/'+ hex.slice(-2) + '/trace_full_' + hex + '.json';
|
||
//console.log('Requesting trace: ' + hex);
|
||
|
||
if (!newPlane) {
|
||
processAircraft({hex: hex, });
|
||
newPlane = Planes[hex];
|
||
newPlane.last_message_time = NaN;
|
||
newPlane.position_time = NaN;
|
||
newPlane.selected = true;
|
||
SelectedPlane = newPlane;
|
||
}
|
||
|
||
traceOpts = options;
|
||
|
||
if (showTrace) {
|
||
traceRate += 3;
|
||
let today = new Date();
|
||
//console.log(today.toUTCString() + ' ' + traceDate.toUTCString());
|
||
// use non historic traces for showTrace until 30 min after midnight
|
||
if (today.getTime() > traceDate.getTime() && today.getTime() < traceDate.getTime() + (24 * 60 * 60 + 30 * 60) * 1000) {
|
||
|
||
today.setUTCHours(0);
|
||
today.setUTCMinutes(0);
|
||
today.setUTCSeconds(0);
|
||
|
||
traceOpts.startStamp = today.getTime() / 1000;
|
||
} else {
|
||
URL1 = null;
|
||
URL2 = 'globe_history/' + traceDateString + '/traces/' + hex.slice(-2) + '/trace_full_' + hex + '.json';
|
||
}
|
||
} else {
|
||
traceRate += 2;
|
||
}
|
||
if (newPlane && (showTrace || showTraceExit)) {
|
||
newPlane.trace = [];
|
||
newPlane.recentTrace = null;
|
||
newPlane.fullTrace = null;
|
||
}
|
||
|
||
//console.log(URL2);
|
||
|
||
let req1 = null;
|
||
let req2 = null;
|
||
|
||
options.plane = newPlane;
|
||
options.defer = $.Deferred();
|
||
|
||
let fake1 = false;
|
||
|
||
if (URL1 && !options.onlyFull) {
|
||
req1 = $.ajax({ url: URL1,
|
||
dataType: 'json',
|
||
options: options,
|
||
});
|
||
} else {
|
||
options.defer.resolve(newPlane);
|
||
fake1 = true;
|
||
}
|
||
|
||
req2 = $.ajax({ url: URL2,
|
||
dataType: 'json',
|
||
options: options,
|
||
});
|
||
|
||
options.req2 = req2;
|
||
|
||
if (!fake1) {
|
||
req1.done(function(data) {
|
||
let plane = data.plane || this.options.plane;
|
||
plane.recentTrace = data;
|
||
if (!showTrace)
|
||
plane.processTrace();
|
||
let defer = data.defer || this.options.defer;
|
||
defer.resolve(plane);
|
||
});
|
||
}
|
||
req2.done(function(data) {
|
||
let plane = this.options.plane;
|
||
plane.fullTrace = data;
|
||
this.options.defer.done(function(plane) {
|
||
if (showTrace)
|
||
legShift(0);
|
||
else
|
||
plane.processTrace();
|
||
});
|
||
if (options.list) {
|
||
newPlane.updateLines();
|
||
getTrace(null, null, options);
|
||
}
|
||
});
|
||
req2.fail(function() {
|
||
if (showTrace)
|
||
legShift(0);
|
||
else
|
||
this.options.plane.processTrace();
|
||
|
||
if (options.list)
|
||
getTrace(null, null, options);
|
||
});
|
||
|
||
return newPlane;
|
||
}
|
||
function initHeatmap() {
|
||
heatmap.init = false;
|
||
if (heatFeatures.length == 0) {
|
||
for (let i = 0; i < heatFeaturesSpread; i++) {
|
||
heatFeatures.push(new ol.source.Vector());
|
||
heatLayers.push(new ol.layer.Vector({
|
||
name: ('heatLayer' + i),
|
||
isTrail: true,
|
||
source: heatFeatures[i],
|
||
declutter: (heatmap.declutter ? true : false),
|
||
zIndex: 150,
|
||
renderOrder: null,
|
||
renderBuffer: 5,
|
||
}));
|
||
trailGroup.push(heatLayers[i]);
|
||
}
|
||
}
|
||
realHeat = new ol.layer.Heatmap({
|
||
source: realHeatFeatures,
|
||
name: realHeat,
|
||
isTrail: true,
|
||
zIndex: 150,
|
||
weight: x => heatmap.weight,
|
||
radius: heatmap.radius,
|
||
blur: heatmap.blur,
|
||
});
|
||
trailGroup.push(realHeat);
|
||
}
|
||
|
||
function setSize(set) {
|
||
let count = 0;
|
||
for (const i of set.values())
|
||
count++;
|
||
return count;
|
||
}
|
||
|
||
function drawHeatmap() {
|
||
if (!heatmap || replay)
|
||
return;
|
||
if (heatmap.init) {
|
||
initHeatmap();
|
||
}
|
||
|
||
console.time("drawHeat");
|
||
|
||
let ext = myExtent(OLMap.getView().calculateExtent(OLMap.getSize()));
|
||
let maxLat = ext.maxLat * 1000000;
|
||
let minLat = ext.minLat * 1000000;
|
||
|
||
for (let i = 0; i < heatFeaturesSpread; i++)
|
||
heatFeatures[i].clear();
|
||
realHeatFeatures.clear();
|
||
|
||
let pointCount = 0;
|
||
let features = [];
|
||
if (lineStyleCache["scale"] != globalScale) {
|
||
lineStyleCache = {};
|
||
lineStyleCache["scale"] = globalScale;
|
||
}
|
||
let done = new Set();
|
||
let iterations = 0;
|
||
let maxIter = 1000 * 1000;
|
||
|
||
|
||
let tempPoints = [];
|
||
for (let k = 0; k < heatChunks.length; k++) {
|
||
if (heatPoints[k] != null) {
|
||
true; // do nothing
|
||
} else if (heatChunks[k] != null) {
|
||
if (heatChunks[k].byteLength % 16 != 0) {
|
||
console.log("Invalid heatmap file (byteLength): " + k);
|
||
continue;
|
||
}
|
||
let points = heatPoints[k] = new Int32Array(heatChunks[k]);
|
||
let found = 0;
|
||
for (let i = 0; i < points.length; i += 4) {
|
||
if (points[i] == 0xe7f7c9d) {
|
||
found = 1;
|
||
break;
|
||
}
|
||
}
|
||
if (!found) {
|
||
heatPoints[k] = heatChunks[k] = null;
|
||
console.log("Invalid heatmap file (magic number): " + k);
|
||
}
|
||
} else {
|
||
continue;
|
||
}
|
||
tempPoints.push(heatPoints[k]);
|
||
}
|
||
//console.log('tempPoints.length: ' + tempPoints.length);
|
||
let myPoints = [];
|
||
if (tempPoints.length <= 2) {
|
||
myPoints = tempPoints;
|
||
} else {
|
||
let len = tempPoints.length;
|
||
let arr1 = tempPoints.splice(0, Math.round(tempPoints.length / 3));
|
||
let arr2 = tempPoints.splice(0, Math.round(tempPoints.length / 2));
|
||
let arr3 = tempPoints;
|
||
myPoints.push(arr2.splice(0, 1));
|
||
myPoints.push(arr3.splice(0, 1));
|
||
myPoints.push(arr1.splice(0, 1));
|
||
len -= 3;
|
||
for (let i = 0; i < Math.ceil(len / 3); i++) {
|
||
myPoints.push(arr2.splice(0, 1));
|
||
myPoints.push(arr3.splice(0, 1));
|
||
myPoints.push(arr1.splice(0, 1));
|
||
}
|
||
}
|
||
myPoints = myPoints.flat();
|
||
|
||
//console.log('myPoints.length: ' + myPoints.length);
|
||
|
||
let indexes = [];
|
||
for (let k = 0; k < myPoints.length; k++) {
|
||
let points = myPoints[k];
|
||
let index = [];
|
||
let i = 0;
|
||
if (!points)
|
||
continue;
|
||
while(points[i] != 0xe7f7c9d && i < points.length) {
|
||
index.push(points[i]);
|
||
//console.log(points[i]);
|
||
i += 4;
|
||
}
|
||
if (!heatmap.lines)
|
||
index.sort((a, b) => (Math.random() - 0.5));
|
||
indexes.push(index);
|
||
}
|
||
let offsets = Array(myPoints.length).fill(0);
|
||
while (pointCount < heatmap.max && setSize(done) < myPoints.length && iterations++ < maxIter) {
|
||
for (let k = 0; k < myPoints.length && pointCount < heatmap.max; k++) {
|
||
if (offsets[k] >= indexes[k].length) {
|
||
done.add(k);
|
||
continue;
|
||
}
|
||
|
||
let points = myPoints[k];
|
||
|
||
let i = 4 * indexes[k][offsets[k]];
|
||
|
||
if (points[i] == 0xe7f7c9d)
|
||
i += 4;
|
||
|
||
if (i < 0) {
|
||
console.log('wat ' + i);
|
||
break;
|
||
}
|
||
for (; i < points.length; i += 4) {
|
||
if (points[i] == 0xe7f7c9d)
|
||
break;
|
||
let lat = points[i+1];
|
||
if (lat > maxLat || lat < minLat)
|
||
continue;
|
||
|
||
lat /= 1000000;
|
||
let lon = points[i + 2] / 1000000;
|
||
let pos = [lon, lat];
|
||
|
||
if (!inView(pos, ext))
|
||
continue;
|
||
|
||
let alt = points[i + 3] & 65535;
|
||
if (alt & 32768)
|
||
alt |= -65536;
|
||
if (alt == -123)
|
||
alt = 'ground';
|
||
else
|
||
alt *= 25;
|
||
|
||
let gs = points[i + 3] >> 16;
|
||
if (gs == -1)
|
||
gs = null;
|
||
else
|
||
gs /= 10;
|
||
|
||
if (PlaneFilter.enabled && altFiltered(alt))
|
||
continue;
|
||
|
||
pointCount++;
|
||
//console.log(pos);
|
||
|
||
alt = calcAltitudeRounded(alt);
|
||
let projHere = ol.proj.fromLonLat(pos);
|
||
let style = lineStyleCache[alt];
|
||
if (!style) {
|
||
let hsl = altitudeColor(alt);
|
||
hsl[1] = hsl[1] * 0.85;
|
||
hsl[2] = hsl[2] * 0.8;
|
||
let col;
|
||
if (heatmap.alpha == null)
|
||
col = hslToRgb(hsl);
|
||
else
|
||
col = hslToRgb(hsl, heatmap.alpha);
|
||
|
||
style = new ol.style.Style({
|
||
image: new ol.style.Circle({
|
||
radius: heatmap.radius * globalScale,
|
||
fill: new ol.style.Fill({
|
||
color: col,
|
||
}),
|
||
}),
|
||
zIndex: i,
|
||
});
|
||
lineStyleCache[alt] = style;
|
||
}
|
||
let feat = new ol.Feature(new ol.geom.Point(projHere));
|
||
feat.setStyle(style);
|
||
features.push(feat);
|
||
//console.log(alt);
|
||
}
|
||
offsets[k] += 1;
|
||
}
|
||
}
|
||
if (iterations >= maxIter)
|
||
console.log("drawHeatmap: MAX_ITERATIONS!");
|
||
//console.log(setSize(done));
|
||
console.log("files: " + myPoints.length + ", points drawn: " + pointCount);
|
||
if (heatmap.real) {
|
||
realHeatFeatures.addFeatures(features);
|
||
} else {
|
||
for (let i = 0; i < heatFeaturesSpread; i++) {
|
||
heatFeatures[i].addFeatures(features.splice(0, pointCount / heatFeaturesSpread + 1));
|
||
//console.log(features.length);
|
||
}
|
||
}
|
||
console.timeEnd("drawHeat");
|
||
$("#loader").addClass("hidden");
|
||
}
|
||
|
||
function initReplay() {
|
||
let index = 0;
|
||
for (let k = 0; k < heatChunks.length; k++) {
|
||
if (heatChunks[k].byteLength % 16 != 0) {
|
||
console.log("Invalid heatmap file (byteLength): " + k);
|
||
continue;
|
||
}
|
||
let points = heatPoints[k] = new Int32Array(heatChunks[k]);
|
||
let found = 0;
|
||
for (let i = 0; i < points.length; i += 4) {
|
||
if (points[i] == 0xe7f7c9d) {
|
||
found = 1;
|
||
break;
|
||
}
|
||
}
|
||
if (!found) {
|
||
heatPoints[k] = heatChunks[k] = null;
|
||
console.log("Invalid heatmap file (magic number): " + k);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
function play() {
|
||
if (!replay)
|
||
return;
|
||
ZoomLvl = OLMap.getView().getZoom();
|
||
let center = ol.proj.toLonLat(OLMap.getView().getCenter());
|
||
localStorage['CenterLon'] = CenterLon = center[0];
|
||
localStorage['CenterLat'] = CenterLat = center[1];
|
||
clearTimeout(refreshId);
|
||
refreshId = setTimeout(replay, replay.ival / replay.speed);
|
||
if (showTrace)
|
||
return;
|
||
|
||
updateIconCache();
|
||
|
||
last = now;
|
||
|
||
for (let j=0; j < acs.length; j++) {
|
||
if (icaoFilter && !icaoFilter.includes(hex))
|
||
continue;
|
||
|
||
if (!onlyMilitary || plane.military)
|
||
plane.updateData(now, last, ac, init);
|
||
else
|
||
plane.last_message_time = now - ac.seen;
|
||
}
|
||
|
||
refreshTableInfo();
|
||
refreshClock(new Date(now * 1000));
|
||
refreshSelected();
|
||
refreshHighlighted();
|
||
|
||
}
|
||
|
||
|
||
function updateIconCache() {
|
||
let item;
|
||
let tryAgain = [];
|
||
while(item = addToIconCache.pop()) {
|
||
let svgKey = item[0];
|
||
let element = item[1];
|
||
if (iconCache[svgKey] != undefined) {
|
||
continue;
|
||
}
|
||
if (!element) {
|
||
element = new Image();
|
||
element.src = item[2];
|
||
item[1] = element;
|
||
tryAgain.push(item);
|
||
continue;
|
||
}
|
||
if (!element.complete) {
|
||
console.log("moep");
|
||
tryAgain.push(item);
|
||
continue;
|
||
}
|
||
|
||
iconCache[svgKey] = element;
|
||
}
|
||
addToIconCache = tryAgain;
|
||
}
|
||
function inactiveUpdate() {
|
||
inactive = (new Date().getTime() - lastActive) / 1000;
|
||
}
|
||
|
||
function active() {
|
||
let now = new Date().getTime();
|
||
if ((now - lastActive) > 200 * 1000) {
|
||
clearTimeout(refreshId);
|
||
refreshId = setTimeout(fetchData, RefreshInterval);
|
||
}
|
||
lastActive = now;
|
||
}
|
||
function drawTileBorder(data) {
|
||
let southWest = ol.proj.fromLonLat([data.west, data.south]);
|
||
let south180p = ol.proj.fromLonLat([180, data.south]);
|
||
let south180m = ol.proj.fromLonLat([-180, data.south]);
|
||
let southEast = ol.proj.fromLonLat([data.east, data.south]);
|
||
let northEast = ol.proj.fromLonLat([data.east, data.north]);
|
||
let north180p = ol.proj.fromLonLat([180, data.north]);
|
||
let north180m = ol.proj.fromLonLat([-180, data.north]);
|
||
let northWest = ol.proj.fromLonLat([data.west, data.north]);
|
||
const estimateStyle = new ol.style.Style({
|
||
stroke: new ol.style.Stroke({
|
||
color: '#303030',
|
||
width: 1.5,
|
||
})
|
||
});
|
||
if (data.west < data.east) {
|
||
let tile = new ol.geom.LineString([southWest, southEast, northEast, northWest, southWest]);
|
||
let tileFeature = new ol.Feature(tile);
|
||
tileFeature.setStyle(estimateStyle);
|
||
StaticFeatures.addFeature(tileFeature);
|
||
} else {
|
||
let west = new ol.geom.LineString([south180p, southWest, northWest, north180p]);
|
||
let east = new ol.geom.LineString([south180m, southEast, northEast, north180m]);
|
||
let westF = new ol.Feature(west);
|
||
let eastF = new ol.Feature(east);
|
||
westF.setStyle(estimateStyle);
|
||
eastF.setStyle(estimateStyle);
|
||
StaticFeatures.addFeature(westF);
|
||
StaticFeatures.addFeature(eastF);
|
||
}
|
||
}
|
||
|
||
function updateMessageRate(data) {
|
||
let time_delta = now - last;
|
||
if (data.messages && uuid == null) {
|
||
// 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});
|
||
|
||
if (MessageCountHistory.length > 1) {
|
||
let message_time_delta = MessageCountHistory[MessageCountHistory.length-1].time - MessageCountHistory[0].time;
|
||
let message_count_delta = MessageCountHistory[MessageCountHistory.length-1].messages - MessageCountHistory[0].messages;
|
||
if (message_time_delta > 0)
|
||
MessageRate = message_count_delta / message_time_delta;
|
||
}
|
||
|
||
// .. and clean up any old values
|
||
if ((now - MessageCountHistory[0].time) > 10)
|
||
MessageCountHistory.shift();
|
||
} else if (uuid != null) {
|
||
if (time_delta > 0.5) {
|
||
let message_delta = 0;
|
||
let acs = data.aircraft;
|
||
for (let j=0; j < acs.length; j++) {
|
||
let plane = Planes[acs[j].hex]
|
||
if (plane) {
|
||
message_delta += (acs[j].messages - plane.messages);
|
||
}
|
||
}
|
||
MessageRate = message_delta / time_delta;
|
||
}
|
||
} else {
|
||
MessageRate = null;
|
||
}
|
||
}
|