mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 08:49:27 +00:00
Map improvements #42
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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 + ")"}
|
||||||
|
|||||||
13
data/spot.py
13
data/spot.py
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|
||||||
|
|||||||
@@ -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> ${freq_string} (${s["band"]})`;
|
||||||
|
// Mode
|
||||||
|
if (s["mode"] != null) {
|
||||||
|
ttt += ` <i class='fa-solid fa-wave-square markerPopupIcon'></i> ${s["mode"]}`;
|
||||||
|
}
|
||||||
|
ttt += "<br/>";
|
||||||
|
|
||||||
|
// Source / SIG / Ref
|
||||||
|
ttt += `<span class='nowrap'><span class='icon-wrapper'><i class='fa-solid fa-${s["icon"]}'></i> ${sigSourceText} ${sig_refs}</span><br/>`;
|
||||||
|
|
||||||
|
// Time
|
||||||
|
ttt += `<i class='fa-solid fa-clock markerPopupIcon'></i> ${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"]);
|
||||||
|
|||||||
@@ -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']};}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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}'>■</span>${freq_string}</td>`);
|
$tr.append(`<td class='nowrap'><span class='band-bullet' title='${bandFullName}' style='color: ${s["band_color"]}'>■</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"]);
|
||||||
|
|||||||
Reference in New Issue
Block a user