Map improvements #42

This commit is contained in:
Ian Renton
2025-10-17 08:52:53 +01:00
parent 2151e441c4
commit c545a73e93
9 changed files with 104 additions and 19 deletions

View File

@@ -16,8 +16,6 @@ Supported data sources include DX Clusters, the Reverse Beacon Network (RBN), th
You can access the public version's web interface at [https://spothole.app](https://spothole.app), and see [https://spothole.app/apidocs](https://spothole.app/apidocs) for the API details. You can access the public version's web interface at [https://spothole.app](https://spothole.app), and see [https://spothole.app/apidocs](https://spothole.app/apidocs) for the API details.
Please note this URL is not necessarily final, and the API is likely to change as the project works its way towards v1.0. Please do not build anything on the Spothole API yet!
### Running your own copy ### Running your own copy
To download and set up Spothole on a Debian server, run the following commands. Other operating systems will likely be similar. To download and set up Spothole on a Debian server, run the following commands. Other operating systems will likely be similar.

View File

@@ -3,7 +3,7 @@ from data.band import Band
# General software # General software
SOFTWARE_NAME = "Spothole by M0TRT" SOFTWARE_NAME = "Spothole by M0TRT"
SOFTWARE_VERSION = "1.0" SOFTWARE_VERSION = "0.1"
# HTTP headers used for spot providers that use HTTP # HTTP headers used for spot providers that use HTTP
HTTP_HEADERS = {"User-Agent": SOFTWARE_NAME + " " + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"} HTTP_HEADERS = {"User-Agent": SOFTWARE_NAME + " " + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"}

View File

@@ -103,8 +103,17 @@ class Spot:
sig_refs_names: list = None sig_refs_names: list = None
# Activation score. SOTA only # Activation score. SOTA only
activation_score: int = None activation_score: int = None
# Icon, from the Font Awesome set. This is fairly opinionated but is here to help the Spothole web UI and Field Spotter. Does not include the "fa-" prefix.
# Display guidance (optional)
# Icon, from the Font Awesome set. This is fairly opinionated but is here to help the Spothole web UI and Field
# Spotter. Does not include the "fa-" prefix.
icon: str = "question" icon: str = "question"
# Colour to represent this spot, if a client chooses to colour spots based on their frequency band, using PSK
# Reporter's default colours. HTML colour e.g. hex. A contrast colour is also provided which will be black or white.
band_color: str = None
band_contrast_color: str = None
# Timing info # Timing info
@@ -183,6 +192,8 @@ class Spot:
if self.freq and not self.band: if self.freq and not self.band:
band = lookup_helper.infer_band_from_freq(self.freq) band = lookup_helper.infer_band_from_freq(self.freq)
self.band = band.name self.band = band.name
self.band_color = band.color
self.band_contrast_color = band.contrast_color
# Mode from comments or bandplan # Mode from comments or bandplan
if self.mode: if self.mode:

View File

@@ -7,7 +7,8 @@ import sys
from diskcache import Cache from diskcache import Cache
from core.cleanup import CleanupTimer from core.cleanup import CleanupTimer
from core.config import config, WEB_SERVER_PORT from core.config import config, WEB_SERVER_PORT, SERVER_OWNER_CALLSIGN
from core.constants import SOFTWARE_NAME, SOFTWARE_VERSION
from core.lookup_helper import lookup_helper from core.lookup_helper import lookup_helper
from core.status_reporter import StatusReporter from core.status_reporter import StatusReporter
from server.webserver import WebServer from server.webserver import WebServer
@@ -62,6 +63,8 @@ if __name__ == '__main__':
root.addHandler(handler) root.addHandler(handler)
logging.info("Starting...") logging.info("Starting...")
logging.info(
"This is " + SOFTWARE_NAME + " version " + SOFTWARE_VERSION + ". This instance is run by " + SERVER_OWNER_CALLSIGN + ".")
# Shut down gracefully on SIGINT # Shut down gracefully on SIGINT
signal.signal(signal.SIGINT, shutdown) signal.signal(signal.SIGINT, shutdown)
@@ -90,7 +93,7 @@ if __name__ == '__main__':
cleanup_timer.start() cleanup_timer.start()
# Set up web server # Set up web server
web_server = WebServer(spots=spots, alerts= alerts, status_data=status_data, port=WEB_SERVER_PORT) web_server = WebServer(spots=spots, alerts=alerts, status_data=status_data, port=WEB_SERVER_PORT)
web_server.start() web_server.start()
# Set up status reporter # Set up status reporter

View File

@@ -827,6 +827,14 @@ components:
type: string type: string
descripton: Icon, from the Font Awesome set. This is fairly opinionated but is here to help the Spothole web UI and Field Spotter. Does not include the "fa-" prefix. descripton: Icon, from the Font Awesome set. This is fairly opinionated but is here to help the Spothole web UI and Field Spotter. Does not include the "fa-" prefix.
example: tree example: tree
band_color:
type: string
descripton: Colour to represent this spot, if a client chooses to colour spots based on their frequency band, using PSK Reporter's default colours. HTML colour e.g. hex.
example: #ff0000"
band_contrast_color:
type: string
descripton: Black or white, whichever best contrasts with "band_color".
example: "white"
source: source:
type: string type: string
description: Where we got the alert from. description: Where we got the alert from.

View File

@@ -142,6 +142,12 @@ div#map {
font-family: var(--bs-body-font-family) !important; font-family: var(--bs-body-font-family) !important;
} }
a.leaflet-popup-callsign-link {
color: black;
font-weight: bold;
text-decoration: none;
}
/* GENERAL MOBILE SUPPORT */ /* GENERAL MOBILE SUPPORT */

View File

@@ -33,6 +33,7 @@ function updateMap() {
spots.forEach(function (s) { spots.forEach(function (s) {
if (s["dx_location_good"]) { if (s["dx_location_good"]) {
let m = L.marker([s["dx_latitude"], s["dx_longitude"]], {icon: getIcon(s)}); let m = L.marker([s["dx_latitude"], s["dx_longitude"]], {icon: getIcon(s)});
m.bindPopup(getTooltipText(s));
markersLayer.addLayer(m); markersLayer.addLayer(m);
} }
}); });
@@ -42,14 +43,75 @@ function updateMap() {
function getIcon(s) { function getIcon(s) {
return L.ExtraMarkers.icon({ return L.ExtraMarkers.icon({
icon: "fa-" + s["icon"], icon: "fa-" + s["icon"],
iconColor: "white", // todo iconColor: s["band_contrast_color"],
markerColor: "black", // todo markerColor: s["band_color"],
shape: 'circle', shape: 'circle',
prefix: 'fa', prefix: 'fa',
svg: true svg: true
}); });
} }
// Tooltip text for the markers
function getTooltipText(s) {
// Format DX flag
var dx_flag = "<i class='fa-solid fa-globe-africa'></i>";
if (s["dx_flag"] && s["dx_flag"] != null && s["dx_flag"] != "") {
dx_flag = s["dx_flag"];
}
// Format the frequency
var mhz = Math.floor(s["freq"] / 1000000.0);
var khz = Math.floor((s["freq"] - (mhz * 1000000.0)) / 1000.0);
var hz = Math.floor(s["freq"] - (mhz * 1000000.0) - (khz * 1000.0));
var hz_string = (hz > 0) ? hz.toFixed(0)[0] : "";
var freq_string = `<span class='freq-mhz'>${mhz.toFixed(0)}</span><span class='freq-khz'>${khz.toFixed(0).padStart(3, '0')}</span><span class='freq-hz hideonmobile'>${hz_string}</span>`
// Format comment
var commentText = "";
if (s["comment"] != null) {
commentText = escapeHtml(s["comment"]);
}
// Sig or fallback to source
var sigSourceText = s["source"];
if (s["sig"]) {
sigSourceText = s["sig"];
}
// Format sig_refs
var sig_refs = "";
if (s["sig_refs"]) {
sig_refs = s["sig_refs"].map(s => `<span class='nowrap'>${s}</span>`).join(", ");
}
// DX
const shortCall = s["dx_call"].split("/").sort(function (a, b) {
return b.length - a.length;
})[0];
ttt = `<span class='nowrap'>${dx_flag} <a href='https://www.qrz.com/db/${shortCall}' target='_blank' class="leaflet-popup-callsign-link">${s["dx_call"]}</a></span><br/>`;
// Frequency & band
ttt += `<i class='fa-solid fa-walkie-talkie markerPopupIcon'></i>&nbsp;${freq_string} (${s["band"]})`;
// Mode
if (s["mode"] != null) {
ttt += ` &nbsp;&nbsp; <i class='fa-solid fa-wave-square markerPopupIcon'></i>&nbsp;${s["mode"]}`;
}
ttt += "<br/>";
// Source / SIG / Ref
ttt += `<span class='nowrap'><span class='icon-wrapper'><i class='fa-solid fa-${s["icon"]}'></i>&nbsp;${sigSourceText} ${sig_refs}</span><br/>`;
// Time
ttt += `<i class='fa-solid fa-clock markerPopupIcon'></i>&nbsp;${moment.unix(s["time"]).fromNow()}`;
// Comment
if (commentText.length > 0) {
ttt += `<br/><i class='fa-solid fa-comment markerPopupIcon'></i> ${commentText}`;
}
return ttt;
}
// 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
// spots repeatedly. // spots repeatedly.
function loadOptions() { function loadOptions() {
@@ -57,8 +119,8 @@ function loadOptions() {
// Store options // Store options
options = jsonData; options = jsonData;
// Add CSS for band bullets and band toggle buttons // Add CSS for band toggle buttons
addBandColourCSS(options["bands"]); addBandToggleColourCSS(options["bands"]);
// Populate the filters panel // Populate the filters panel
generateBandsMultiToggleFilterCard(options["bands"]); generateBandsMultiToggleFilterCard(options["bands"]);

View File

@@ -4,14 +4,13 @@ const REFRESH_INTERVAL_SEC = 60;
// Storage for the spot data that the server gives us. // Storage for the spot data that the server gives us.
var spots = [] var spots = []
// Dynamically add CSS code for the band bullets and band toggle buttons to be in the appropriate colour. // Dynamically add CSS code for the band toggle buttons to be in the appropriate colour.
// Some band names contain decimal points which are not allowed in CSS classes, so we text-replace them to "p". // Some band names contain decimal points which are not allowed in CSS classes, so we text-replace them to "p".
function addBandColourCSS(band_options) { function addBandToggleColourCSS(band_options) {
var $style = $('<style>'); var $style = $('<style>');
band_options.forEach(o => { band_options.forEach(o => {
// CSS doesn't like IDs with decimal points in, so we need to replace that // CSS doesn't like IDs with decimal points in, so we need to replace that
var cssFormattedBandName = o['name'] ? o['name'].replace('.', 'p') : "unknown"; var cssFormattedBandName = o['name'] ? o['name'].replace('.', 'p') : "unknown";
$style.append(`.band-bullet-${cssFormattedBandName} { color: ${o['color']}; }`);
$style.append(`#filter-button-label-band-${cssFormattedBandName} { border-color: ${o['color']}; color: var(--bs-primary);}`); $style.append(`#filter-button-label-band-${cssFormattedBandName} { border-color: ${o['color']}; color: var(--bs-primary);}`);
$style.append(`.btn-check:checked + #filter-button-label-band-${cssFormattedBandName} { background-color: ${o['color']}; color: ${o['contrast_color']};}`); $style.append(`.btn-check:checked + #filter-button-label-band-${cssFormattedBandName} { background-color: ${o['color']}; color: ${o['contrast_color']};}`);
}); });

View File

@@ -177,9 +177,7 @@ function updateTable() {
de_flag = ""; de_flag = "";
} }
// CSS doesn't like classes with decimal points in, so we need to replace that in the same way as when we originally // Format band name
// queried the options endpoint and set our CSS.
var cssFormattedBandName = s['band'] ? s['band'].replace('.', 'p') : "unknown";
var bandFullName = s['band'] ? s['band'] + " band": "Unknown band"; var bandFullName = s['band'] ? s['band'] + " band": "Unknown band";
// Populate the row // Populate the row
@@ -190,7 +188,7 @@ function updateTable() {
$tr.append(`<td class='nowrap'><span class='flag-wrapper hideonmobile' title='${dx_country}'>${dx_flag}</span><a class='dx-link' href='https://qrz.com/db/${s["dx_call"]}' target='_new'>${s["dx_call"]}</a></td>`); $tr.append(`<td class='nowrap'><span class='flag-wrapper hideonmobile' title='${dx_country}'>${dx_flag}</span><a class='dx-link' href='https://qrz.com/db/${s["dx_call"]}' target='_new'>${s["dx_call"]}</a></td>`);
} }
if (showFreq) { if (showFreq) {
$tr.append(`<td class='nowrap'><span class='band-bullet band-bullet-${cssFormattedBandName}' title='${bandFullName}'>&#9632;</span>${freq_string}</td>`); $tr.append(`<td class='nowrap'><span class='band-bullet' title='${bandFullName}' style='color: ${s["band_color"]}'>&#9632;</span>${freq_string}</td>`);
} }
if (showMode) { if (showMode) {
$tr.append(`<td class='nowrap'>${mode_string}</td>`); $tr.append(`<td class='nowrap'>${mode_string}</td>`);
@@ -245,8 +243,8 @@ function loadOptions() {
// Store options // Store options
options = jsonData; options = jsonData;
// Add CSS for band bullets and band toggle buttons // Add CSS for band toggle buttons
addBandColourCSS(options["bands"]); addBandToggleColourCSS(options["bands"]);
// Populate the filters panel // Populate the filters panel
generateBandsMultiToggleFilterCard(options["bands"]); generateBandsMultiToggleFilterCard(options["bands"]);