mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 00:39:26 +00:00
Continue work on bands display. #48
This commit is contained in:
@@ -207,6 +207,11 @@ class WebServer:
|
|||||||
needs_sig = query.get(k).upper() == "TRUE"
|
needs_sig = query.get(k).upper() == "TRUE"
|
||||||
if needs_sig:
|
if needs_sig:
|
||||||
spots = [s for s in spots if s.sig]
|
spots = [s for s in spots if s.sig]
|
||||||
|
case "needs_sig_ref":
|
||||||
|
# If true, at least one sig ref is required, regardless of what it is, it just can't be missing.
|
||||||
|
needs_sig_ref = query.get(k).upper() == "TRUE"
|
||||||
|
if needs_sig_ref:
|
||||||
|
spots = [s for s in spots if s.sig_refs and len(s.sig_refs) > 0]
|
||||||
case "band":
|
case "band":
|
||||||
bands = query.get(k).split(",")
|
bands = query.get(k).split(",")
|
||||||
spots = [s for s in spots if s.band and s.band in bands]
|
spots = [s for s in spots if s.band and s.band in bands]
|
||||||
@@ -230,11 +235,11 @@ class WebServer:
|
|||||||
prevent_qrt = query.get(k).upper() == "FALSE"
|
prevent_qrt = query.get(k).upper() == "FALSE"
|
||||||
if prevent_qrt:
|
if prevent_qrt:
|
||||||
spots = [s for s in spots if not s.qrt or s.qrt == False]
|
spots = [s for s in spots if not s.qrt or s.qrt == False]
|
||||||
case "needs_location":
|
case "needs_good_location":
|
||||||
# If true, spots require a location to be returned
|
# If true, spots require a "good" location to be returned
|
||||||
needs_location = query.get(k).upper() == "TRUE"
|
needs_good_location = query.get(k).upper() == "TRUE"
|
||||||
if needs_location:
|
if needs_good_location:
|
||||||
spots = [s for s in spots if s.latitude and s.longitude]
|
spots = [s for s in spots if s.dx_location_good]
|
||||||
case "dedupe":
|
case "dedupe":
|
||||||
# Ensure only the latest spot of each callsign is present in the list. This relies on the list being
|
# Ensure only the latest spot of each callsign is present in the list. This relies on the list being
|
||||||
# in reverse time order, so if any future change allows re-ordering the list, that should be done
|
# in reverse time order, so if any future change allows re-ordering the list, that should be done
|
||||||
|
|||||||
@@ -85,7 +85,14 @@ paths:
|
|||||||
- IOTA
|
- IOTA
|
||||||
- name: needs_sig
|
- name: needs_sig
|
||||||
in: query
|
in: query
|
||||||
description: "Limit the spots to only ones from a Special Interest Grous such as POTA. Because supplying all known SIGs as a `sigs` parameter is unwieldy, and leaving `sigs` blank will also return spots with *no* SIG, this parameter can be set true to return only spots with a SIG, regardless of what it is, so long as it's not blank. This is what Field Spotter uses to exclude generic cluster spots and only retrieve xOTA things."
|
description: "Limit the spots to only ones with a Special Interest Group such as POTA. Because supplying all known SIGs as a `sigs` parameter is unwieldy, and leaving `sigs` blank will also return spots with *no* SIG, this parameter can be set true to return only spots with a SIG, regardless of what it is, so long as it's not blank. This is what Field Spotter uses to exclude generic cluster spots and only retrieve xOTA things."
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
- name: needs_sig_ref
|
||||||
|
in: query
|
||||||
|
description: "Limit the spots to only ones which have at least one reference (e.g. a park reference) for Special Interest Groups such as POTA."
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: boolean
|
||||||
@@ -195,9 +202,9 @@ paths:
|
|||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
- name: needs_location
|
- name: needs_good_location
|
||||||
in: query
|
in: query
|
||||||
description: Return only spots with a location set.
|
description: "Return only spots with a 'good' location. (See the spot `dx_location_good` parameter for details. Useful for map-based clients, to avoid spots with 'bad' locations e.g. loads of cluster spots ending up in the centre of the DXCC entitity.)"
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|||||||
@@ -150,6 +150,110 @@ div#map {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* BANDS PANEL */
|
||||||
|
|
||||||
|
div#bands-container {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
overscroll-behavior-x: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bands panel inner layout */
|
||||||
|
div.bandCol {
|
||||||
|
height: 100%;
|
||||||
|
min-width: 8em;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
overflow-y: clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bandColHeader {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bandColMiddle {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bandColMiddle ul {
|
||||||
|
display: table;
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bandColMiddle ul li {
|
||||||
|
display: table-row;
|
||||||
|
line-height: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*noinspection CssUnusedSymbol*/
|
||||||
|
div.bandColMiddle ul li.withSpots {
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bandColMiddle ul li span {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bandColMiddle ul {
|
||||||
|
display: table;
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-left: 2px dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bandColHeader {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bandColMiddle {
|
||||||
|
margin-left: 3px;
|
||||||
|
border-left: 2px dotted var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bandColSpot {
|
||||||
|
display: block;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 3px;
|
||||||
|
background: lightyellow;
|
||||||
|
margin-right: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.bandColSpot {
|
||||||
|
vertical-align: bottom;
|
||||||
|
display: inline !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Don't wrap frequencies */
|
||||||
|
span.bandColSpotFreq {
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.bandColSpotMode {
|
||||||
|
padding-left: 0.5em;
|
||||||
|
font-size: 0.8em;
|
||||||
|
line-height: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* GENERAL MOBILE SUPPORT */
|
/* GENERAL MOBILE SUPPORT */
|
||||||
|
|
||||||
@media (max-width: 991.99px) {
|
@media (max-width: 991.99px) {
|
||||||
|
|||||||
@@ -30,22 +30,20 @@ function updateBands() {
|
|||||||
// Stop here if nothing to display
|
// Stop here if nothing to display
|
||||||
var bandsPanel = $("#bands-container");
|
var bandsPanel = $("#bands-container");
|
||||||
if (spots.length === 0) {
|
if (spots.length === 0) {
|
||||||
// todo bootstrapify
|
bandsPanel.html("<div class='alert alert-danger' role='alert'>No spots match your filters.</div>");
|
||||||
bandsPanel.html("<p id='bandPanelNoSpots'>There are no spots matching your filters.</p>");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do some harsher de-duping. Because we only display callsign, frequency and mode here, the previous
|
// 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.
|
// 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.
|
// Typically that's a person activating two programs at the same time, e.g. POTA & WWFF.
|
||||||
// todo fix from here
|
spotList = removeDuplicatesForBandPanel(spots);
|
||||||
spotList = removeDuplicatesForBandPanel(spotList);
|
|
||||||
|
|
||||||
// Convert to a map of band names to the spots on that band. Bands with no
|
// Convert to a map of band names to the spots on that band. Bands with no
|
||||||
// spots in view will not be present.
|
// spots in view will not be present.
|
||||||
const bandToSpots = new Map();
|
const bandToSpots = new Map();
|
||||||
options["bands"].forEach(function (band) {
|
options["bands"].forEach(function (band) {
|
||||||
const matchingSpots = spots.filter(function (s) {
|
const matchingSpots = spotList.filter(function (s) {
|
||||||
return s.band === band.name;
|
return s.band === band.name;
|
||||||
});
|
});
|
||||||
if (matchingSpots.length > 0) {
|
if (matchingSpots.length > 0) {
|
||||||
@@ -60,7 +58,7 @@ function updateBands() {
|
|||||||
bandToSpots.forEach(function (spotList, bandName) {
|
bandToSpots.forEach(function (spotList, bandName) {
|
||||||
// Get the colours for the band from the first spot, and prepare the header
|
// Get the colours for the band from the first spot, and prepare the header
|
||||||
html += "<div class='bandCol' style='width:" + columnWidthPercent + "%'>";
|
html += "<div class='bandCol' style='width:" + columnWidthPercent + "%'>";
|
||||||
html += "<div class='bandColHeader' style='background-color:" + spotList[0].color + "; color:" + spotList[0].contrast_color + "'>" + spotList[0].band + "</div>";
|
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'>";
|
html += "<div class='bandColMiddle'>";
|
||||||
|
|
||||||
// Get the band data to fetch start and end frequencies
|
// Get the band data to fetch start and end frequencies
|
||||||
@@ -68,7 +66,7 @@ function updateBands() {
|
|||||||
return b.name === bandName;
|
return b.name === bandName;
|
||||||
})[0];
|
})[0];
|
||||||
// Start printing the band
|
// Start printing the band
|
||||||
const freqStep = (band.stopFreq - band.startFreq) / 40.0;
|
const freqStep = (band.end_freq - band.start_freq) / 40.0;
|
||||||
html += "<ul>";
|
html += "<ul>";
|
||||||
html += "<li><span>-</span></li>";
|
html += "<li><span>-</span></li>";
|
||||||
|
|
||||||
@@ -76,12 +74,12 @@ function updateBands() {
|
|||||||
for (let i = 0; i <= 40; i++) {
|
for (let i = 0; i <= 40; i++) {
|
||||||
|
|
||||||
// Work out if there are any spots in this step
|
// Work out if there are any spots in this step
|
||||||
const freqStepStart = band.startFreq + i * freqStep;
|
const freqStepStart = band.start_freq + i * freqStep;
|
||||||
const freqStepEnd = freqStepStart + freqStep;
|
const freqStepEnd = freqStepStart + freqStep;
|
||||||
const spotsInStep = spotList.filter(function (s) {
|
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
|
// 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
|
// right at the end of the band, we include this too
|
||||||
return s.freq >= freqStepStart && (s.freq < freqStepEnd || (s.freq === freqStepEnd && freqStepEnd === band.stopFreq));
|
return s.freq >= freqStepStart && (s.freq < freqStepEnd || (s.freq === freqStepEnd && freqStepEnd === band.end_freq));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (spotsInStep.length > 0) {
|
if (spotsInStep.length > 0) {
|
||||||
@@ -89,15 +87,7 @@ function updateBands() {
|
|||||||
html += "<li class='withSpots'><span>";
|
html += "<li class='withSpots'><span>";
|
||||||
spotsInStep.sort((a, b) => (a.freq > b.freq) ? 1 : ((b.freq > a.freq) ? -1 : 0));
|
spotsInStep.sort((a, b) => (a.freq > b.freq) ? 1 : ((b.freq > a.freq) ? -1 : 0));
|
||||||
spotsInStep.forEach(function (s) {
|
spotsInStep.forEach(function (s) {
|
||||||
// Figure out the class to use for the spot's div, which defines its colour.
|
html += "<div class='bandColSpot'><span class='bandColSpot'>" + s.dx_call + "<br/><span class='bandColSpotFreq'>" + (s.freq/1000000) + "</span>";
|
||||||
let spotDivClass = "bandColSpotCurrent";
|
|
||||||
if (currentPopupSpotUID === s.uid) {
|
|
||||||
spotDivClass = "bandColSpotSelected";
|
|
||||||
} else if (preQSYStatusShouldShowGrey(s.preqsy)) {
|
|
||||||
spotDivClass = "bandColSpotOld";
|
|
||||||
}
|
|
||||||
|
|
||||||
html += "<div class='bandColSpot " + spotDivClass + "' onClick='handleBandPanelSpotClick(\"" + s.uid + "\")'><span class='bandColSpot'>" + s.activator + "<br/><span class='bandColSpotFreq'>" + getFormattedFrequency(s.freq) + "</span>";
|
|
||||||
if (s.mode != null && s.mode.length > 0 && s.mode !== "Unknown") {
|
if (s.mode != null && s.mode.length > 0 && s.mode !== "Unknown") {
|
||||||
html += "<span class='bandColSpotMode'>" + s.mode + "</span>";
|
html += "<span class='bandColSpotMode'>" + s.mode + "</span>";
|
||||||
}
|
}
|
||||||
@@ -108,7 +98,7 @@ function updateBands() {
|
|||||||
} else {
|
} else {
|
||||||
// Step had no spots in it, so just print a marker. This is a frequency on multiples of 4, or a dash otherwise.
|
// 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) {
|
if (i % 4 === 0) {
|
||||||
html += "<li><span>—" + (band.startFreq + i * freqStep).toFixed(3) + "</span></li>";
|
html += "<li><span>—" + ((band.start_freq + i * freqStep)/1000000).toFixed(3) + "</span></li>";
|
||||||
} else if (i % 4 === 2) {
|
} else if (i % 4 === 2) {
|
||||||
html += "<li><span>–</span></li>";
|
html += "<li><span>–</span></li>";
|
||||||
} else {
|
} else {
|
||||||
@@ -128,7 +118,31 @@ function updateBands() {
|
|||||||
// Desktop mouse wheel to scroll bands horizontally if used on the headers
|
// Desktop mouse wheel to scroll bands horizontally if used on the headers
|
||||||
// noinspection JSDeprecatedSymbols
|
// noinspection JSDeprecatedSymbols
|
||||||
$(".bandColHeader").on("wheel", () => bandsPanel.scrollLeft(bandsPanel.scrollLeft() + event.deltaY / 10.0));
|
$(".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
|
// Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ function buildQueryString() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
str = str + "max_age=" + $("#max-spot-age option:selected").val();
|
str = str + "max_age=" + $("#max-spot-age option:selected").val();
|
||||||
// Additional filters for the map view: No dupes, no QRT, only spots with locations
|
// Additional filters for the map view: No dupes, no QRT, only spots with good locations
|
||||||
str = str + "&dedupe=true&allow_qrt=false&needs_location=true";
|
str = str + "&dedupe=true&allow_qrt=false&needs_good_location=true";
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user