mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 00:39:26 +00:00
213 lines
8.8 KiB
JavaScript
213 lines
8.8 KiB
JavaScript
// 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("<div class='alert alert-danger' role='alert'>No spots match your filters.</div>");
|
|
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 += "<div class='bandCol' style='width:" + columnWidthPercent + "%'>";
|
|
html += "<div class='bandColHeader' style='background-color:" + spotList[0].band_color + "; color:" + spotList[0].band_contrast_color + "'>" + spotList[0].band + "</div>";
|
|
html += "<div class='bandColMiddle'>";
|
|
|
|
// 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 += "<ul>";
|
|
html += "<li><span>-</span></li>";
|
|
|
|
// Do 40 steps down the band
|
|
for (let i = 0; i <= 40; i++) {
|
|
|
|
// Work out if there are any spots in this step
|
|
const freqStepStart = band.start_freq + i * freqStep;
|
|
const freqStepEnd = freqStepStart + freqStep;
|
|
const spotsInStep = spotList.filter(function (s) {
|
|
// Normally we do >= start and < end, but in the special case where this is the last step and there is a spot
|
|
// right at the end of the band, we include this too
|
|
return s.freq >= freqStepStart && (s.freq < freqStepEnd || (s.freq === freqStepEnd && freqStepEnd === band.end_freq));
|
|
});
|
|
|
|
if (spotsInStep.length > 0) {
|
|
// If this step has spots in it, print them
|
|
html += "<li class='withSpots'><span>";
|
|
spotsInStep.sort((a, b) => (a.freq > b.freq) ? 1 : ((b.freq > a.freq) ? -1 : 0));
|
|
spotsInStep.forEach(function (s) {
|
|
html += "<div class='bandColSpot'><span class='bandColSpot'>" + s.dx_call + "<br/><span class='bandColSpotFreq'>" + (s.freq/1000000) + "</span>";
|
|
if (s.mode != null && s.mode.length > 0 && s.mode !== "Unknown") {
|
|
html += "<span class='bandColSpotMode'>" + s.mode + "</span>";
|
|
}
|
|
html += "</span></div>";
|
|
});
|
|
html += "</li></span>";
|
|
|
|
} else {
|
|
// Step had no spots in it, so just print a marker. This is a frequency on multiples of 4, or a dash otherwise.
|
|
if (i % 4 === 0) {
|
|
html += "<li><span>—" + ((band.start_freq + i * freqStep)/1000000).toFixed(3) + "</span></li>";
|
|
} else if (i % 4 === 2) {
|
|
html += "<li><span>–</span></li>";
|
|
} else {
|
|
html += "<li><span>-</span></li>";
|
|
}
|
|
}
|
|
}
|
|
html += "<li><span>-</span></li>";
|
|
html += "</ul>";
|
|
|
|
html += "</div></div>";
|
|
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);
|
|
}); |