// Load spots and populate the bands display. function loadSpots() { $.getJSON('/api/v1/spots' + buildQueryString(), function(jsonData) { // Store last updated time lastUpdateTime = moment.utc(); updateRefreshDisplay(); // Store data spots = jsonData; // Update bands display updateBands(); }); } // Build a query string for the API, based on the filters that the user has selected. function buildQueryString() { var str = "?"; ["dx_continent", "de_continent", "mode_type", "source", "band"].forEach(fn => { if (!allFilterOptionsSelected(fn)) { str = str + getQueryStringFor(fn) + "&"; } }); str = str + "max_age=" + $("#max-spot-age option:selected").val(); // Additional filters for the bands view: No dupes, no QRT str = str + "&dedupe=true&allow_qrt=false"; return str; } // Update the bands display function updateBands() { // Stop here if nothing to display var bandsPanel = $("#bands-container"); if (spots.length === 0) { bandsPanel.html(""); return; } // Do some harsher de-duping. Because we only display callsign, frequency and mode here, the previous // de-duplication could have let some through that don't look like dupes on the map, but would do here. // Typically that's a person activating two programs at the same time, e.g. POTA & WWFF. spotList = removeDuplicatesForBandPanel(spots); // Convert to a map of band names to the spots on that band. Bands with no // spots in view will not be present. const bandToSpots = new Map(); options["bands"].forEach(function (band) { const matchingSpots = spotList.filter(function (s) { return s.band === band.name; }); if (matchingSpots.length > 0) { bandToSpots.set(band.name, matchingSpots); } }); // Build up HTML content for each band let html = ""; const columnWidthPercent = Math.max(30, 100 / bandToSpots.size); let columnIndex = 0; bandToSpots.forEach(function (spotList, bandName) { // Get the colours for the band from the first spot, and prepare the header html += "
"; html += "
" + spotList[0].band + "
"; html += "
"; // Get the band data to fetch start and end frequencies let band = options["bands"].filter(function (b) { return b.name === bandName; })[0]; // Start printing the band const freqStep = (band.end_freq - band.start_freq) / 40.0; html += ""; html += "
"; columnIndex++; }); // Update the DOM with the band HTML bandsPanel.html(html); // Desktop mouse wheel to scroll bands horizontally if used on the headers // noinspection JSDeprecatedSymbols $(".bandColHeader").on("wheel", () => bandsPanel.scrollLeft(bandsPanel.scrollLeft() + event.deltaY / 10.0)); } // Iterate through a temporary list of spots, merging duplicates in a way suitable for the band panel. If two or more // spots with the activator, mode and frequency are found, these will be merged and reduced until only one remains, // with the best data. Note that unlike removeDuplicates(), which operates on the main spot map, this operates only // on the temporary array of spots provided as an argument, and returns the output, for use when constructing the // band panel. function removeDuplicatesForBandPanel(spotList) { const spotsToRemove = []; spotList.forEach(function (check) { spotList.forEach(function (s) { if (s !== check) { if (s.dx_call === check.dx_call && s.freq === check.freq && s.mode === check.mode) { // Find which one to keep and which to delete const checkSpotNewer = check.time > s.time; const keepSpot = checkSpotNewer ? check : s; const deleteSpot = checkSpotNewer ? s : check; // Aggregate list of spots to remove spotsToRemove.push(deleteSpot.uid); } } }); }); // Perform the removal return spotList.filter(s => !spotsToRemove.includes(s.uid)); } // Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query // spots repeatedly. function loadOptions() { $.getJSON('/api/v1/options', function(jsonData) { // Store options options = jsonData; // Add CSS for band toggle buttons addBandToggleColourCSS(options["bands"]); // Populate the filters panel generateBandsMultiToggleFilterCard(options["bands"]); generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]); generateMultiToggleFilterCard("#de-continent-options", "de_continent", options["continents"]); generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]); generateMultiToggleFilterCard("#source-options", "source", options["spot_sources"]); // Load settings from settings storage now all the controls are available loadSettings(); // Load spots and set up the timer loadSpots(); setInterval(loadSpots, REFRESH_INTERVAL_SEC * 1000); }); } // Method called when any display property is changed to reload the map and persist the display settings. function displayUpdated() { updateMap(); saveSettings(); } // React to toggling/closing panels function toggleFiltersPanel() { // If we are going to show the filters panel, hide the display panel if (!$("#filters-area").is(":visible") && $("#display-area").is(":visible")) { $("#display-area").hide(); $("#display-button").button("toggle"); } $("#filters-area").toggle(); } function closeFiltersPanel() { $("#filters-button").button("toggle"); $("#filters-area").hide(); } function toggleDisplayPanel() { // If we are going to show the display panel, hide the filters panel if (!$("#display-area").is(":visible") && $("#filters-area").is(":visible")) { $("#filters-area").hide(); $("#filters-button").button("toggle"); } $("#display-area").toggle(); } function closeDisplayPanel() { $("#display-button").button("toggle"); $("#display-area").hide(); } // Startup $(document).ready(function() { // Call loadOptions(), this will then trigger loading spots and setting up timers. loadOptions(); // Update the refresh timing display every second setInterval(updateRefreshDisplay, 1000); });