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.
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
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
SOFTWARE_NAME = "Spothole by M0TRT"
SOFTWARE_VERSION = "1.0"
SOFTWARE_VERSION = "0.1"
# HTTP headers used for spot providers that use HTTP
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
# Activation score. SOTA only
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"
# 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
@@ -183,6 +192,8 @@ class Spot:
if self.freq and not self.band:
band = lookup_helper.infer_band_from_freq(self.freq)
self.band = band.name
self.band_color = band.color
self.band_contrast_color = band.contrast_color
# Mode from comments or bandplan
if self.mode:

View File

@@ -7,7 +7,8 @@ import sys
from diskcache import Cache
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.status_reporter import StatusReporter
from server.webserver import WebServer
@@ -62,6 +63,8 @@ if __name__ == '__main__':
root.addHandler(handler)
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
signal.signal(signal.SIGINT, shutdown)
@@ -90,7 +93,7 @@ if __name__ == '__main__':
cleanup_timer.start()
# 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()
# Set up status reporter

View File

@@ -827,6 +827,14 @@ components:
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.
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:
type: string
description: Where we got the alert from.

View File

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

View File

@@ -33,6 +33,7 @@ function updateMap() {
spots.forEach(function (s) {
if (s["dx_location_good"]) {
let m = L.marker([s["dx_latitude"], s["dx_longitude"]], {icon: getIcon(s)});
m.bindPopup(getTooltipText(s));
markersLayer.addLayer(m);
}
});
@@ -42,14 +43,75 @@ function updateMap() {
function getIcon(s) {
return L.ExtraMarkers.icon({
icon: "fa-" + s["icon"],
iconColor: "white", // todo
markerColor: "black", // todo
iconColor: s["band_contrast_color"],
markerColor: s["band_color"],
shape: 'circle',
prefix: 'fa',
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
// spots repeatedly.
function loadOptions() {
@@ -57,8 +119,8 @@ function loadOptions() {
// Store options
options = jsonData;
// Add CSS for band bullets and band toggle buttons
addBandColourCSS(options["bands"]);
// Add CSS for band toggle buttons
addBandToggleColourCSS(options["bands"]);
// Populate the filters panel
generateBandsMultiToggleFilterCard(options["bands"]);

View File

@@ -4,14 +4,13 @@ const REFRESH_INTERVAL_SEC = 60;
// Storage for the spot data that the server gives us.
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".
function addBandColourCSS(band_options) {
function addBandToggleColourCSS(band_options) {
var $style = $('<style>');
band_options.forEach(o => {
// CSS doesn't like IDs with decimal points in, so we need to replace that
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(`.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 = "";
}
// CSS doesn't like classes with decimal points in, so we need to replace that in the same way as when we originally
// queried the options endpoint and set our CSS.
var cssFormattedBandName = s['band'] ? s['band'].replace('.', 'p') : "unknown";
// Format band name
var bandFullName = s['band'] ? s['band'] + " band": "Unknown band";
// 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>`);
}
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) {
$tr.append(`<td class='nowrap'>${mode_string}</td>`);
@@ -245,8 +243,8 @@ function loadOptions() {
// Store options
options = jsonData;
// Add CSS for band bullets and band toggle buttons
addBandColourCSS(options["bands"]);
// Add CSS for band toggle buttons
addBandToggleColourCSS(options["bands"]);
// Populate the filters panel
generateBandsMultiToggleFilterCard(options["bands"]);