// Some global variables 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 variables let webgl = false; let webglSupported = false; let webglFeatures = new ol.source.Vector(); let webglLayer; 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 lineStyleCache = {}; let Planes = {}; let PlanesOrdered = []; let PlaneFilter = {}; let SelectedPlane = null; let SelectedAllPlanes = false; let HighlightedPlane = null; let FollowSelected = false; let noPan = false; let loadFinished = false; let mapResizeTimeout; let pointerMoveTimeout; let scaleFactor; let debugTracks = false; let debugAll = false; let trackLabels = false; let grouptype_checkbox; let multiSelect = false; let uat_data = null; let enableLabels = false; let extendedLabels = 0; let mapIsVisible = true; let tableInView = false; let onlyMLAT = false; let onlyMilitary = false; let onlyADSB = false; let onlySelected = false; let onlyDataSource = null; let fetchingPf = false; let debug = false; let debugJump = false; let jumpTo = null; let noMLAT = false; let noVanish = false; let filterTracks = false; let refreshId = 0; let globeIndexGrid = 0; let globeIndexNow = {}; let globeIndexSpecialTiles; let globeSimLoad = 4; let globeTableLimit = 80; let showGrid = false; let lastGlobeExtent; let PendingFetches = 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 userScale = 1; let iconScale = 1; let labelScale = 1; let newWidth = lineWidth; 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 firstFetchDone = false; let overrideMapType = null; let halloween = false; let noRegOnly = false; let triggerMapRefresh = false; 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 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; const nullStyle = new ol.style.Style({}); let estimateStyle; let estimateStyleSlim; let badLine; let badLineMlat; let badDot; let badDotMlat; // TAR1090 application object let TAR; TAR = (function (global, $, TAR) { return TAR; }(window, jQuery, TAR || {})); 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; 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"); 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); } else if (globeIndex) { let indexes = globeIndexes(); 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'; } PendingFetches = ac_url.length; if (globeIndex) { clearTimeout(refreshId); refreshId = setTimeout(fetchData, 25000); } else { $("#lastLeg_cb").parent().hide(); } 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) { triggerMapRefresh = true; } PendingFetches--; if (globeIndex) { clearTimeout(refreshId); refreshId = setTimeout(fetchData, refreshInt()); } // 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() { try { if (new URLSearchParams(window.location.search).has('iconTest')) { iconTest(); return; } } catch (error) { console.log(error); } $.when(configureReceiver, heatmapDefer).done(function() { configureReceiver = null; // Initialize stuff initPage(); initMap(); // Wait for history item downloads and append them to the buffer push_history(); $.when(historyLoaded).done(function() { startPage(); }); }); } function initPage() { 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('halloween')) halloween = 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('noRegOnly')) noRegOnly = true; if (search.has('mapContrast')) { let contrast = parseFloat(search.get('mapContrast')); if (!isNaN(contrast)) mapContrastPercentage = contrast; } if (search.has('iconScale')) { let scale = parseFloat(search.get('iconScale')); if (!isNaN(scale)) iconScale = scale; } if (search.has('labelScale')) { let scale = parseFloat(search.get('labelScale')); if (!isNaN(scale)) labelScale = scale; } if (search.has('scale')) { let scale = parseFloat(search.get('scale')); if (!isNaN(scale)) userScale = scale; } 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 (adsbexchange) { 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); } $('#tabs').tabs({ active: localStorage['active_tab'], activate: function (event, ui) { localStorage['active_tab'] = $("#tabs").tabs("option", "active"); }, collapsible: true }); // Set page basics document.title = PageName; TAR.planesTable.init(); 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 (showTrace) { toggleShowTrace(); } if (SelectedPlane) { SelectedPlane.selected = null; SelectedPlane.clearLines(); SelectedPlane.updateMarker(); SelectedPlane = null; refreshSelected(); setSelectedInfoBlockVisibility(); TAR.planesTable.refresh(); updateAddressBar(); } }); /* // 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 $("#expand_sidebar_button").click(expandSidebar); $("#shrink_sidebar_button").click(showMap); $("#large_mode_button").click(toggleLargeMode); // 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); $('#settingsCog').on('click', function() { $('#settings_infoblock').toggle(); }); $('#settings_close').on('click', function() { $('#settings_infoblock').hide(); }); $('#groundvehicle_filter').on('click', function() { filterGroundVehicles(true); refreshSelected(); refreshHighlighted(); TAR.planesTable.refresh(); mapRefresh(); }); $('#blockedmlat_filter').on('click', function() { filterBlockedMLAT(true); refreshSelected(); refreshHighlighted(); TAR.planesTable.refresh(); mapRefresh(); }); $('#grouptype_checkbox').on('click', function() { if ($('#grouptype_checkbox').hasClass('settingsCheckboxChecked')) { TAR.planesTable.cols.distance.sort(); } else { TAR.planesTable.cols.data_source.sort(); } }); new Toggle({ key: "lastLeg", display: "Last Leg only", container: "#settingsLeft", init: true, setState: function(state) { lastLeg = state; if (SelectedPlane && !showTrace) SelectedPlane.processTrace(); } }); 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(); $('#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({ key: "debugTracks", display: "Debug Tracks", container: "#settingsLeft", init: false, setState: function(state) { debugTracks = state; remakeTrails(); } }); new Toggle({ key: "debugAll", display: "Debug show all", container: "#settingsLeft", init: false, setState: function(state) { if (state) debugAll = true; else debugAll = false; } }); new Toggle({ key: "ColoredPlanes", display: "Colored Planes", container: "#settingsRight", init: true, setState: function(state) { if (state) monochromeMarkers = null; else monochromeMarkers = "#EEEEEE"; refreshFeatures(); } }); new Toggle({ key: "ColoredTrails", display: "Colored Trails", container: "#settingsRight", init: true, setState: function(state) { if (state) monochromeTracks = null; else monochromeTracks = "#000000"; remakeTrails(); } }); new Toggle({ key: "sidebar_visible", display: "Sidebar visible", container: null, checkbox: null, button: '#toggle_sidebar_button', init: (onMobile ? false : true), setState: function (state) { if (state) { $("#sidebar_container").show(); $("#expand_sidebar_control").show(); $("#toggle_sidebar_button").removeClass("show_sidebar"); $("#toggle_sidebar_button").addClass("hide_sidebar"); } else { $("#sidebar_container").hide(); $("#expand_sidebar_control").hide(); $("#toggle_sidebar_button").removeClass("hide_sidebar"); $("#toggle_sidebar_button").addClass("show_sidebar"); } updateMapSize(); }, }); $('#selectall_checkbox').on('click', function() { if ($('#selectall_checkbox').hasClass('settingsCheckboxChecked')) { deselectAllPlanes(); } else { selectAllPlanes(); } }) // 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); TAR.altitudeChart.init(); if (adsbexchange) { $('#adsbexchange_header').show(); if (window.self != window.top) { window.top.location.href = "https://www.adsbexchange.com/" return; } } } function push_history() { $("#loader_progress").attr('max',nHistoryItems*2); for (let i = 0; i < nHistoryItems; i++) { push_history_item(i); } if (globeIndex) { parseHistory(); } else if (!nHistoryItems) { parseHistory(); 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) { parseHistory(); } }) .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) { parseHistory(); } }); } function parseHistory() { if (nHistoryItems) { console.timeEnd("Downloaded History"); console.time("Loaded aircraft tracks from History"); } for (let i in deferHistory) deferHistory[i] = null; 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(); TAR.planesTable.refresh(); } PositionHistoryBuffer = null; if (nHistoryItems) console.timeEnd("Loaded aircraft tracks from History"); historyLoaded.resolve(); } function startPage() { console.log("Completing init"); 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(); } if (hideButtons) { $('#large_mode_control').hide(); $('#header_top').hide(); $('#header_side').hide(); $('#splitter').hide(); $('#tabs').hide(); $('#filterButton').hide(); $('.ol-control').hide(); $('.ol-attribution').show(); } // 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(TAR.planesTable.refresh, 1000); //window.setInterval(function() {PendingFetches--;}, 10000); setInterval(decrementTraceRate, 1000); setInterval(updateIconCache, 800); 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 } loadFinished = true; if (tempTrails) selectAllPlanes(); geoMag = geoMagFactory(cof2Obj()); if (!heatmap) $("#loader").addClass("hidden"); refreshSelected(); refreshHighlighted(); } // // Utils begin // (function (global, $, TAR) { let utils = TAR.utils = TAR.utils || {}; // 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 utils.make_geodesic_circle = function (center, radius, points) { const angularDistance = radius / 6378137.0; const lon1 = center[0] * Math.PI / 180.0; const lat1 = center[1] * Math.PI / 180.0; let geom; for (let i = 0; i <= points; ++i) { const 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; } return TAR; }(window, jQuery, TAR || {})); // // Utils end // function webglInit() { try { const canvas = document.createElement('canvas'); let gl = canvas.getContext("webgl"); webglSupported = true; } catch (error) { console.error(error); console.log('disabling webGL support!'); webglSupported = false; } if (webglSupported) { new Toggle({ key: "webgl", display: "WebGL", container: "#settingsLeft", init: true, setState: function(state) { if (state) webgl = true; else webgl = false; }, }); let glStyle = { symbol: { symbolType: 'image', src: 'images/sprites001.png', size: [ 'get', 'size' ], offset: [0, 0], textureCoord: [ 'array', [ 'get', 'cx' ], [ 'get', 'cy' ], [ 'get', 'dx' ], [ 'get', 'dy' ] ], color: [ 'color', [ 'get', 'r' ], [ 'get', 'g' ], [ 'get', 'b' ], 1 ], rotateWithView: false, rotation: [ 'get', 'rotation' ], }, }; webglLayer = new ol.layer.WebGLPoints({ name: 'webglLayer', type: 'overlay', title: 'Aircraft pos. webGL', source: webglFeatures, declutter: false, zIndex: 200, renderBuffer: 20, style: glStyle, }); layers.push(webglLayer); } } // Initalizes the map and starts up our timers to call various functions function initMap() { 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']; } // Initialize OL3 layers_group = createBaseLayers(); layers = layers_group.getLayers(); 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); webglInit(); iconLayer = new ol.layer.Vector({ name: 'iconLayer', type: 'overlay', title: 'Aircraft positions', source: PlaneIconFeatures, declutter: false, zIndex: 200, renderBuffer: 20, }); 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 == webglLayer || 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', 'webglLayer'); }); new Toggle({ key: "MapDim", display: "Dim Map", container: "#settingsLeft", init: MapDim, setState: function(state) { if (!state) { ol.control.LayerSwitcher.forEachRecursive(layers_group, function(lyr) { if (lyr.get('type') != 'base') return; ol.Observable.unByKey(lyr.dimKey); }); } else { ol.control.LayerSwitcher.forEachRecursive(layers_group, function(lyr) { if (lyr.get('type') != 'base') return; lyr.dimKey = lyr.on('postrender', dim); }); } OLMap.render(); buttonActive('#B', state); } }); 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": toggles['MapDim'].toggle(); 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(); $('#tabs').hide(); $('#filterButton').hide(); $('.ol-control').hide(); $('.ol-attribution').show(); } else { $('#large_mode_control').show(); $('#header_top').show(); $('#header_side').show(); $('#splitter').show(); $('#tabs').show(); $('#filterButton').show(); $('.ol-control').show(); $('#expand_sidebar_control').hide(); toggles['sidebar_visible'].restore(); TAR.altitudeChart.render(); } hideButtons = !hideButtons; break; case "f": toggleFollow(); break; // filters case "M": onlyMLAT = !onlyMLAT; TAR.planesTable.refresh(); mapRefresh(); 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": toggles['lastLeg'].toggle(); 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 variable function reaper(all) { //console.log("Reaping started.."); today = new Date().getDate(); if (noVanish) return; // 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 (pTracks) return; 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() && selected.source != 'mlat') { $('#anon_mlat_info').addClass('hidden'); $('#reg_info').addClass('hidden'); $('#tisb_info').removeClass('hidden'); } else if (selected.isNonIcao() && selected.source == 'mlat') { $('#reg_info').addClass('hidden'); $('#tisb_info').addClass('hidden'); $('#anon_mlat_info').removeClass('hidden'); } else { $('#tisb_info').addClass('hidden'); $('#anon_mlat_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 = ""; if (new_html != selectedPhotoCache) { $('#selected_photo').html(new_html); selectedPhotoCache = new_html; } } else { $('#selected_photo').text(""); } $("#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 heading = null; 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; $('#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)); 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 = "Share"; icao_link = 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'); } 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"); if (globeIndex && binCraft && !showTrace) { $('#selected_message_count').prev().text('Receivers:'); $('#selected_message_count').prop('title', 'Number of receivers receiving this aircraft'); if (selected.receiverCount >= 5 && selected.dataSource != 'mlat') { $('#selected_message_count').text('> ' + selected.receiverCount); } else { $('#selected_message_count').text(selected.receiverCount); } } else { $('#selected_message_count').prev().text('Messages:'); $('#selected_message_count').prop('title', 'The total number of messages received from this aircraft'); $('#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-3"; break; case 1: silDesc = "≤ 1×10-3"; break; case 2: silDesc = "≤ 1×10-5"; break; case 3: silDesc = "≤ 1×10-7"; 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 removeHighlight() { HighlightedPlane = null; refreshHighlighted(); } function refreshFeatures() { for (let i in PlanesOrdered) { PlanesOrdered[i].updateTick(true); } } // // Planes table begin // (function (global, $, TAR) { let planesTable = TAR.planesTable = TAR.planesTable || {}; 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; } let cols = planesTable.cols = {}; cols.icao = { text: 'Hex id', sort: function () { sortBy('icao', compareAlpha, function(x) { return x.icao; }); }, value: function(plane) { return plane.icao; }, td: '', }; cols.flag = { text: 'Flag', header: function() { return ""; }, sort: function () { sortBy('country', compareAlpha, function(x) { return x.icaorange.country; }); }, value: function(plane) { return (plane.icaorange.flag_image ? ('') : ''); }, hStyle: 'style="width: 20px; padding: 3px;"', html: true, }; cols.flight = { sort: function () { sortBy('flight', compareAlpha, function(x) { return x.flight }); }, value: function(plane) { return (flightawareLinks ? getFlightAwareModeSLink(plane.icao, plane.flight, plane.name) : plane.name); }, html: flightawareLinks, text: 'Callsign' }; cols.registration = { sort: function () { sortBy('registration', compareAlpha, function(x) { return x.registration; }); }, value: function(plane) { return (flightawareLinks ? getFlightAwareIdentLink(plane.registration, plane.registration) : (plane.registration ? plane.registration : "")); }, html: flightawareLinks, text: 'Registration' }; cols.aircraft_type = { sort: function () { sortBy('type', compareAlpha, function(x) { return x.icaoType; }); }, value: function(plane) { return (plane.icaoType != null ? plane.icaoType : ""); }, text: 'Type' }; cols.squawk = { text: 'Squawk', sort: function () { sortBy('squawk', compareAlpha, function(x) { return x.squawk; }); }, value: function(plane) { return (plane.squawk != null ? plane.squawk : ""); }, align: 'right' }; cols.altitude = { text: 'Altitude', sort: function () { sortBy('altitude',compareNumeric, function(x) { return (x.altitude == "ground" ? -100000 : x.altitude); }); }, value: function(plane) { return format_altitude_brief(plane.altitude, plane.vert_rate, DisplayUnits); }, align: 'right', header: function () { return 'Altitude(' + get_unit_label("altitude", DisplayUnits) + ')';}, }; cols.speed = { text: 'Speed', sort: function () { sortBy('speed', compareNumeric, function(x) { return x.gs; }); }, value: function(plane) { return format_speed_brief(plane.gs, DisplayUnits); }, align: 'right', header: function () { return 'Spd(' + get_unit_label("speed", DisplayUnits) + ')';}, }; cols.vert_rate = { text: 'Vertical Rate', sort: function () { sortBy('vert_rate', compareNumeric, function(x) { return x.vert_rate; }); }, value: function(plane) { return format_vert_rate_brief(plane.vert_rate, DisplayUnits); }, align: 'right', header: function () { return 'V. Rate(' + get_unit_label("verticalRate", DisplayUnits) + ')';}, }; cols.distance = { text: 'Distance', sort: function () { sortBy('sitedist',compareNumeric, function(x) { return x.sitedist; }); }, value: function(plane) { return format_distance_brief(plane.sitedist, DisplayUnits); }, align: 'right', header: function () { return 'Dist.(' + get_unit_label("distance", DisplayUnits) + ')';}, }; cols.track = { text: 'Track', sort: function () { sortBy('track', compareNumeric, function(x) { return x.track; }); }, value: function(plane) { return format_track_brief(plane.track); }, align: 'right' }; cols.msgs = { text: 'Messages', sort: function () { sortBy('msgs', compareNumeric, function(x) { return x.messages; }); }, value: function(plane) { return plane.messages; }, align: 'right' }; cols.seen = { text: 'Seen', sort: function () { sortBy('seen', compareNumeric, function(x) { return x.seen; }); }, value: function(plane) { return plane.seen.toFixed(0); }, align: 'right' }; cols.rssi = { text: 'RSSI', sort: function () { sortBy('rssi', compareNumeric, function(x) { return x.rssi; }); }, value: function(plane) { return (plane.rssi != null ? plane.rssi.toFixed(1) : ""); }, align: 'right' }; cols.lat = { text: 'Latitude', sort: function () { sortBy('lat', compareNumeric, function(x) { return (x.position !== null ? x.position[1] : null); }); }, value: function(plane) { return (plane.position != null ? plane.position[1].toFixed(4) : ""); }, align: 'right' }; cols.lon = { text: 'Longitude', sort: function () { sortBy('lon', compareNumeric, function(x) { return (x.position !== null ? x.position[0] : null); }); }, value: function(plane) { return (plane.position != null ? plane.position[0].toFixed(4) : ""); }, align: 'right' }; cols.data_source = { text: 'Source', sort: function () { sortBy('data_source', compareNumeric, function(x) { return x.getDataSourceNumber() } ); }, value: function(plane) { return format_data_source(plane.getDataSource()); }, align: 'right' }; let colsEntries = Object.entries(cols); for (let i in colsEntries) { let key = colsEntries[i][0]; let value = colsEntries[i][1]; value.id = key; value.text = value.text ? value.text : ""; value.header = value.header ? value.header : function() { return value.text; }; value.hStyle = value.hStyle ? value.hStyle : ""; if (!value.td) value.td = value.align ? ('') : ''; } let columns = Object.values(cols); let activeCols = null; let initializing = true; let planeRowTemplate = null; let lastRealExtent = null; let htmlTable = null; let tbody = null; planesTable.init = function () { // initialize columns htmlTable = document.getElementById('planesTable'); for (let i in columns) { let col = columns[i]; col.visible = true; col.toggleKey = 'column_' + col.id; if (HideCols.includes('#' + col.id)) { planesTable.setColumnVis(col.id, false); } } createColumnToggles(); if (!ShowFlags) { planesTable.setColumnVis('flag', false); } planesTable.redraw(); initializing = false; } planesTable.redraw = function () { activeCols = []; for (let i in columns) { let col = columns[i]; if (col.visible || !mapIsVisible) { activeCols.push(col); } } for (let i = 0; i < PlanesOrdered.length; ++i) { PlanesOrdered[i].destroyTR(); } let table = ''; table += ''; table += ' '; for (let i in activeCols) { let col = activeCols[i]; table += ''+ col.header() +''; } table += ' '; table += ''; table += ''; table += ' '; for (let i in activeCols) { let col = activeCols[i]; table += col.td; table += ''; } table += ' '; table += ''; htmlTable.innerHTML = table; tbody = htmlTable.tBodies[0]; planeRowTemplate = document.getElementById('plane_row_template'); planesTable.refresh(); } planesTable.setColumnVis = function (col, visible) { cols[col].visible = visible; if (!initializing) planesTable.redraw(); } // Refreshes the larger table of all the planes planesTable.refresh = function () { if (initializing || pTracks) return; resortTable(); TrackedAircraft = 0; TrackedAircraftPositions = 0; TrackedHistorySize = 0; let nPlanes = 0; if (mapIsVisible || lastRealExtent === null) { lastRealExtent = myExtent(OLMap.getView().calculateExtent(OLMap.getSize())); } const sidebarVisible = toggles['sidebar_visible'].state; for (let i = 0; i < PlanesOrdered.length; ++i) { const plane = PlanesOrdered[i]; TrackedHistorySize += plane.history_size; plane.inView = !plane.isFiltered() && inView(plane.position, lastRealExtent); plane.showInTable = false; if (tableInView && plane.visible && (plane.inView || (plane.selected && !SelectedAllPlanes)) ) { plane.showInTable = true; TrackedAircraftPositions++; } 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; } let classes = "plane_table_row"; if (plane.showInTable) { nPlanes++; if (plane.tr == null) { plane.makeTR(planeRowTemplate.cloneNode(true), tbody); plane.tr.id = plane.icao; } 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; } for (let cell in activeCols) { let col = activeCols[cell]; if (!col.value) continue; let newValue = col.value(plane); if (newValue != plane.trCache[cell]) { plane.trCache[cell] = newValue; if (col.html) { plane.tr.cells[cell].innerHTML = newValue; } else { plane.tr.cells[cell].textContent = newValue; } } } } if (plane.tr && plane.classesCache != classes) { plane.classesCache = classes; plane.tr.className = classes; } } global.refreshPageTitle(); $('#dump1090_total_history').text(TrackedHistorySize); $('#dump1090_message_rate').text(MessageRate === null ? 'n/a' : MessageRate.toFixed(1)); $('#dump1090_total_ac').text(globeIndex ? globeTrackedAircraft : TrackedAircraft); $('#dump1090_total_ac_positions').text(TrackedAircraftPositions); 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; } } } // // ---- table sorting begin ---- // let sortId = ''; let sortCompare = null; let sortExtract = null; let sortAscending = true; let columnVis = Array(30).fill(true); function sortFunction(x,y) { const xv = x._sort_value; const 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; const c = sortAscending ? sortCompare(xv,yv) : sortCompare(yv,xv); if (c !== 0) return c; return x._sort_pos - y._sort_pos; } function resortTable() { if (!sortExtract) return; const pList = PlanesOrdered; if (globeIndex) { // don't presort for globeIndex } // presort by dataSource else 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) { return (x.position ? x.position[0] : 500) - (y.position ? y.position[0] : 500); }); } // 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. if (globeIndex) { for (let i = 0; i < pList.length; ++i) { pList[i]._sort_pos = pList[i].numHex; pList[i]._sort_value = sortExtract(pList[i]); } } else { 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; planesTable.refresh(); } // // ---- table sorting end ---- // function createColumnToggles() { let container = '#columns_block1'; for (let i in columns) { let col = columns[i]; if (col.id === 'distance') { container = '#columns_block2'; } new Toggle({ key: col.toggleKey, display: col.text, container: container, init: col.visible, setState: function (state) { planesTable.setColumnVis(col.id, state); } }); } } return TAR; }(window, jQuery, TAR || {})); // // Planes table end // 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(); TAR.planesTable.refresh(); } // 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(); TAR.planesTable.refresh(); } // 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(); TAR.planesTable.refresh(); 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() { if (OLMap) OLMap.updateSize(); } function expandSidebar(e) { e.preventDefault(); $("#map_container").hide() mapIsVisible = false; $("#toggle_sidebar_control").hide(); $("#splitter").hide(); $("#shrink_sidebar_button").show(); $("#sidebar_container").width("100%"); TAR.planesTable.redraw(); clearTimeout(refreshId); fetchData(); 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(); TAR.planesTable.redraw(); clearTimeout(refreshId); fetchData(); updateMapSize(); } 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'; } DisplayUnits = localStorage['displayUnits']; // Initialize drop-down $('#units_selector') .val(DisplayUnits) .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) { localStorage['displayUnits'] = DisplayUnits = e.target.value; TAR.altitudeChart.render(); // 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)); TAR.planesTable.redraw(); } 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(); TAR.planesTable.refresh(); mapRefresh(); 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 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'; } // // Altitude Chart begin // (function (global, $, TAR) { let altitudeChart = TAR.altitudeChart = TAR.altitudeChart || {}; function createLegendGradientStops() { const mapOffsetToAltitude = [[0.033, 500], [0.066, 1000], [0.126, 2000], [0.19, 4000], [0.253, 6000], [0.316, 8000], [0.38, 10000], [0.59, 20000], [0.79, 30000], [1, 40000]]; let stops = ''; for (let i in mapOffsetToAltitude) { let map = mapOffsetToAltitude[i]; const color = altitudeColor(map[1]); stops += ''; } return stops; } function createLegendUrl(data) { $(data).find('#linear-gradient').html(createLegendGradientStops()); const svg = $('svg', data).prop('outerHTML'); return 'url("data:image/svg+xml;base64,' + global.btoa(svg) + '")'; } function loadLegend() { let baseLegend = (DisplayUnits === 'metric') ? 'images/alt_legend_meters.svg' : 'images/alt_legend_feet.svg'; $.get(baseLegend, function (data) { $('#altitude_chart_button').css("background-image", createLegendUrl(data)); }); } altitudeChart.render = function () { if (toggles['altitudeChart'].state) { loadLegend(); $('#altitude_chart').show(); } else { $('#altitude_chart').hide(); } } altitudeChart.init = function () { new Toggle({ key: "altitudeChart", display: "Altitude Chart", container: "#settingsLeft", init: (onMobile ? false : true), setState: altitudeChart.render }); } return TAR; }(window, jQuery, TAR || {})); // // Altitude Chart end // 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; TAR.planesTable.refresh(); } localStorage['tableInView'] = tableInView; $('#with_positions').text(tableInView ? "On Screen:" : "With Position:"); buttonActive('#V', tableInView); } function toggleLabels() { 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 "" + linkText + ""; } return ""; } function getFlightAwareModeSLink(code, ident, linkText) { if (code !== null && code.length > 0 && code[0] !== '~' && code !== "000000") { if (!linkText) { linkText = "FlightAware: " + code.toUpperCase(); } let linkHtml = "" + linkText + ""; return linkHtml; } return ""; } function getPhotoLink(ac) { if (flightawareLinks) { if (ac.registration == null || ac.registration == "") return ""; return "See Photos"; } else { return "View on Planespotters"; } } // 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 (!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 > 2) checkRefresh(); 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 checkRefresh() { const center = ol.proj.toLonLat(OLMap.getView().getCenter()); const zoom = OLMap.getView().getZoom(); if (!triggerMapRefresh && ZoomLvl == zoom && center[0] == CenterLon && center[1] == CenterLat) return; //console.time("refreshTable"); refreshSelected(); refreshHighlighted(); TAR.planesTable.refresh(); mapRefresh(); //console.timeEnd("refreshTable"); triggerMapRefresh = false; changeZoom(); changeCenter(); } function mapRefresh() { //console.log('mapRefresh()'); let addToMap = []; let lastRenderExtent = null; let nMapPlanes = 0; const mapSize = OLMap.getSize() const size = [mapSize[0] * 1.2, mapSize[1] * 1.2]; lastRenderExtent = myExtent(OLMap.getView().calculateExtent(size)); if (globeIndex && !icaoFilter) { for (let i in PlanesOrdered) { const plane = PlanesOrdered[i]; // disable mobile limitations when using webGL if ( (!onMobile || webgl || nMapPlanes < 150) && (!onMobile || webgl || ZoomLvl > 10 || !plane.onGround) && !plane.isFiltered() && inView(plane.position, lastRenderExtent) ) { addToMap.push(plane); nMapPlanes++; } else if (plane.selected && !SelectedAllPlanes) { addToMap.push(plane); nMapPlanes++; } else if (plane.visible) { plane.clearMarker(); plane.clearLines(); plane.visible = false; } } } else { for (let i in PlanesOrdered) { addToMap.push(PlanesOrdered[i]); } } // webGL zIndex hack: // sort all planes by altitude // clear the vector source // delete all feature objects so they are recreated, this is important // draw order will be insertion / updateFeatures / updateTick order addToMap.sort(function(x, y) { return x.altSort - y.altSort; }); //console.log('table refresh: ' + addToMap.length); if (webgl) { webglFeatures.clear(); for (let i in addToMap) { delete addToMap[i].glMarker; } } if (globeIndex && !icaoFilter) { for (let i in addToMap) addToMap[i].updateFeatures(now, last); } else { for (let i in addToMap) { addToMap[i].updateTick(); } } } 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 * iconScale;; if (!init && showTrace) updateAddressBar(); if (ZoomLvl > 5.5 && enableMouseover) { OLMap.on('pointermove', onPointermove); } else { OLMap.un('pointermove', onPointermove); removeHighlight(); } } 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 || layer == webglLayer); }, hitTolerance: 5 * globalScale, } ); 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]]); //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; window.history && window.history.replaceState && window.history.replaceState("object or string", "Title", string); } function refreshInt() { let refresh = RefreshInterval; if (!globeIndex) { if (document[hidden]) return 3 * refresh; else return refresh; } if (document[hidden]) return 24 * 3600 * 1000; // hidden tab, don't refresh to avoid freeze when the tab is switched to again. if (adsbexchange && refresh < 2700) refresh = 2700; let inactive = getInactive(); if (inactive < 70) inactive = 70; if (inactive > 180) inactive = 180; refresh *= inactive / 70; if (!mapIsVisible) refresh *= 2; if (onMobile && TrackedAircraftPositions > 800) 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 * userScale; root.style.setProperty("--SCALE", globalScale); labelFont = "bold " + (12 * globalScale * labelScale) + "px/" + (14 * globalScale * labelScale) + "px Tahoma, Verdana, Helvetica, sans-serif"; localStorage['largeMode'] = largeMode; changeZoom("init"); setLineWidth(); refreshFeatures(); refreshSelected(); remakeTrails(); } function toggleShowTrace() { if (!showTrace) { toggleMultiSelect("off"); showTrace = true; toggleFollow(false); showTraceWasIsolation = onlySelected; toggleIsolation("on", null); shiftTrace(); } 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(); const hex = SelectedPlane.icao; SelectedPlane = null; showTraceExit = true; selectPlaneByHex(hex, {follow: true, zoom: ZoomLvl,}); } $('#history_collapse').toggle({ duration: 0, complete: function () { $('#show_trace').toggleClass('active'); } }); } 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() { // Set SitePosition if (SiteLat != null && SiteLon != null) { SitePosition = [SiteLon, SiteLat]; // Add home marker if requested createSiteCircleFeatures(); } else { TAR.planesTable.setColumnVis('distance', false); } if (SitePosition && !onMobile) { TAR.planesTable.cols.distance.sort(); } else { TAR.planesTable.cols.altitude.sort(); TAR.planesTable.cols.altitude.sort(); } } /* 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 = TAR.utils.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() { // 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 in 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() { 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; 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; } TAR.planesTable.refresh(); 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 getInactive() { return (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) { if (data.messages && uuid == null) { // Detect stats reset if (MessageCountHistory.length > 0 && MessageCountHistory[MessageCountHistory.length-1].messages > data.messages) { MessageCountHistory = []; } // Note the message count in the history MessageCountHistory.push({ 'time' : data.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) { let time_delta = now - last; 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; } } initialize();