// -*- mode: javascript; indent-tabs-mode: nil; c-basic-offset: 8 -*- "use strict"; // Define our global variables var OLMap = null; var StaticFeatures = new ol.Collection(); var SiteCircleFeatures = new ol.Collection(); var PlaneIconFeatures = new ol.Collection(); var PlaneTrailFeatures = new ol.Collection(); var Planes = {}; var PlanesOrdered = []; var PlaneFilter = {}; var SelectedPlane = null; var SelectedAllPlanes = false; var HighlightedPlane = null; var FollowSelected = false; var infoBoxOriginalPosition = {}; var customAltitudeColors = true; var myAdsbStatsSiteUrl = null; var ADSB_Enabled = true; var UAT_Enabled = false; var 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 var CenterLat, CenterLon, ZoomLvl, MapType, SiteCirclesCount, SiteCirclesBaseDistance, SiteCirclesInterval; var SkyAwareVersion = "unknown version"; var RefreshInterval = 1000; var PlaneRowTemplate = null; var TrackedAircraft = 0; var TrackedAircraftPositions = 0; var TrackedHistorySize = 0; var SitePosition = null; var LastReceiverTimestamp = 0; var StaleReceiverCount = 0; var FetchPending = null; var FetchPending_UAT = null; var MessageCountHistory = []; var MessageCountHistory_UAT = []; var MessageRate = 0; var UatMessageRate = 0; var NBSP='\u00a0'; var layers; var layerGroup; var ActiveFilterCount = 0; var altitude_slider = null; var speed_slider = null; var AircraftLabels = false; // piaware vs flightfeeder var isFlightFeeder = false; var checkbox_div_map = new Map ([ ['#icao_col_checkbox', '#icao'], ['#flag_col_checkbox', '#flag'], ['#ident_col_checkbox', '#flight'], ['#reg_col_checkbox', '#registration'], ['#ac_col_checkbox', '#aircraft_type'], ['#squawk_col_checkbox', '#squawk'], ['#alt_col_checkbox', '#altitude'], ['#speed_col_checkbox', '#speed'], ['#vrate_col_checkbox', '#vert_rate'], ['#distance_col_checkbox', '#distance'], ['#heading_col_checkbox', '#track'], ['#messages_col_checkbox', '#msgs'], ['#msg_age_col_checkbox', '#seen'], ['#rssi_col_checkbox', '#rssi'], ['#lat_col_checkbox', '#lat'], ['#lon_col_checkbox', '#lon'], ['#datasource_col_checkbox', '#data_source'], ['#airframes_col_checkbox', '#airframes_mode_s_link'], ['#fa_modes_link_checkbox', '#flightaware_mode_s_link'], ['#fa_photo_link_checkbox', '#flightaware_photo_link'], ]); var DefaultMinMaxFilters = { 'nautical': {min: 0, maxSpeed: 1000, maxAltitude: 65000}, // kt, ft 'metric' : {min: 0, maxSpeed: 1000, maxAltitude: 20000}, // km/h, m 'imperial' : {min: 0, maxSpeed: 600, maxAltitude: 65000} // mph, ft }; // Update Planes with data in aircraft json // receiver_source will specify where the aircraft.json originated from (dump1090-fa or skyaware978) function processReceiverUpdate(data, receiver_source) { // Loop through all the planes in the data packet var now = data.now; var acs = data.aircraft; if (receiver_source === "skyaware978") { // Detect stats reset (i.e. if MessageCountHistory array is > 0 and the latest value > the "messages" field in the data packet) if (MessageCountHistory_UAT.length > 0 && MessageCountHistory_UAT[MessageCountHistory_UAT.length-1].messages > data.messages) { MessageCountHistory_UAT = [{'time' : MessageCountHistory_UAT[MessageCountHistory_UAT.length-1].time, 'messages' : 0}]; } // Maintain a 30 second rollinng history of message counts MessageCountHistory_UAT.push({ 'time' : now, 'messages' : data.messages}); // .. and clean up any old values if ((now - MessageCountHistory_UAT[0].time) > 30) MessageCountHistory_UAT.shift(); } else { // Detect stats reset (i.e. if MessageCountHistory array is > 0 and the latest value > the "messages" field in the data packet) if (MessageCountHistory.length > 0 && MessageCountHistory[MessageCountHistory.length-1].messages > data.messages) { MessageCountHistory = [{'time' : MessageCountHistory[MessageCountHistory.length-1].time, 'messages' : 0}]; } // Maintain a 30 second rollinng history of message counts MessageCountHistory.push({ 'time' : now, 'messages' : data.messages}); // .. and clean up any old values if ((now - MessageCountHistory[0].time) > 30) MessageCountHistory.shift(); } for (var j=0; j < acs.length; j++) { var ac = acs[j]; var hex = ac.hex; var squawk = ac.squawk; var plane = null; // Do we already have this plane object in Planes? // If not make it. if (Planes[hex]) { plane = Planes[hex]; } else { plane = new PlaneObject(hex); plane.filter = PlaneFilter; plane.tr = PlaneRowTemplate.cloneNode(true); if (hex[0] === '~') { // Non-ICAO address plane.tr.cells[0].textContent = hex.substring(1); $(plane.tr).css('font-style', 'italic'); } else { plane.tr.cells[0].textContent = hex; } // set flag image if available if (ShowFlags && plane.icaorange.flag_image !== null) { $('img', plane.tr.cells[1]).attr('src', FlagPath + plane.icaorange.flag_image); $('img', plane.tr.cells[1]).attr('title', plane.icaorange.country); } else { $('img', plane.tr.cells[1]).css('display', 'none'); } plane.tr.addEventListener('click', function(h, evt) { if (evt.srcElement instanceof HTMLAnchorElement) { evt.stopPropagation(); return; } if (!$("#map_container").is(":visible")) { showMap(); } selectPlaneByHex(h, false); adjustSelectedInfoBlockPosition(); evt.preventDefault(); }.bind(undefined, hex)); plane.tr.addEventListener('dblclick', function(h, evt) { if (!$("#map_container").is(":visible")) { showMap(); } selectPlaneByHex(h, true); adjustSelectedInfoBlockPosition(); evt.preventDefault(); }.bind(undefined, hex)); Planes[hex] = plane; PlanesOrdered.push(plane); } // Call the function update plane.updateData(now, ac, receiver_source); } } function fetchData() { if (ADSB_Enabled) { if (FetchPending !== null && FetchPending.state() == 'pending') { // don't double up on fetches, let the last one resolve return; } FetchPending = $.ajax({ url: 'data/aircraft.json', timeout: 5000, cache: false, dataType: 'json' }); FetchPending.done(function(data) { process_aircraft_json(data, 'dump1090-fa'); }); FetchPending.fail(function(jqxhr, status, error) { $("#update_error_detail").text("AJAX call failed (" + status + (error ? (": " + error) : "") + "). Maybe dump1090 is no longer running?"); $("#update_error").css('display','block'); }); } // Fetch UAT if enabled if (UAT_Enabled) { if (FetchPending_UAT !== null && FetchPending_UAT.state() == 'pending') { // don't double up on fetches, let the last one resolve return; } FetchPending_UAT = $.ajax({ url: 'data-978/aircraft.json', timeout: 5000, cache: false, dataType: 'json' }); FetchPending_UAT.done(function(data) { // Process UAT aircraft.json here process_aircraft_json(data, 'skyaware978'); }); FetchPending_UAT.fail(function(jqxhr, status, error) { $("#uat_update_error_detail").text("AJAX call failed (" + status + (error ? (": " + error) : "") + "). Maybe skyaware978 is no longer running?"); $("#uat_update_error").css('display','block'); }); } } // Process an aircraft.json and update Planes. // receiver_source will specify where the aircraft.json originated from (dump1090-fa or skyaware978) function process_aircraft_json(data, receiver_source) { var now = data.now; processReceiverUpdate(data, receiver_source); // update timestamps, visibility, history track for all planes - not only those updated for (var i = 0; i < PlanesOrdered.length; ++i) { var plane = PlanesOrdered[i]; plane.updateTick(now, LastReceiverTimestamp); } selectNewPlanes(); refreshTableInfo(); refreshSelected(); refreshHighlighted(); // Check for stale receiver data if (LastReceiverTimestamp === now) { StaleReceiverCount++; if (StaleReceiverCount > 5) { $("#update_error_detail").text("The data from dump1090 hasn't been updated in a while. Maybe dump1090 is no longer running?"); $("#update_error").css('display','block'); } } else { StaleReceiverCount = 0; LastReceiverTimestamp = now; $("#update_error").css('display','none'); } } var PositionHistorySize = 0; var UatPositionHistorySize = 0; function initialize() { // Set page basics document.title = PageName; flightFeederCheck(); setStatsLink(); PlaneRowTemplate = document.getElementById("plane_row_template"); refreshClock(); $("#loader").removeClass("hidden"); if (ExtendedData || window.location.hash == '#extended') { $("#extendedData").removeClass("hidden"); } // Set up map/sidebar splitter $("#sidebar_container").resizable({ handles: { w: '#splitter' }, minWidth: 350 }); // Set up datablock splitter $('#selected_infoblock').resizable({ handles: { s: '#splitter-infoblock' }, containment: "#sidebar_container", minHeight: 50 }); $('#close-button').on('click', function() { if (SelectedPlane !== null) { var selectedPlane = Planes[SelectedPlane]; SelectedPlane = null; selectedPlane.selected = null; selectedPlane.clearLines(); selectedPlane.updateMarker(); refreshSelected(); refreshHighlighted(); $('#selected_infoblock').hide(); } }); // 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() { var topCalc = ($(window).height() - $('#selected_infoblock').height() - 60); // 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() - 60) +'px'); } $('#selected_infoblock').css('top', topCalc + 'px'); }); // to make the infoblock responsive $('#sidebar_container').on('resize', function() { if ($('#sidebar_container').width() < 500) { $('#selected_infoblock').addClass('infoblock-container-small'); } else { $('#selected_infoblock').removeClass('infoblock-container-small'); } }); // Set up event handlers for buttons $("#toggle_sidebar_button").click(toggleSidebarVisibility); $("#expand_sidebar_button").click(expandSidebar); $("#show_map_button").click(showMap); // Set initial element visibility $("#show_map_button").hide(); $("#range_ring_column").hide(); setColumnVisibility(); // Initialize other controls initializeUnitsSelector(); // check if the altitude color values are default to enable the altitude filter if (ColorByAlt.air.h.length === 3 && ColorByAlt.air.h[0].alt === 2000 && ColorByAlt.air.h[0].val === 20 && ColorByAlt.air.h[1].alt === 10000 && ColorByAlt.air.h[1].val === 140 && ColorByAlt.air.h[2].alt === 40000 && ColorByAlt.air.h[2].val === 300) { customAltitudeColors = false; } create_filter_sliders(); $("#aircraft_type_filter_form").submit(onFilterByAircraftType); $("#aircraft_type_filter_reset_button").click(onResetAircraftTypeFilter); $("#aircraft_ident_filter_form").submit(onFilterByAircraftIdent); $("#aircraft_ident_filter_reset_button").click(onResetAircraftIdentFilter); $('#settingsCog').on('click', function() { $('#settings_infoblock').toggle(); }); $('#settings_close').on('click', function() { $('#settings_infoblock').hide(); }); $('#groundvehicle_filter').on('click', function() { filterGroundVehicles(true); refreshSelected(); refreshHighlighted(); refreshTableInfo(); }); $('#blockedmlat_filter').on('click', function() { filterBlockedMLAT(true); refreshSelected(); refreshHighlighted(); refreshTableInfo(); }); $('#grouptype_checkbox').on('click', function() { toggleGroupByDataType(true); }); $('#aircraft_label_checkbox').on('click', function() { toggleAircraftLabels(true); }); $('#altitude_checkbox').on('click', function() { toggleAltitudeChart(true); }); $('#selectall_checkbox').on('click', function() { toggleAllPlanes(true); }) $('#select_all_column_checkbox').on('click', function() { toggleAllColumns(true); }) $('#adsb_datasource_checkbox').on('click', function() { toggleADSBAircraft(true); refreshDataSourceFilters(); }) $('#uat_datasource_checkbox').on('click', function() { toggleUATAircraft(true); refreshDataSourceFilters(); }) $('#mlat_datasource_checkbox').on('click', function() { toggleMLATAircraft(true); refreshDataSourceFilters(); }) $('#other_datasource_checkbox').on('click', function() { toggleOtherAircraft(true); refreshDataSourceFilters(); }) $('#tisb_datasource_checkbox').on('click', function() { toggleTISBAircraft(true); refreshDataSourceFilters(); }) $('#column_select_button').on('click', function() { this.classList.toggle("config_button_active"); $('#column_select_panel').toggle(); }); $('#filter_button').on('click', function() { this.classList.toggle("config_button_active"); $('#filter_panel').toggle(); }); $('#stats_page_button').on('click', function() { if (myAdsbStatsSiteUrl) { window.open(myAdsbStatsSiteUrl); } }); // Event handlers for to column checkboxes checkbox_div_map.forEach(function (checkbox, div) { $(div).on('click', function() { toggleColumn(checkbox, div, true); }); }); // Force map to redraw if sidebar container is resized - use a timer to debounce var mapResizeTimeout; $("#sidebar_container").on("resize", function() { clearTimeout(mapResizeTimeout); mapResizeTimeout = setTimeout(updateMapSize, 10); }); // Initialize settings from local storage filterGroundVehicles(false); filterBlockedMLAT(false); toggleAltitudeChart(false); toggleAllPlanes(false); toggleGroupByDataType(false); toggleAircraftLabels(false); toggleAllColumns(false); toggleADSBAircraft(false); toggleUATAircraft(false); toggleMLATAircraft(false); toggleOtherAircraft(false); toggleTISBAircraft(false); refreshDataSourceFilters(); // Get 978 receiver metadata if present $.ajax({ url: 'data-978/receiver.json', timeout: 5000, cache: false, dataType: 'json' }) .done(function(data) { console.log('SkyAware978 enabled') UAT_Enabled = true; UatPositionHistorySize = data.history; }) .fail(function(data) { console.warn('Error reading SkyAware978 receiver.json. SkyAware978 may be disabled') UAT_Enabled = false; }); // Get receiver metadata, reconfigure using it, then continue // with initialization $.ajax({ url: 'data/receiver.json', timeout: 5000, cache: false, dataType: 'json' }) .done(function(data) { console.log('dump1090-fa enabled'); ADSB_Enabled = true; if (typeof data.lat !== "undefined") { SiteShow = true; SiteLat = data.lat; SiteLon = data.lon; DefaultCenterLat = data.lat; DefaultCenterLon = data.lon; } SkyAwareVersion = data.version; RefreshInterval = data.refresh; PositionHistorySize = data.history; }) .fail(function(data) { console.warn('Error reading dump1090-fa receiver.json. dump1090-fa may be disabled'); ADSB_Enabled = false; }) .always(function() { initialize_map(); start_load_history(); }); } function create_filter_sliders() { var maxAltitude = DefaultMinMaxFilters[DisplayUnits].maxAltitude; var minAltitude = DefaultMinMaxFilters[DisplayUnits].min; var maxSpeed = DefaultMinMaxFilters[DisplayUnits].maxSpeed; var minSpeed = DefaultMinMaxFilters[DisplayUnits].min; altitude_slider = document.getElementById('altitude_slider'); noUiSlider.create(altitude_slider, { start: [minAltitude, maxAltitude], connect: true, range: { 'min': minAltitude, 'max': maxAltitude }, step: 25, format: { to: (v) => parseFloat(v).toFixed(0), from: (v) => parseFloat(v).toFixed(0) } }); // Change text to reflect slider values var minAltitudeInput = document.getElementById('minAltitudeText'), maxAltitudeInput = document.getElementById('maxAltitudeText'); altitude_slider.noUiSlider.on('update', function (values, handle) { if (handle) { maxAltitudeInput.innerHTML = values[handle]; } else { minAltitudeInput.innerHTML = values[handle]; } }); // 'Set' event - Whenever a slider is changed to a new value, this event is fired. This function will trigger every time a slider stops changing, including after calls to the .set() method. This event can be considered as the 'end of slide'. altitude_slider.noUiSlider.on('set', function (values, handle) { onFilterByAltitude(); }); speed_slider = document.getElementById('speed_slider'); noUiSlider.create(speed_slider, { start: [minSpeed, maxSpeed], connect: true, range: { 'min': minSpeed, 'max': maxSpeed }, step: 5, format: { to: (v) => parseFloat(v).toFixed(0), from: (v) => parseFloat(v).toFixed(0) } }); // Change text to reflect slider values var minSpeedInput = document.getElementById('minSpeedText'), maxSpeedInput = document.getElementById('maxSpeedText'); speed_slider.noUiSlider.on('update', function (values, handle) { if (handle) { maxSpeedInput.innerHTML = values[handle]; } else { minSpeedInput.innerHTML = values[handle]; } }); // 'Set' event - Whenever a slider is changed to a new value, this event is fired. This function will trigger every time a slider stops changing, including after calls to the .set() method. This event can be considered as the 'end of slide'. speed_slider.noUiSlider.on('set', function (values, handle) { onFilterBySpeed(); }); } function reset_filter_sliders() { var maxAltitude = DefaultMinMaxFilters[DisplayUnits].maxAltitude; var minAltitude = DefaultMinMaxFilters[DisplayUnits].min; var maxSpeed = DefaultMinMaxFilters[DisplayUnits].maxSpeed; var minSpeed = DefaultMinMaxFilters[DisplayUnits].min; altitude_slider.noUiSlider.updateOptions({ start: [minAltitude, maxAltitude], range: { 'min': minAltitude, 'max': maxAltitude } }); speed_slider.noUiSlider.updateOptions({ start: [minSpeed, maxSpeed], range: { 'min': minSpeed, 'max': maxSpeed } }); // Update filters updatePlaneFilter(); } var CurrentHistoryFetch = 0; var PositionHistoryBuffer = []; var HistoryItemsReturned = 0; var TotalPositionHistorySize = 0; function start_load_history() { let url = new URL(window.location.href); let params = new URLSearchParams(url.search); // Get total number of history.json files to load TotalPositionHistorySize = PositionHistorySize + UatPositionHistorySize; if (TotalPositionHistorySize > 0 && params.get('nohistory') !== 'true') { $("#loader_progress").attr('max', TotalPositionHistorySize); console.log("Starting to load history (" + TotalPositionHistorySize + " items)"); // Load dump1090 history.json files for (var i = 0; i < PositionHistorySize; i++) { load_history_item(i, 'data'); CurrentHistoryFetch++; } // Load skyaware978 history.json files for (var i = 0; i < UatPositionHistorySize; i++) { load_history_item(i, 'data-978'); CurrentHistoryFetch++; } } else { // Nothing to load end_load_history(); } } // Loads a history json file function load_history_item(i, source) { var historyfile = 'history_' + i + '.json'; console.log('Loading ' + source + ' ' + historyfile); $("#loader_progress").attr('value', CurrentHistoryFetch); var receiver_source = (source == "data-978" ? "skyaware978" : "dump1090-fa"); $.ajax({ url: source + '/' + historyfile, timeout: 5000, cache: false, dataType: 'json' }) .done(function(data) { // Tag history.json files with the source we fetched from (/data or /data-978) data["source"] = receiver_source; PositionHistoryBuffer.push(data); HistoryItemsReturned++; if (HistoryItemsReturned == TotalPositionHistorySize) { // End load history when all files have been loaded end_load_history(); } }) .fail(function(jqxhr, status, error) { //Doesn't matter if it failed, we'll just be missing a data point HistoryItemsReturned++; if (HistoryItemsReturned == TotalPositionHistorySize) { // End load history when all files have been loaded end_load_history(); } }); } function end_load_history() { $("#loader").addClass("hidden"); console.log("Done loading history"); if (PositionHistoryBuffer.length > 0) { var now, last=0; // Sort history by timestamp console.log("Sorting history"); PositionHistoryBuffer.sort(function(x,y) { return (x.now - y.now); }); // Process history for (var h = 0; h < PositionHistoryBuffer.length; ++h) { now = PositionHistoryBuffer[h].now; console.log("Applying history " + (h + 1) + "/" + PositionHistoryBuffer.length + " at: " + now); processReceiverUpdate(PositionHistoryBuffer[h], PositionHistoryBuffer[h].source); // Update track console.log("Updating tracks at: " + now); for (var i = 0; i < PlanesOrdered.length; ++i) { var plane = PlanesOrdered[i]; plane.updateTrack(now, last); } last = now; } // Final pass to update all planes to their latest state console.log("Final history cleanup pass"); for (var i = 0; i < PlanesOrdered.length; ++i) { var plane = PlanesOrdered[i]; plane.updateTick(now); } LastReceiverTimestamp = last; } PositionHistoryBuffer = null; console.log("Completing init"); refreshTableInfo(); refreshSelected(); refreshHighlighted(); reaper(); // Setup our timer to poll from the server. window.setInterval(fetchData, RefreshInterval); window.setInterval(reaper, 60000); // And kick off one refresh immediately. fetchData(); // update the display layout from any URL query strings applyUrlQueryStrings(); } // Function to apply any URL query value to the map before we start function applyUrlQueryStrings() { // if asked, toggle featrues at start let url = new URL(window.location.href); let params = new URLSearchParams(url.search); // be sure we start with a 'clean' layout, but only if we need it var allOptions = [ 'banner', 'altitudeChart', 'aircraftTrails', 'map', 'sidebar', 'zoomOut', 'zoomIn', 'moveNorth', 'moveSouth', 'moveWest', 'moveEast', 'displayUnits', 'rangeRings', 'ringCount', 'ringBaseDistance', 'ringInterval' ] var needReset = false; for (var option of allOptions) { if (params.has(option)) { needReset = true; break; } } if (needReset) { resetMap(); } if (params.get('banner') === 'hide') { hideBanner(); } if (params.get('altitudeChart') === 'hide') { $('#altitude_checkbox').removeClass('settingsCheckboxChecked'); $('#altitude_chart').hide(); } if (params.get('altitudeChart') === 'show') { $('#altitude_checkbox').addClass('settingsCheckboxChecked'); $('#altitude_chart').show(); } if (params.get('aircraftTrails') === 'show') { selectAllPlanes(); } if (params.get('aircraftTrails') === 'hide') { deselectAllPlanes(); } if (params.get('map') === 'show') { showMap(); } if (params.get('map') === 'hide') { expandSidebar(); } if (params.get('sidebar') === 'show') { $("#sidebar_container").show(); updateMapSize(); } if (params.get('sidebar') === 'hide') { $("#sidebar_container").hide(); updateMapSize(); } if (params.get('zoomOut')) { zoomMap(params.get('zoomOut'), true); } if (params.get('zoomIn')) { zoomMap(params.get('zoomIn'), false); } if (params.get('moveNorth')) { moveMap(params.get('moveNorth'), true, false); } if (params.get('moveSouth')) { moveMap(params.get('moveSouth'), true, true); } if (params.get('moveEast')) { moveMap(params.get('moveEast'), false, false); } if (params.get('moveWest')) { moveMap(params.get('moveWest'), false, true); } if (params.get('displayUnits')) { setDisplayUnits(params.get('displayUnits')); } if (params.get('rangeRings')) { setRangeRingVisibility(params.get('rangeRings')); } if (params.get('ringCount')) { setRingCount(params.get('ringCount')); } if (params.get('ringBaseDistance')) { setRingBaseDistance(params.get('ringBaseDistance')); } if (params.get('ringInterval')) { setRingInterval(params.get('ringInterval')); } } // Make a LineString with 'points'-number points // that is a closed circle on the sphere such that the // great circle distance from 'center' to each point is // 'radius' meters function make_geodesic_circle(center, radius, points) { var angularDistance = radius / 6378137.0; var lon1 = center[0] * Math.PI / 180.0; var lat1 = center[1] * Math.PI / 180.0; var geom; for (var i = 0; i <= points; ++i) { var bearing = i * 2 * Math.PI / points; var lat2 = Math.asin( Math.sin(lat1)*Math.cos(angularDistance) + Math.cos(lat1)*Math.sin(angularDistance)*Math.cos(bearing) ); var lon2 = lon1 + Math.atan2(Math.sin(bearing)*Math.sin(angularDistance)*Math.cos(lat1), Math.cos(angularDistance)-Math.sin(lat1)*Math.sin(lat2)); lat2 = lat2 * 180.0 / Math.PI; lon2 = lon2 * 180.0 / Math.PI; if (!geom) { geom = new ol.geom.LineString([[lon2, lat2]]); } else { geom.appendCoordinate([lon2, lat2]); } } return geom; } // Initalizes the map and starts up our timers to call various functions function initialize_map() { // Load stored map settings if present CenterLat = Number(localStorage['CenterLat']) || DefaultCenterLat; CenterLon = Number(localStorage['CenterLon']) || DefaultCenterLon; ZoomLvl = Number(localStorage['ZoomLvl']) || DefaultZoomLvl; MapType = localStorage['MapType']; var groupByDataTypeBox = localStorage.getItem('groupByDataType'); // Set SitePosition, initialize sorting if (SiteShow && (typeof SiteLat !== 'undefined') && (typeof SiteLon !== 'undefined')) { SitePosition = [SiteLon, SiteLat]; if (groupByDataTypeBox === 'deselected') { sortByDistance(); } } else { SitePosition = null; PlaneRowTemplate.cells[9].style.display = 'none'; // hide distance column document.getElementById("distance").style.display = 'none'; // hide distance header if (groupByDataTypeBox === 'deselected') { sortByAltitude(); } } // Maybe hide flag info if (!ShowFlags) { PlaneRowTemplate.cells[1].style.display = 'none'; // hide flag column document.getElementById("flag").style.display = 'none'; // hide flag header document.getElementById("infoblock_country").style.display = 'none'; // hide country row } // Initialize OL3 layers = createBaseLayers(); var iconsLayer = new ol.layer.Vector({ name: 'ac_positions', type: 'overlay', title: 'Aircraft positions', source: new ol.source.Vector({ features: PlaneIconFeatures, }) }); layers.push(new ol.layer.Group({ title: 'Overlays', layers: [ new ol.layer.Vector({ name: 'site_pos', type: 'overlay', title: 'Site position and range rings', source: new ol.source.Vector({ features: StaticFeatures, }) }), new ol.layer.Vector({ name: 'ac_trail', type: 'overlay', title: 'Selected aircraft trail', source: new ol.source.Vector({ features: PlaneTrailFeatures, }) }), iconsLayer ] })); var foundType = false; var baseCount = 0; layerGroup = new ol.layer.Group({ layers: layers }) ol.control.LayerSwitcher.forEachRecursive(layerGroup, function(lyr) { if (!lyr.get('name')) return; if (lyr.get('type') === 'base') { baseCount++; if (MapType === lyr.get('name')) { foundType = true; lyr.setVisible(true); } else { lyr.setVisible(false); } lyr.on('change:visible', function(evt) { if (evt.target.getVisible()) { MapType = localStorage['MapType'] = evt.target.get('name'); createSiteCircleFeatures(); } }); } else if (lyr.get('type') === 'overlay') { var 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(layerGroup, 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 }), controls: [new ol.control.Zoom(), new ol.control.Rotate(), new ol.control.Attribution({collapsed: true}), new ol.control.ScaleLine({units: DisplayUnits}) ], loadTilesWhileAnimating: true, loadTilesWhileInteracting: true }); if (baseCount > 1) { OLMap.addControl(new ol.control.LayerSwitcher()); } // Listeners for newly created Map OLMap.getView().on('change:center', function(event) { var center = ol.proj.toLonLat(OLMap.getView().getCenter(), OLMap.getView().getProjection()); localStorage['CenterLon'] = center[0] localStorage['CenterLat'] = center[1] if (FollowSelected) { // On manual navigation, disable follow var selected = Planes[SelectedPlane]; if (typeof selected === 'undefined' || (Math.abs(center[0] - selected.position[0]) > 0.0001 && Math.abs(center[1] - selected.position[1]) > 0.0001)){ FollowSelected = false; refreshSelected(); refreshHighlighted(); } } }); OLMap.getView().on('change:resolution', function(event) { ZoomLvl = localStorage['ZoomLvl'] = OLMap.getView().getZoom(); for (var plane in Planes) { Planes[plane].updateMarker(false); }; }); OLMap.on(['click', 'dblclick'], function(evt) { var hex = evt.map.forEachFeatureAtPixel(evt.pixel, function(feature, layer) { return feature.hex; }, { layerFilter: function(layer) { return (layer === iconsLayer); }, hitTolerance: 5, }); if (hex) { selectPlaneByHex(hex, (evt.type === 'dblclick')); adjustSelectedInfoBlockPosition(); evt.stopPropagation(); } else { deselectAllPlanes(); evt.stopPropagation(); } }); // show the hover box OLMap.on('pointermove', function(evt) { var hex = evt.map.forEachFeatureAtPixel(evt.pixel, function(feature, layer) { return feature.hex; }, { layerFilter: function(layer) { return (layer === iconsLayer); }, hitTolerance: 5, } ); if (hex) { highlightPlaneByHex(hex); } else { removeHighlight(); } }) // handle the layer settings pane checkboxes OLMap.once('postrender', function(e) { toggleLayer('#nexrad_checkbox', 'nexrad'); toggleLayer('#sitepos_checkbox', 'site_pos'); toggleLayer('#actrail_checkbox', 'ac_trail'); toggleLayer('#acpositions_checkbox', 'ac_positions'); }); // Add home marker if requested if (SitePosition) { var markerStyle = new ol.style.Style({ image: new ol.style.Circle({ radius: 7, snapToPixel: false, fill: new ol.style.Fill({color: 'black'}), stroke: new ol.style.Stroke({ color: 'white', width: 2 }) }) }); var feature = new ol.Feature(new ol.geom.Point(ol.proj.fromLonLat(SitePosition))); feature.setStyle(markerStyle); StaticFeatures.push(feature); $('#range_ring_column').show(); setRangeRings(); $('#range_rings_button').click(onSetRangeRings); $("#range_ring_form").validate({ errorPlacement: function(error, element) { return true; }, rules: { ringCount: { number: true, min: 0 }, baseRing: { number: true, min: 0 }, ringInterval: { number: true, min: 0 } } }); if (SiteCircles) { createSiteCircleFeatures(); } } // Add terrain-limit rings. To enable this: // // create a panorama for your receiver location on heywhatsthat.com // // note the "view" value from the URL at the top of the panorama // i.e. the XXXX in http://www.heywhatsthat.com/?view=XXXX // // fetch a json file from the API for the altitudes you want to see: // // wget -O /usr/share/dump1090-mutability/html/upintheair.json \ // 'http://www.heywhatsthat.com/api/upintheair.json?id=XXXX&refraction=0.25&alts=3048,9144' // // NB: altitudes are in _meters_, you can specify a list of altitudes // kick off an ajax request that will add the rings when it's done var request = $.ajax({ url: 'upintheair.json', timeout: 5000, cache: true, dataType: 'json' }); request.done(function(data) { for (var i = 0; i < data.rings.length; ++i) { var geom = new ol.geom.LineString([]); var points = data.rings[i].points; if (points.length > 0) { for (var j = 0; j < points.length; ++j) { geom.appendCoordinate([ points[j][1], points[j][0] ]); } geom.appendCoordinate([ points[0][1], points[0][0] ]); geom.transform('EPSG:4326', 'EPSG:3857'); var feature = new ol.Feature(geom); feature.setStyle(ringStyleForAlt(data.rings[i].alt)); StaticFeatures.push(feature); } } }); request.fail(function(jqxhr, status, error) { // no rings available, do nothing }); } function ringStyleForAlt(altitude) { return new ol.style.Style({ fill: null, stroke: new ol.style.Stroke({ color: PlaneObject.prototype.hslRepr(PlaneObject.prototype.getAltitudeColor(altitude*3.281)), // converting from m to ft width: 1 }) }); } function createSiteCircleFeatures() { const darkMaps = ['carto_dark_nolabels', 'carto_dark_all', 'esri_satellite']; if (darkMaps.includes(MapType)) { var SiteCircleColor = '#FFFFFF' } else { var SiteCircleColor = '#000000' } // Clear existing circles first SiteCircleFeatures.forEach(function(circleFeature) { StaticFeatures.remove(circleFeature); }); SiteCircleFeatures.clear(); var circleStyle = function(distance) { return new ol.style.Style({ fill: null, stroke: new ol.style.Stroke({ color: SiteCircleColor, width: 1 }), text: new ol.style.Text({ font: '10px Helvetica Neue, sans-serif', fill: new ol.style.Fill({ color: SiteCircleColor }), offsetY: -8, text: format_distance_long(distance, DisplayUnits, 0) }) }); }; var conversionFactor = 1000.0; if (DisplayUnits === "nautical") { conversionFactor = 1852.0; } else if (DisplayUnits === "imperial") { conversionFactor = 1609.0; } for (var i=0; i < SiteCirclesCount; ++i) { var distance = (SiteCirclesBaseDistance + (SiteCirclesInterval * i)) * conversionFactor; var circle = make_geodesic_circle(SitePosition, distance, 360); circle.transform('EPSG:4326', 'EPSG:3857'); var feature = new ol.Feature(circle); feature.setStyle(circleStyle(distance)); StaticFeatures.push(feature); SiteCircleFeatures.push(feature); } } // This looks for planes to reap out of the master Planes variable function reaper() { //console.log("Reaping started.."); // Look for planes where we have seen no messages for >300 seconds var newPlanes = []; for (var i = 0; i < PlanesOrdered.length; ++i) { var plane = PlanesOrdered[i]; if (plane.seen > 300) { // Reap it. plane.tr.parentNode.removeChild(plane.tr); plane.tr = null; delete Planes[plane.icao]; plane.destroy(); } else { // Keep it. newPlanes.push(plane); } }; PlanesOrdered = newPlanes; refreshTableInfo(); refreshSelected(); refreshHighlighted(); } // Page Title update function function refreshPageTitle() { if (!PlaneCountInTitle && !MessageRateInTitle) { document.title = PageName; return; } var aircraftCount = ""; var rate = ""; if (PlaneCountInTitle) { aircraftCount += TrackedAircraft; } if (MessageRateInTitle && MessageRate) { rate += ' - ' + MessageRate.toFixed(1) + ' msg/sec'; } document.title = '(' + aircraftCount + ') ' + PageName + rate; } // Refresh the detail window about the plane function refreshSelected() { updateMessageRates(); refreshPageTitle(); var selected = false; if (typeof SelectedPlane !== 'undefined' && SelectedPlane != "ICAO" && SelectedPlane != null) { selected = Planes[SelectedPlane]; } $('#dump1090_infoblock').css('display','block'); $('#skyaware_version').text('SkyAware ' + SkyAwareVersion); $('#dump1090_total_ac').text(TrackedAircraft); $('#dump1090_total_ac_positions').text(TrackedAircraftPositions); $('#dump1090_total_history').text(TrackedHistorySize); $('#active_filter_count').text(ActiveFilterCount); if (ADSB_Enabled) { $('#adsb_datasource_checkbox, #adsb_datasource_label').show(); $('#adsb_message_rate_row').show(); if (MessageRate !== null) { $('#dump1090_message_rate').text(MessageRate.toFixed(1) + '/sec'); } } else { $('#adsb_datasource_checkbox, #adsb_datasource_label').hide(); $('#adsb_message_rate_row').hide(); } if (UAT_Enabled) { $('#uat_datasource_checkbox, #uat_datasource_label').show(); $('#uat_message_rate_row').show(); if (UatMessageRate !== null) { $('#uat_message_rate').text(UatMessageRate.toFixed(1) + '/sec'); } } else { $('#uat_datasource_checkbox, #uat_datasource_label').hide(); $('#uat_message_rate_row').hide(); } setSelectedInfoBlockVisibility(); if (!selected) { return; } if (selected.flight !== null && selected.flight !== "") { $('#selected_callsign').text(selected.flight); } else { $('#selected_callsign').text('n/a'); } $('#selected_flightaware_link').html(getFlightAwareModeSLink(selected.icao, selected.flight, "Visit Flight Page")); if (selected.registration !== null) { $('#selected_registration').text(selected.registration); } else { $('#selected_registration').text("n/a"); } if (selected.icaotype !== null) { $('#selected_icaotype').text(selected.icaotype); } else { $('#selected_icaotype').text("n/a"); } // Not using this logic for the redesigned info panel at the time, but leaving it in if/when adding it back // var emerg = document.getElementById('selected_emergency'); // if (selected.squawk in SpecialSquawks) { // emerg.className = SpecialSquawks[selected.squawk].cssClass; // emerg.textContent = NBSP + 'Squawking: ' + SpecialSquawks[selected.squawk].text + NBSP ; // } else { // emerg.className = 'hidden'; // } $("#selected_altitude").text(format_altitude_long(selected.altitude, selected.vert_rate, DisplayUnits)); $('#selected_onground').text(format_onground(selected.altitude)); if (selected.squawk === null || selected.squawk === '0000') { $('#selected_squawk').text('n/a'); } else { $('#selected_squawk').text(selected.squawk); } $('#selected_speed').text(format_speed_long(selected.gs, DisplayUnits)); $('#selected_ias').text(format_speed_long(selected.ias, DisplayUnits)); $('#selected_tas').text(format_speed_long(selected.tas, DisplayUnits)); $('#selected_vertical_rate').text(format_vert_rate_long(selected.baro_rate, DisplayUnits)); $('#selected_vertical_rate_geo').text(format_vert_rate_long(selected.geom_rate, DisplayUnits)); $('#selected_icao').text(selected.icao.toUpperCase()); $('#airframes_post_icao').attr('value',selected.icao); $('#selected_track').text(format_track_long(selected.track)); if (selected.seen <= 1) { $('#selected_seen').text('now'); } else { $('#selected_seen').text(selected.seen.toFixed(1) + 's'); } if (selected.seen_pos <= 1) { $('#selected_seen_pos').text('now'); } else { $('#selected_seen_pos').text(selected.seen_pos.toFixed(1) + 's'); } $('#selected_country').text(selected.icaorange.country); if (ShowFlags && selected.icaorange.flag_image !== null) { $('#selected_flag').removeClass('hidden'); $('#selected_flag img').attr('src', FlagPath + selected.icaorange.flag_image); $('#selected_flag img').attr('title', selected.icaorange.country); } else { $('#selected_flag').addClass('hidden'); } if (selected.position === null) { $('#selected_position').text('n/a'); $('#selected_follow').addClass('hidden'); } else { $('#selected_position').text(format_latlng(selected.position)); $('#position_age').text(selected.seen_pos.toFixed(1) + 's'); $('#selected_follow').removeClass('hidden'); if (FollowSelected) { $('#selected_follow').css('font-weight', 'bold'); OLMap.getView().setCenter(ol.proj.fromLonLat(selected.position)); } else { $('#selected_follow').css('font-weight', 'normal'); } } var datasource = selected.getDataSource(); if (datasource === "uat") { $('#selected_source').text("UAT"); } else if (datasource === "adsb_icao") { $('#selected_source').text("ADS-B"); } else if (datasource === "tisb_trackfile" || datasource === "tisb_icao" || datasource === "tisb_other") { $('#selected_source').text("TIS-B"); } else if (datasource === "mlat") { $('#selected_source').text("MLAT"); } else { $('#selected_source').text("Other"); } $('#selected_category').text(selected.category ? selected.category : "n/a"); $('#selected_sitedist').text(format_distance_long(selected.sitedist, DisplayUnits)); $('#selected_rssi').text(selected.rssi.toFixed(1) + ' dBFS'); $('#selected_message_count').text(selected.messages); $('#selected_photo_link').html(getFlightAwarePhotoLink(selected.registration)); $('#selected_altitude_geom').text(format_altitude_long(selected.alt_geom, selected.geom_rate, DisplayUnits)); $('#selected_mag_heading').text(format_track_long(selected.mag_heading)); $('#selected_true_heading').text(format_track_long(selected.true_heading)); $('#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_long(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 { var sampleRate = ""; var 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('N/A'); } 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); } if (selected.uat_version == null) { $('#selected_uat_version').text('N/A'); } else if (selected.uat_version == 0) { $('#selected_uat_version').text('v0 (DO-282)'); } else if (selected.uat_version == 1) { $('#selected_uat_version').text('v1 (DO-282A)'); } else if (selected.uat_version == 2) { $('#selected_uat_version').text('v2 (DO-282B)'); } else { $('#selected_uat_version').text('v' + selected.uat_version); } } // Calculate 1090 and 978 Message rate using the rolling history of Message Counts recorded from processing aircraft.json function updateMessageRates () { if (MessageCountHistory.length > 1) { var message_time_delta = MessageCountHistory[MessageCountHistory.length-1].time - MessageCountHistory[0].time; var message_count_delta = MessageCountHistory[MessageCountHistory.length-1].messages - MessageCountHistory[0].messages; if (message_time_delta > 0) MessageRate = message_count_delta / message_time_delta; } else { MessageRate = null; } if (MessageCountHistory_UAT.length > 1) { var message_time_delta = MessageCountHistory_UAT[MessageCountHistory_UAT.length-1].time - MessageCountHistory_UAT[0].time; var message_count_delta = MessageCountHistory_UAT[MessageCountHistory_UAT.length-1].messages - MessageCountHistory_UAT[0].messages; if (message_time_delta > 0) UatMessageRate = message_count_delta / message_time_delta; } else { UatMessageRate = null; } } function refreshHighlighted() { // this is following nearly identical logic, etc, as the refreshSelected function, but doing less junk for the highlighted pane var highlighted = false; if (typeof HighlightedPlane !== 'undefined' && HighlightedPlane !== null) { highlighted = Planes[HighlightedPlane]; } var infoBox = $('#highlighted_infoblock'); // no highlighted plane or in process of removing plane if (!highlighted || !highlighted.marker) { infoBox.fadeOut(); return; } var mapCanvas = $('#map_canvas'); var markerCoordinates = highlighted.marker.getGeometry().getCoordinates(); var markerPosition = OLMap.getPixelFromCoordinate(markerCoordinates); var x = markerPosition[0]; var y = markerPosition[1]; if (x < 0 || y < 0 || x > mapCanvas.width() || y > mapCanvas.height()) { infoBox.fadeOut(); return; } x = x + 20; y = y + 60; var w = infoBox.outerWidth() + 20; var h = infoBox.outerHeight(); if (x > mapCanvas.width() - w) { x -= w + 20; } if (y > mapCanvas.height() - h) { y -= h; } if (infoBox.css('visibility', 'visible')) { infoBox.animate({ left: x, top: y }, 500); } else { infoBox.css({ left: x, top: y }); } infoBox.fadeIn(100); if (highlighted.flight !== null && highlighted.flight !== "") { $('#highlighted_callsign').text(highlighted.flight); } else { $('#highlighted_callsign').text('n/a'); } if (highlighted.icaotype !== null) { $('#higlighted_icaotype').text(highlighted.icaotype); } else { $('#higlighted_icaotype').text("n/a"); } var datasource = highlighted.getDataSource(); if (datasource === "uat") { $('#highlighted_source').text("UAT"); } else if (datasource === "adsb_icao") { $('#highlighted_source').text("ADS-B"); } else if (datasource === "tisb_trackfile" || datasource === "tisb_icao" || datasource === "tisb_other") { $('#highlighted_source').text("TIS-B"); } else if (datasource === "mlat") { $('#highlighted_source').text("MLAT"); } else { $('#highlighted_source').text("Other"); } if (highlighted.registration !== null) { $('#highlighted_registration').text(highlighted.registration); } else { $('#highlighted_registration').text("n/a"); } $('#highlighted_speed').text(format_speed_long(highlighted.speed, DisplayUnits)); $("#highlighted_altitude").text(format_altitude_long(highlighted.altitude, highlighted.vert_rate, DisplayUnits)); $('#highlighted_icao').text(highlighted.icao.toUpperCase()); } function refreshClock() { $('#clock_div').text(new Date().toLocaleString()); var c = setTimeout(refreshClock, 500); } function removeHighlight() { HighlightedPlane = null; refreshHighlighted(); } // Refreshes the larger table of all the planes function refreshTableInfo() { var show_squawk_warning = false; TrackedAircraft = 0 TrackedAircraftPositions = 0 TrackedHistorySize = 0 $(".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)); for (var i = 0; i < PlanesOrdered.length; ++i) { var tableplane = PlanesOrdered[i]; TrackedHistorySize += tableplane.history_size; if (tableplane.seen >= 58 || tableplane.isFiltered()) { tableplane.tr.className = "plane_table_row hidden"; } else { TrackedAircraft++; var classes = "plane_table_row"; if (tableplane.position !== null && tableplane.seen_pos < 60) { ++TrackedAircraftPositions; } var datasource = tableplane.getDataSource(); if (datasource === "uat") { classes += " uat"; } else if (datasource === "adsb_icao") { classes += " vPosition"; } else if (datasource === "tisb_trackfile" || datasource === "tisb_icao" || datasource === "tisb_other") { classes += " tisb"; } else if (datasource === "mlat") { classes += " mlat"; } else { classes += " other"; } if (tableplane.icao == SelectedPlane) classes += " selected"; if (tableplane.squawk in SpecialSquawks) { classes = classes + " " + SpecialSquawks[tableplane.squawk].cssClass; show_squawk_warning = true; } // ICAO doesn't change if (tableplane.flight) { tableplane.tr.cells[2].innerHTML = getFlightAwareModeSLink(tableplane.icao, tableplane.flight, tableplane.flight); tableplane.tr.cells[2].className = "ident_normal"; } else if (tableplane.registration !== null) { // Show registration with special styling if ident is not present tableplane.tr.cells[2].innerHTML = getFlightAwareIdentLink(tableplane.registration, tableplane.registration); tableplane.tr.cells[2].className = "ident_fallback"; } else { tableplane.tr.cells[2].innerHTML = ""; tableplane.tr.cells[2].className = ""; } tableplane.tr.cells[3].textContent = (tableplane.registration !== null ? tableplane.registration : ""); tableplane.tr.cells[4].textContent = (tableplane.icaotype !== null ? tableplane.icaotype : ""); tableplane.tr.cells[5].textContent = (tableplane.squawk !== null ? tableplane.squawk : ""); tableplane.tr.cells[6].innerHTML = format_altitude_brief(tableplane.altitude, tableplane.vert_rate, DisplayUnits); tableplane.tr.cells[7].textContent = format_speed_brief(tableplane.gs, DisplayUnits); tableplane.tr.cells[8].textContent = format_vert_rate_brief(tableplane.vert_rate, DisplayUnits); tableplane.tr.cells[9].textContent = format_distance_brief(tableplane.sitedist, DisplayUnits); tableplane.tr.cells[10].textContent = format_track_brief(tableplane.track); tableplane.tr.cells[11].textContent = tableplane.messages; tableplane.tr.cells[12].textContent = tableplane.seen.toFixed(0); tableplane.tr.cells[13].textContent = (tableplane.rssi !== null ? tableplane.rssi : ""); tableplane.tr.cells[14].textContent = (tableplane.position !== null ? tableplane.position[1].toFixed(4) : ""); tableplane.tr.cells[15].textContent = (tableplane.position !== null ? tableplane.position[0].toFixed(4) : ""); tableplane.tr.cells[16].textContent = format_data_source(tableplane.getDataSource()); tableplane.tr.cells[17].innerHTML = getAirframesModeSLink(tableplane.icao); tableplane.tr.cells[18].innerHTML = getFlightAwareModeSLink(tableplane.icao, tableplane.flight); tableplane.tr.cells[19].innerHTML = getFlightAwarePhotoLink(tableplane.registration); tableplane.tr.className = classes; } } if (show_squawk_warning) { $("#SpecialSquawkWarning").css('display','block'); } else { $("#SpecialSquawkWarning").css('display','none'); } resortTable(); } // // ---- table sorting ---- // function compareAlpha(xa,ya) { if (xa === ya) return 0; if (xa < ya) return -1; return 1; } function compareNumeric(xf,yf) { if (Math.abs(xf - yf) < 1e-9) return 0; return xf - yf; } function sortByICAO() { sortBy('icao', compareAlpha, function(x) { return x.icao; }); } function sortByFlight() { sortBy('flight', compareAlpha, function(x) { return x.flight ? x.flight : x.registration; }); } function sortByRegistration() { sortBy('registration', compareAlpha, function(x) { return x.registration; }); } function sortByAircraftType() { sortBy('icaotype', compareAlpha, function(x) { return x.icaotype; }); } function sortBySquawk() { sortBy('squawk', compareAlpha, function(x) { return x.squawk; }); } function sortByAltitude() { sortBy('altitude',compareNumeric, function(x) { return (x.altitude == "ground" ? -1e9 : x.altitude); }); } function sortBySpeed() { sortBy('speed', compareNumeric, function(x) { return x.gs; }); } function sortByVerticalRate() { sortBy('vert_rate', compareNumeric, function(x) { return x.vert_rate; }); } function sortByDistance() { sortBy('sitedist',compareNumeric, function(x) { return x.sitedist; }); } function sortByTrack() { sortBy('track', compareNumeric, function(x) { return x.track; }); } function sortByMsgs() { sortBy('msgs', compareNumeric, function(x) { return x.messages; }); } function sortBySeen() { sortBy('seen', compareNumeric, function(x) { return x.seen; }); } function sortByCountry() { sortBy('country', compareAlpha, function(x) { return x.icaorange.country; }); } function sortByRssi() { sortBy('rssi', compareNumeric, function(x) { return x.rssi }); } function sortByLatitude() { sortBy('lat', compareNumeric, function(x) { return (x.position !== null ? x.position[1] : null) }); } function sortByLongitude() { sortBy('lon', compareNumeric, function(x) { return (x.position !== null ? x.position[0] : null) }); } function sortByDataSource() { sortBy('data_source', compareAlpha, function(x) { return x.getDataSource() } ); } var sortId = ''; var sortCompare = null; var sortExtract = null; var sortAscending = true; function sortFunction(x,y) { var xv = x._sort_value; var 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; var c = sortAscending ? sortCompare(xv,yv) : sortCompare(yv,xv); if (c !== 0) return c; return x._sort_pos - y._sort_pos; } function resortTable() { // number the existing rows so we can do a stable sort // regardless of whether sort() is stable or not. // Also extract the sort comparison value. for (var i = 0; i < PlanesOrdered.length; ++i) { PlanesOrdered[i]._sort_pos = i; PlanesOrdered[i]._sort_value = sortExtract(PlanesOrdered[i]); } PlanesOrdered.sort(sortFunction); var tbody = document.getElementById('tableinfo').tBodies[0]; for (var i = 0; i < PlanesOrdered.length; ++i) { tbody.appendChild(PlanesOrdered[i].tr); } } function sortBy(id,sc,se) { if (id !== 'data_source') { $('#grouptype_checkbox').removeClass('settingsCheckboxChecked'); localStorage.setItem('groupByDataType', 'deselected'); } if (id === sortId) { sortAscending = !sortAscending; PlanesOrdered.reverse(); // this correctly flips the order of rows that compare equal } else { sortAscending = true; } sortId = id; sortCompare = sc; sortExtract = se; resortTable(); } function selectPlaneByHex(hex,autofollow) { //console.log("select: " + hex); // If SelectedPlane has something in it, clear out the selected if (SelectedAllPlanes) { deselectAllPlanes(); } if (SelectedPlane != null) { Planes[SelectedPlane].selected = false; Planes[SelectedPlane].clearLines(); Planes[SelectedPlane].updateMarker(); $(Planes[SelectedPlane].tr).removeClass("selected"); // scroll the infoblock back to the top for the next plane to be selected $('.infoblock-container').scrollTop(0); } // If we are clicking the same plane, we are deselecting it. // (unless it was a doubleclick..) if (SelectedPlane === hex && !autofollow) { hex = null; } if (hex !== null) { // Assign the new selected SelectedPlane = hex; Planes[SelectedPlane].selected = true; Planes[SelectedPlane].updateLines(); Planes[SelectedPlane].updateMarker(); $(Planes[SelectedPlane].tr).addClass("selected"); } else { SelectedPlane = null; } if (SelectedPlane !== null && autofollow) { FollowSelected = true; if (OLMap.getView().getZoom() < 8) OLMap.getView().setZoom(8); } else { FollowSelected = false; } refreshSelected(); refreshHighlighted(); } function highlightPlaneByHex(hex) { if (hex != null) { HighlightedPlane = hex; } } // 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(); } else { // If SelectedPlane has something in it, clear out the selected if (SelectedPlane != null) { Planes[SelectedPlane].selected = false; Planes[SelectedPlane].clearLines(); Planes[SelectedPlane].updateMarker(); $(Planes[SelectedPlane].tr).removeClass("selected"); } SelectedPlane = null; SelectedAllPlanes = true; for(var key in Planes) { if (Planes[key].visible && !Planes[key].isFiltered()) { Planes[key].selected = true; Planes[key].updateLines(); Planes[key].updateMarker(); } } } $('#selectall_checkbox').addClass('settingsCheckboxChecked'); refreshSelected(); refreshHighlighted(); } // on refreshes, try to find new planes and mark them as selected function selectNewPlanes() { if (SelectedAllPlanes) { for (var key in Planes) { if (!Planes[key].visible || Planes[key].isFiltered()) { Planes[key].selected = false; Planes[key].clearLines(); Planes[key].updateMarker(); } else { if (Planes[key].selected !== true) { Planes[key].selected = true; Planes[key].updateLines(); Planes[key].updateMarker(); } } } } } function toggleGroupByDataType(switchToggle) { if (typeof localStorage['groupByDataType'] === 'undefined') { localStorage.setItem('groupByDataType', 'deselected'); } var groupByDataType = localStorage.getItem('groupByDataType'); if (switchToggle === true) { groupByDataType = (groupByDataType === 'deselected') ? 'selected' : 'deselected'; } if (groupByDataType === 'deselected') { $('#grouptype_checkbox').removeClass('settingsCheckboxChecked'); } else { sortByDataSource(); $('#grouptype_checkbox').addClass('settingsCheckboxChecked'); } localStorage.setItem('groupByDataType', groupByDataType); } function toggleAircraftLabels(switchToggle) { if (typeof localStorage['showAircraftLabels'] === 'undefined') { localStorage.setItem('showAircraftLabels', 'deselected'); } var showAircraftLabels = localStorage.getItem('showAircraftLabels'); if (switchToggle === true) { showAircraftLabels = (showAircraftLabels === 'deselected') ? 'selected' : 'deselected'; } if (showAircraftLabels === 'deselected') { // hide aircraft labels AircraftLabels = false; $('#aircraft_label_checkbox').removeClass('settingsCheckboxChecked'); } else { // show aicraft labels AircraftLabels = true; $('#aircraft_label_checkbox').addClass('settingsCheckboxChecked'); } localStorage.setItem('showAircraftLabels', showAircraftLabels); } function toggleAllPlanes(switchToggle) { if (typeof localStorage['allPlanesSelection'] === 'undefined') { localStorage.setItem('allPlanesSelection','deselected'); } var allPlanesSelection = localStorage.getItem('allPlanesSelection'); if (switchToggle === true) { allPlanesSelection = (allPlanesSelection === 'deselected') ? 'selected' : 'deselected'; } if (allPlanesSelection === 'deselected') { deselectAllPlanes(); } else { selectAllPlanes(); } localStorage.setItem('allPlanesSelection', allPlanesSelection); } // deselect all the planes function deselectAllPlanes() { for(var key in Planes) { Planes[key].selected = false; Planes[key].clearLines(); Planes[key].updateMarker(); $(Planes[key].tr).removeClass("selected"); } $('#selectall_checkbox').removeClass('settingsCheckboxChecked'); SelectedPlane = null; SelectedAllPlanes = false; refreshSelected(); refreshHighlighted(); } function toggleFollowSelected() { FollowSelected = !FollowSelected; if (FollowSelected && OLMap.getView().getZoom() < 8) OLMap.getView().setZoom(8); refreshSelected(); } function resetMap() { // Reset localStorage values and map settings localStorage['CenterLat'] = CenterLat = DefaultCenterLat; localStorage['CenterLon'] = CenterLon = DefaultCenterLon; localStorage['ZoomLvl'] = ZoomLvl = DefaultZoomLvl; // Reset to default range rings localStorage['SiteCirclesCount'] = SiteCirclesCount = DefaultSiteCirclesCount; localStorage['SiteCirclesBaseDistance'] = SiteCirclesBaseDistance = DefaultSiteCirclesBaseDistance; localStorage['SiteCirclesInterval'] = SiteCirclesInterval = DefaultSiteCirclesInterval; setRangeRings(); createSiteCircleFeatures(); // Set and refresh OLMap.getView().setZoom(ZoomLvl); OLMap.getView().setCenter(ol.proj.fromLonLat([CenterLon, CenterLat])); selectPlaneByHex(null,false); } function updateMapSize() { OLMap.updateSize(); } function toggleSidebarVisibility(e) { if (e) { e.preventDefault(); } $("#sidebar_container").toggle(); $("#expand_sidebar_control").toggle(); $("#toggle_sidebar_button").toggleClass("show_sidebar"); $("#toggle_sidebar_button").toggleClass("hide_sidebar"); updateMapSize(); } function expandSidebar(e) { if (e) { e.preventDefault(); } $("#map_container").hide() $("#toggle_sidebar_control").hide(); $("#splitter").hide(); $("#sudo_buttons").hide(); $("#show_map_button").show(); $("#sidebar_container").width("100%"); setColumnVisibility(); setSelectedInfoBlockVisibility(); updateMapSize(); } function showMap() { $("#map_container").show() $("#toggle_sidebar_control").show(); $("#splitter").show(); $("#sudo_buttons").show(); $("#show_map_button").hide(); $("#sidebar_container").width("470px"); setColumnVisibility(); setSelectedInfoBlockVisibility(); updateMapSize(); } function showColumn(table, columnId, visible) { var index = $(columnId).index(); if (index >= 0) { var cells = $(table).find("td:nth-child(" + (index + 1).toString() + ")"); if (visible) { cells.show(); } else { cells.hide(); } } } function setColumnVisibility() { var mapIsVisible = $("#map_container").is(":visible"); var infoTable = $("#tableinfo"); var defaultCheckBoxes = [ '#icao_col_checkbox', '#flag_col_checkbox', '#ident_col_checkbox', '#squawk_col_checkbox', '#alt_col_checkbox', '#speed_col_checkbox', '#distance_col_checkbox', '#heading_col_checkbox', '#messages_col_checkbox', '#msg_age_col_checkbox' ] // Show default columns if checkboxes have not been set for (var i=0; i < defaultCheckBoxes.length; i++) { var checkBoxdiv = defaultCheckBoxes[i]; var columnDiv = checkbox_div_map.get(checkBoxdiv) if (typeof localStorage[checkBoxdiv] === 'undefined') { $(checkBoxdiv).addClass('settingsCheckboxChecked'); localStorage.setItem(checkBoxdiv, 'selected'); showColumn(infoTable, columnDiv, true); } } // Now check local storage checkbox status checkbox_div_map.forEach(function (div, checkbox) { var status = localStorage.getItem(checkbox); if (status === 'selected') { $(checkbox).addClass('settingsCheckboxChecked'); showColumn(infoTable, div, true); } else { $(checkbox).removeClass('settingsCheckboxChecked'); showColumn(infoTable, div, false); } }); } function setSelectedInfoBlockVisibility() { var mapIsVisible = $("#map_container").is(":visible"); var planeSelected = (typeof SelectedPlane !== 'undefined' && SelectedPlane != null && SelectedPlane != "ICAO"); if (planeSelected && mapIsVisible) { $('#selected_infoblock').show(); $('#sidebar_canvas').css('margin-bottom', $('#selected_infoblock').height() + 'px'); } else { $('#selected_infoblock').hide(); $('#sidebar_canvas').css('margin-bottom', 0); } } // Reposition selected plane info box if it overlaps plane marker function adjustSelectedInfoBlockPosition() { if (typeof Planes === 'undefined' || typeof SelectedPlane === 'undefined' || Planes === null) { return; } var selectedPlane = Planes[SelectedPlane]; if (selectedPlane === undefined || selectedPlane === null || selectedPlane.marker === undefined || selectedPlane.marker === null) { return; } try { // Get marker position var marker = selectedPlane.marker; var markerCoordinates = selectedPlane.marker.getGeometry().getCoordinates(); var markerPosition = OLMap.getPixelFromCoordinate(markerCoordinates); // Get map size var mapCanvas = $('#map_canvas'); var mapExtent = getExtent(0, 0, mapCanvas.width(), mapCanvas.height()); // Check for overlap if (isPointInsideExtent(markerPosition[0], markerPosition[1], infoBoxExtent)) { // Array of possible new positions for info box var candidatePositions = []; candidatePositions.push( { x: 40, y: 60 } ); candidatePositions.push( { x: 40, y: markerPosition[1] + 80 } ); // Find new position for (var i = 0; i < candidatePositions.length; i++) { var candidatePosition = candidatePositions[i]; var candidateExtent = getExtent(candidatePosition.x, candidatePosition.y, infoBox.outerWidth(), infoBox.outerHeight()); if (!isPointInsideExtent(markerPosition[0], markerPosition[1], candidateExtent) && isPointInsideExtent(candidatePosition.x, candidatePosition.y, mapExtent)) { // Found a new position that doesn't overlap marker - move box to that position infoBox.css("left", candidatePosition.x); infoBox.css("top", candidatePosition.y); return; } } } } catch(e) { } } function getExtent(x, y, width, height) { return { xMin: x, yMin: y, xMax: x + width - 1, yMax: y + height - 1, }; } function isPointInsideExtent(x, y, extent) { return x >= extent.xMin && x <= extent.xMax && y >= extent.yMin && y <= extent.yMax; } function initializeUnitsSelector() { // Get display unit preferences from local storage if (!localStorage.getItem('displayUnits')) { localStorage['displayUnits'] = DisplayUnits; } var displayUnits = localStorage['displayUnits']; DisplayUnits = displayUnits; setAltitudeLegend(displayUnits); // Initialize drop-down var unitsSelector = $("#units_selector"); unitsSelector.val(displayUnits); unitsSelector.on("change", onDisplayUnitsChanged); } function onDisplayUnitsChanged(e) { if (e) { var displayUnits = e.target.value; // Save display units to local storage localStorage['displayUnits'] = displayUnits; } DisplayUnits = localStorage['displayUnits']; setAltitudeLegend(DisplayUnits); // Update filters updatePlaneFilter(); // Refresh data refreshTableInfo(); refreshSelected(); refreshHighlighted(); // Reset filter sliders on Display Units change reset_filter_sliders(); // Redraw range rings if (SitePosition !== null && SitePosition !== undefined && SiteCircles) { createSiteCircleFeatures(); } // Reset map scale line units OLMap.getControls().forEach(function(control) { if (control instanceof ol.control.ScaleLine) { control.setUnits(DisplayUnits); } }); } function setAltitudeLegend(units) { if (units === 'metric') { $('#altitude_chart_button').addClass('altitudeMeters'); } else { $('#altitude_chart_button').removeClass('altitudeMeters'); } } function onFilterByAltitude() { updatePlaneFilter(); refreshTableInfo(); var selectedPlane = Planes[SelectedPlane]; if (selectedPlane !== undefined && selectedPlane !== null && selectedPlane.isFiltered()) { SelectedPlane = null; selectedPlane.selected = false; selectedPlane.clearLines(); selectedPlane.updateMarker(); refreshSelected(); refreshHighlighted(); } } function onFilterBySpeed() { updatePlaneFilter(); refreshTableInfo(); } function onFilterByAircraftType(e) { e.preventDefault(); updatePlaneFilter(); refreshTableInfo(); } function onResetAircraftTypeFilter(e) { $("#aircraft_type_filter").val(""); updatePlaneFilter(); refreshTableInfo(); } function onFilterByAircraftIdent(e) { e.preventDefault(); updatePlaneFilter(); refreshTableInfo(); } function onResetAircraftIdentFilter(e) { $("#aircraft_ident_filter").val(""); updatePlaneFilter(); refreshTableInfo(); } function filterGroundVehicles(switchFilter) { if (typeof localStorage['groundVehicleFilter'] === 'undefined') { localStorage.setItem('groundVehicleFilter' , 'not_filtered'); } var groundFilter = localStorage.getItem('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.setItem('groundVehicleFilter',groundFilter); PlaneFilter.groundVehicles = groundFilter; } function filterBlockedMLAT(switchFilter) { if (typeof localStorage['blockedMLATFilter'] === 'undefined') { localStorage.setItem('blockedMLATFilter','not_filtered'); } var blockedMLATFilter = localStorage.getItem('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.setItem('blockedMLATFilter', blockedMLATFilter); PlaneFilter.blockedMLAT = blockedMLATFilter; } function toggleAltitudeChart(switchToggle) { if (typeof localStorage['altitudeChart'] === 'undefined') { localStorage.setItem('altitudeChart','show'); } var altitudeChartDisplay = localStorage.getItem('altitudeChart'); if (switchToggle === true) { altitudeChartDisplay = (altitudeChartDisplay === 'show') ? 'hidden' : 'show'; } // if you're using custom colors always hide the chart if (customAltitudeColors === true) { altitudeChartDisplay = 'hidden'; // also hide the control option $('#altitude_chart_container').hide(); } if (altitudeChartDisplay === 'show') { $('#altitude_checkbox').addClass('settingsCheckboxChecked'); $('#altitude_chart').show(); } else { $('#altitude_checkbox').removeClass('settingsCheckboxChecked'); $('#altitude_chart').hide(); } localStorage.setItem('altitudeChart', altitudeChartDisplay); } function updatePlaneFilter() { // Get min/max altitude values from slider var minAltitude = document.getElementById('minAltitudeText').innerHTML.trim(); var maxAltitude = document.getElementById('maxAltitudeText').innerHTML.trim(); PlaneFilter.minAltitude = minAltitude; PlaneFilter.maxAltitude = maxAltitude; PlaneFilter.altitudeUnits = DisplayUnits; // Get min/max speed values from slider var minSpeedFilter = document.getElementById('minSpeedText').innerHTML.trim(); var maxSpeedFilter = document.getElementById('maxSpeedText').innerHTML.trim(); PlaneFilter.minSpeedFilter = minSpeedFilter; PlaneFilter.maxSpeedFilter = maxSpeedFilter; PlaneFilter.speedUnits = DisplayUnits; // Get aircraft type code filter from input box var aircraftTypeCode = $("#aircraft_type_filter").val().trim().toUpperCase() if (aircraftTypeCode === "") { aircraftTypeCode = undefined } // Get aircraft ident filter from input box var aircraftIdent = $("#aircraft_ident_filter").val().trim().toUpperCase() if (aircraftIdent === "") { aircraftIdent = undefined } PlaneFilter.aircraftTypeCode = aircraftTypeCode; PlaneFilter.aircraftIdent = aircraftIdent; var altitudeFilterSet = (PlaneFilter.minAltitude == DefaultMinMaxFilters[DisplayUnits].min && PlaneFilter.maxAltitude == DefaultMinMaxFilters[DisplayUnits].maxAltitude) ? 0 : 1; var speedFilterSet = (PlaneFilter.minSpeedFilter == DefaultMinMaxFilters[DisplayUnits].min && PlaneFilter.maxSpeedFilter == DefaultMinMaxFilters[DisplayUnits].maxSpeed) ? 0 : 1; var aircraftTypeFilterSet = (PlaneFilter.aircraftTypeCode == undefined) ? 0 : 1; var aircraftIdentFilterSet = (PlaneFilter.aircraftIdent == undefined) ? 0 : 1; ActiveFilterCount = altitudeFilterSet + speedFilterSet + aircraftTypeFilterSet + aircraftIdentFilterSet; var filter = document.getElementById('filter_button'); filter.style.backgroundColor = (ActiveFilterCount > 0) ? "Lime" : "#FEBC11"; } function refreshDataSourceFilters () { PlaneFilter.ADSB = (localStorage.getItem('sourceADSBFilter') === 'selected') ? true : false; PlaneFilter.MLAT = (localStorage.getItem('sourceMLATFilter') === 'selected') ? true : false; PlaneFilter.Other = (localStorage.getItem('sourceOtherFilter') === 'selected') ? true : false; PlaneFilter.TISB = (localStorage.getItem('sourceTISBFilter') === 'selected') ? true : false; PlaneFilter.UAT = (localStorage.getItem('sourceUATFilter') === 'selected') ? true : false; } 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(); } var linkHtml = "" + linkText + ""; return linkHtml; } return ""; } function getFlightAwarePhotoLink(registration) { if (registration !== null && registration !== "") { return "See Photos"; } return ""; } function getAirframesModeSLink(code) { if (code !== null && code.length > 0 && code[0] !== '~' && code !== "000000") { return "Airframes.org: " + code.toUpperCase() + ""; } return ""; } // 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(layerGroup, function(lyr) { if (lyr.get('name') === layer && lyr.getVisible()) { $(element).addClass('settingsCheckboxChecked'); } }); $(element).on('click', function() { var visible = false; if ($(element).hasClass('settingsCheckboxChecked')) { visible = true; } ol.control.LayerSwitcher.forEachRecursive(layerGroup, function(lyr) { if (lyr.get('name') === layer) { if (visible) { lyr.setVisible(false); $(element).removeClass('settingsCheckboxChecked'); } else { lyr.setVisible(true); $(element).addClass('settingsCheckboxChecked'); } } }); }); } // check status.json if it has a serial number for a flightfeeder function flightFeederCheck() { $.ajax('/status.json', { success: function(data) { if (data.type === "flightfeeder") { isFlightFeeder = true; updatePiAwareOrFlightFeeder(); } } }) } function setStatsLink() { $.ajax('/status.json', { success: function(data) { if (data.unclaimed_feeder_id) { var claim_link = "https://flightaware.com/adsb/piaware/claim/" + data.unclaimed_feeder_id; $('#stats_page_button').text("Claim this feeder on FlightAware") myAdsbStatsSiteUrl = claim_link; } else if (data.site_url) { myAdsbStatsSiteUrl = data.site_url; } } }).fail(function() { $('#stats_page_button').hide(); }); } // updates the page to replace piaware with flightfeeder references function updatePiAwareOrFlightFeeder() { if (isFlightFeeder) { $('.piAwareLogo').hide(); $('.flightfeederLogo').show(); PageName = 'FlightFeeder SkyAware'; } else { $('.flightfeederLogo').hide(); $('.piAwareLogo').show(); PageName = 'PiAware SkyAware'; } refreshPageTitle(); } // Function to hide banner (ex. for a kiosk to show maximum data possible) function hideBanner() { document.getElementById("header").style.display = 'none'; document.getElementById("layout_container").style.height = '100%'; updateMapSize(); } // Helper function to restrict the range of the inputs function restrictUrlRequest(c) { let v = parseFloat(c); if (v < 0) { v = 0; } else if (v > 5) { v = 5; } return v; } // Function to zoom, but not by too much per 'amount' function zoomMap(c, zoomOut) { c = restrictUrlRequest(c); ZoomLvl = OLMap.getView().getZoom(); if (zoomOut) { ZoomLvl *= Math.pow(0.95, c); } else { ZoomLvl /= Math.pow(0.95, c); } localStorage['ZoomLvl'] = ZoomLvl; OLMap.getView().setZoom(ZoomLvl); } // Function to move map at 0.005% of the extent per 'move' function moveMap(c, moveVertical, moveDownLeft) { c = restrictUrlRequest(c); let cn = OLMap.getView().getCenter(); let dist = 0; if (moveVertical) { dist = ol.extent.getHeight(OLMap.getView().getProjection().getExtent()); } else { dist = ol.extent.getWidth(OLMap.getView().getProjection().getExtent()); } let d = c * (dist * .005); // 'up' or 'right' needs a negative number if (moveDownLeft) { d *= -1.0; } if (moveVertical) { ol.coordinate.add(cn, [0, d]); } else { ol.coordinate.add(cn, [d, 0]); } OLMap.getView().setCenter(cn); } // Function to set displayUnits function setDisplayUnits(units) { if (units === 'nautical') { localStorage['displayUnits'] = "nautical"; } else if (units === 'metric') { localStorage['displayUnits'] = "metric"; } else if (units === 'imperial') { localStorage['displayUnits'] = "imperial"; } onDisplayUnitsChanged(); } // Function to set range ring visibility function setRangeRingVisibility (showhide) { var show = null; if (showhide === 'hide') { $('#sitepos_checkbox').removeClass('settingsCheckboxChecked') show = false; } else if (showhide === 'show') { $('#sitepos_checkbox').addClass('settingsCheckboxChecked') show = true; } else { return } ol.control.LayerSwitcher.forEachRecursive(layerGroup, function(lyr) { if (lyr.get('name') === 'site_pos') { lyr.setVisible(show); } }); } // simple function to set range ring count function setRingCount(val) { localStorage['SiteCirclesCount'] = val; setRangeRings(); createSiteCircleFeatures(); } // simple function to set range ring distance function setRingBaseDistance(val) { localStorage['SiteCirclesBaseDistance'] = val; setRangeRings(); createSiteCircleFeatures(); } // simple function to set range ring interval function setRingInterval(val) { localStorage['SiteCirclesInterval'] = val; setRangeRings(); createSiteCircleFeatures(); } // Set range ring globals and populate form values function setRangeRings() { SiteCirclesCount = Number(localStorage['SiteCirclesCount']) || DefaultSiteCirclesCount; SiteCirclesBaseDistance = Number(localStorage['SiteCirclesBaseDistance']) || DefaultSiteCirclesBaseDistance; SiteCirclesInterval = Number(localStorage['SiteCirclesInterval']) || DefaultSiteCirclesInterval; // Populate text fields with current values $('#range_ring_count').val(SiteCirclesCount); $('#range_ring_base').val(SiteCirclesBaseDistance); $('#range_ring_interval').val(SiteCirclesInterval); } // redraw range rings with form values function onSetRangeRings() { // Save state to localStorage localStorage.setItem('SiteCirclesCount', parseFloat($("#range_ring_count").val().trim())); localStorage.setItem('SiteCirclesBaseDistance', parseFloat($("#range_ring_base").val().trim())); localStorage.setItem('SiteCirclesInterval', parseFloat($("#range_ring_interval").val().trim())); setRangeRings(); createSiteCircleFeatures(); } function toggleColumn(div, checkbox, toggled) { if (typeof localStorage[checkbox] === 'undefined') { localStorage.setItem(checkbox, 'deselected'); } var status = localStorage.getItem(checkbox); var infoTable = $("#tableinfo"); if (toggled === true) { status = (status === 'deselected') ? 'selected' : 'deselected'; } // Toggle checkbox and column visibility if (status === 'selected') { $(checkbox).addClass('settingsCheckboxChecked'); showColumn(infoTable, div, true); } else { $(checkbox).removeClass('settingsCheckboxChecked'); showColumn(infoTable, div, false); $('#select_all_column_checkbox').removeClass('settingsCheckboxChecked'); localStorage.setItem('selectAllColumnsCheckbox', 'deselected'); } localStorage.setItem(checkbox, status); } function toggleAllColumns(switchToggle) { if (typeof localStorage['selectAllColumnsCheckbox'] === 'undefined') { localStorage.setItem('selectAllColumnsCheckbox','deselected'); } var infoTable = $("#tableinfo"); var selectAllColumnsCheckbox = localStorage.getItem('selectAllColumnsCheckbox'); if (switchToggle === true) { selectAllColumnsCheckbox = (selectAllColumnsCheckbox === 'deselected') ? 'selected' : 'deselected'; checkbox_div_map.forEach(function (div, checkbox) { if (selectAllColumnsCheckbox === 'deselected') { $('#select_all_column_checkbox').removeClass('settingsCheckboxChecked'); $(checkbox).removeClass('settingsCheckboxChecked'); showColumn(infoTable, div, false); } else { $('#select_all_column_checkbox').addClass('settingsCheckboxChecked'); $(checkbox).addClass('settingsCheckboxChecked'); showColumn(infoTable, div, true); } localStorage.setItem(checkbox, selectAllColumnsCheckbox); }); }; if (selectAllColumnsCheckbox === 'deselected') { $('#select_all_column_checkbox').removeClass('settingsCheckboxChecked'); } else { $('#select_all_column_checkbox').addClass('settingsCheckboxChecked'); } localStorage.setItem('selectAllColumnsCheckbox', selectAllColumnsCheckbox); } function toggleADSBAircraft(switchFilter) { if (typeof localStorage['sourceADSBFilter'] === 'undefined') { localStorage.setItem('sourceADSBFilter','selected'); } var sourceADSBFilter = localStorage.getItem('sourceADSBFilter'); if (switchFilter === true) { sourceADSBFilter = (sourceADSBFilter === 'deselected') ? 'selected' : 'deselected'; } if (sourceADSBFilter === 'deselected') { $('#adsb_datasource_checkbox').removeClass('sourceCheckboxChecked'); } else { $('#adsb_datasource_checkbox').addClass('sourceCheckboxChecked'); } localStorage.setItem('sourceADSBFilter', sourceADSBFilter); } function toggleUATAircraft(switchFilter) { if (typeof localStorage['sourceUATFilter'] === 'undefined') { localStorage.setItem('sourceUATFilter','selected'); } var sourceUATFilter = localStorage.getItem('sourceUATFilter'); if (switchFilter === true) { sourceUATFilter = (sourceUATFilter === 'deselected') ? 'selected' : 'deselected'; } if (sourceUATFilter === 'deselected') { $('#uat_datasource_checkbox').removeClass('sourceCheckboxChecked'); } else { $('#uat_datasource_checkbox').addClass('sourceCheckboxChecked'); } localStorage.setItem('sourceUATFilter', sourceUATFilter); } function toggleMLATAircraft(switchFilter) { if (typeof localStorage['sourceMLATFilter'] === 'undefined') { localStorage.setItem('sourceMLATFilter','selected'); } var sourceMLATFilter = localStorage.getItem('sourceMLATFilter'); if (switchFilter === true) { sourceMLATFilter = (sourceMLATFilter === 'deselected') ? 'selected' : 'deselected'; } if (sourceMLATFilter === 'deselected') { $('#mlat_datasource_checkbox').removeClass('sourceCheckboxChecked'); } else { $('#mlat_datasource_checkbox').addClass('sourceCheckboxChecked'); } localStorage.setItem('sourceMLATFilter', sourceMLATFilter); } function toggleOtherAircraft(switchFilter) { if (typeof localStorage['sourceOtherFilter'] === 'undefined') { localStorage.setItem('sourceOtherFilter','selected'); } var sourceOtherFilter = localStorage.getItem('sourceOtherFilter'); if (switchFilter === true) { sourceOtherFilter = (sourceOtherFilter === 'deselected') ? 'selected' : 'deselected'; } if (sourceOtherFilter === 'deselected') { $('#other_datasource_checkbox').removeClass('sourceCheckboxChecked'); } else { $('#other_datasource_checkbox').addClass('sourceCheckboxChecked'); } localStorage.setItem('sourceOtherFilter', sourceOtherFilter); } function toggleTISBAircraft(switchFilter) { if (typeof localStorage['sourceTISBFilter'] === 'undefined') { localStorage.setItem('sourceTISBFilter','selected'); } var sourceTISBFilter = localStorage.getItem('sourceTISBFilter'); if (switchFilter === true) { sourceTISBFilter = (sourceTISBFilter === 'deselected') ? 'selected' : 'deselected'; } if (sourceTISBFilter === 'deselected') { $('#tisb_datasource_checkbox').removeClass('sourceCheckboxChecked'); } else { $('#tisb_datasource_checkbox').addClass('sourceCheckboxChecked'); } localStorage.setItem('sourceTISBFilter', sourceTISBFilter); }