mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2025-10-27 08:49:27 +00:00
Various stuff
This commit is contained in:
@@ -7,28 +7,28 @@ SOFTWARE_VERSION = "0.1"
|
||||
# Modes
|
||||
CW_MODES = ["CW"]
|
||||
PHONE_MODES = ["PHONE", "SSB", "USB", "LSB", "AM", "FM", "DV", "DMR", "DSTAR", "C4FM", "M17"]
|
||||
DATA_MODES = ["DIGI", "DATA", "FT8", "FT4", "RTTY", "SSTV", "JS8", "HELL", "BPSK", "PSK", "BPSK31", "OLIVIA"]
|
||||
DATA_MODES = ["DIGI", "DATA", "DIGITAL", "FT8", "FT4", "RTTY", "SSTV", "JS8", "HELL", "BPSK", "PSK", "BPSK31", "OLIVIA"]
|
||||
ALL_MODES = CW_MODES + PHONE_MODES + DATA_MODES
|
||||
|
||||
# Band definitions
|
||||
BANDS = [
|
||||
Band(name="160m", start_freq=1800, end_freq=2000),
|
||||
Band(name="80m", start_freq=3500, end_freq=4000),
|
||||
Band(name="60m", start_freq=5250, end_freq=5410),
|
||||
Band(name="40m", start_freq=7000, end_freq=7300),
|
||||
Band(name="30m", start_freq=10100, end_freq=10150),
|
||||
Band(name="20m", start_freq=14000, end_freq=14350),
|
||||
Band(name="17m", start_freq=18068, end_freq=18168),
|
||||
Band(name="15m", start_freq=21000, end_freq=21450),
|
||||
Band(name="12m", start_freq=24890, end_freq=24990),
|
||||
Band(name="10m", start_freq=28000, end_freq=29700),
|
||||
Band(name="6m", start_freq=50000, end_freq=54000),
|
||||
Band(name="4m", start_freq=70000, end_freq=70500),
|
||||
Band(name="2m", start_freq=144000, end_freq=148000),
|
||||
Band(name="70cm", start_freq=420000, end_freq=450000),
|
||||
Band(name="23cm", start_freq=1240000, end_freq=1325000),
|
||||
Band(name="13cm", start_freq=2300000, end_freq=2450000)]
|
||||
UNKNOWN_BAND = Band(name="Unknown", start_freq=0, end_freq=0)
|
||||
Band(name="160m", start_freq=1800, end_freq=2000, color="#7cfc00", contrast_color="black"),
|
||||
Band(name="80m", start_freq=3500, end_freq=4000, color="#e550e5", contrast_color="black"),
|
||||
Band(name="60m", start_freq=5250, end_freq=5410, color="#00008b", contrast_color="white"),
|
||||
Band(name="40m", start_freq=7000, end_freq=7300, color="#5959ff", contrast_color="white"),
|
||||
Band(name="30m", start_freq=10100, end_freq=10150, color="#62d962", contrast_color="black"),
|
||||
Band(name="20m", start_freq=14000, end_freq=14350, color="#f2c40c", contrast_color="black"),
|
||||
Band(name="17m", start_freq=18068, end_freq=18168, color="#f2f261", contrast_color="black"),
|
||||
Band(name="15m", start_freq=21000, end_freq=21450, color="#cca166", contrast_color="black"),
|
||||
Band(name="12m", start_freq=24890, end_freq=24990, color="#b22222", contrast_color="white"),
|
||||
Band(name="10m", start_freq=28000, end_freq=29700, color="#ff69b4", contrast_color="black"),
|
||||
Band(name="6m", start_freq=50000, end_freq=54000, color="#FF0000", contrast_color="white"),
|
||||
Band(name="4m", start_freq=70000, end_freq=70500, color="#cc0044", contrast_color="white"),
|
||||
Band(name="2m", start_freq=144000, end_freq=148000, color="#FF1493", contrast_color="black"),
|
||||
Band(name="70cm", start_freq=420000, end_freq=450000, color="#999900", contrast_color="white"),
|
||||
Band(name="23cm", start_freq=1240000, end_freq=1325000, color="#5AB8C7", contrast_color="black"),
|
||||
Band(name="13cm", start_freq=2300000, end_freq=2450000, color="#FF7F50", contrast_color="black")]
|
||||
UNKNOWN_BAND = Band(name="Unknown", start_freq=0, end_freq=0, color="black", contrast_color="white")
|
||||
|
||||
# DXCC flags (borrowed from https:#github.com/wavelog/wavelog/blob/master/application/libraries/DxccFlag.php)
|
||||
DXCC_FLAGS = {
|
||||
|
||||
@@ -3,6 +3,7 @@ from datetime import datetime
|
||||
|
||||
from diskcache import Cache
|
||||
from pyhamtools import LookupLib, Callinfo
|
||||
from pyhamtools.frequency import freq_to_band
|
||||
from pyhamtools.locator import latlong_to_locator
|
||||
|
||||
from core.config import config
|
||||
@@ -25,7 +26,7 @@ def infer_mode_from_comment(comment):
|
||||
return None
|
||||
|
||||
# Infer a "mode family" from a mode.
|
||||
def infer_mode_family_from_mode(mode):
|
||||
def infer_mode_type_from_mode(mode):
|
||||
if mode.upper() in CW_MODES:
|
||||
return "CW"
|
||||
elif mode.upper() in PHONE_MODES:
|
||||
@@ -137,6 +138,11 @@ def infer_grid_from_callsign_dxcc(call):
|
||||
return latlong_to_locator(latlon[0], latlon[1], 8)
|
||||
|
||||
|
||||
# Infer a mode from the frequency according to the band plan. Just a guess really.
|
||||
def infer_mode_from_frequency(freq):
|
||||
return freq_to_band(freq)["mode"]
|
||||
|
||||
|
||||
# Convert objects to serialisable things. Used by JSON serialiser as a default when it encounters unserializable things.
|
||||
# Converts datetimes to ISO.
|
||||
# Anything else it tries to convert to a dict.
|
||||
|
||||
@@ -9,3 +9,7 @@ class Band:
|
||||
start_freq: float
|
||||
# Stop frequency, in kHz
|
||||
end_freq: float
|
||||
# Colour to use for this band, as per PSK Reporter
|
||||
color: str
|
||||
# Contrast colour to use for text against a background of the band colour
|
||||
contrast_color: str
|
||||
35
data/spot.py
35
data/spot.py
@@ -7,10 +7,10 @@ import pytz
|
||||
from pyhamtools.locator import locator_to_latlong, latlong_to_locator
|
||||
|
||||
from core.constants import DXCC_FLAGS
|
||||
from core.utils import infer_mode_family_from_mode, infer_band_from_freq, infer_continent_from_callsign, \
|
||||
from core.utils import infer_mode_type_from_mode, infer_band_from_freq, infer_continent_from_callsign, \
|
||||
infer_country_from_callsign, infer_cq_zone_from_callsign, infer_itu_zone_from_callsign, infer_dxcc_id_from_callsign, \
|
||||
infer_mode_from_comment, infer_name_from_callsign, infer_latlon_from_callsign_dxcc, infer_grid_from_callsign_dxcc, \
|
||||
infer_latlon_from_callsign_qrz, infer_grid_from_callsign_qrz
|
||||
infer_latlon_from_callsign_qrz, infer_grid_from_callsign_qrz, infer_mode_from_frequency
|
||||
|
||||
|
||||
# Data class that defines a spot.
|
||||
@@ -50,7 +50,9 @@ class Spot:
|
||||
# Reported mode, such as SSB, PHONE, CW, FT8...
|
||||
mode: str = None
|
||||
# Inferred mode "family". One of "CW", "PHONE" or "DIGI".
|
||||
mode_family: str = None
|
||||
mode_type: str = None
|
||||
# Source of the mode information. "SPOT", "COMMENT", "BANDPLAN" or "NONE"
|
||||
mode_source: str = "NONE"
|
||||
# Frequency, in kHz
|
||||
freq: float = None
|
||||
# Band, defined by the frequency, e.g. "40m" or "70cm"
|
||||
@@ -77,11 +79,11 @@ class Spot:
|
||||
latitude: float = None
|
||||
longitude: float = None
|
||||
# Location source. Indicates how accurate the location might be. Values: "SPOT", "QRZ, "DXCC", "NONE"
|
||||
location_source: str = None
|
||||
location_source: str = "NONE"
|
||||
# Location good. Indicates that the software thinks the location data is good enough to plot on a map.
|
||||
location_good: bool = None
|
||||
location_good: bool = False
|
||||
# QRT state. Some APIs return spots marked as QRT. Otherwise we can check the comments.
|
||||
qrt: bool = None
|
||||
qrt: bool = False
|
||||
# Where we got the spot from, e.g. "POTA", "Cluster"...
|
||||
source: str = None
|
||||
# The ID the source gave it, if any.
|
||||
@@ -118,11 +120,24 @@ class Spot:
|
||||
band = infer_band_from_freq(self.freq)
|
||||
self.band = band.name
|
||||
|
||||
# Mode from comments, mode family from mode
|
||||
# Mode from comments or bandplan
|
||||
if self.mode:
|
||||
self.mode_source = "SPOT"
|
||||
if self.comment and not self.mode:
|
||||
self.mode=infer_mode_from_comment(self.comment)
|
||||
if self.mode and not self.mode_family:
|
||||
self.mode_family=infer_mode_family_from_mode(self.mode)
|
||||
self.mode = infer_mode_from_comment(self.comment)
|
||||
self.mode_source = "COMMENT"
|
||||
if self.freq and not self.mode:
|
||||
self.mode = infer_mode_from_frequency(self.freq)
|
||||
self.mode_source = "BANDPLAN"
|
||||
|
||||
# Normalise "generic digital" modes. "DIGITAL", "DIGI" and "DATA" are just the same thing with no extra
|
||||
# information, so standardise on "DATA"
|
||||
if self.mode == "DIGI" or self.mode == "DIGITAL":
|
||||
self.mode = "DATA"
|
||||
|
||||
# Mode type from mode
|
||||
if self.mode and not self.mode_type:
|
||||
self.mode_type = infer_mode_type_from_mode(self.mode)
|
||||
|
||||
# Grid to lat/lon and vice versa
|
||||
if self.grid and not self.latitude:
|
||||
|
||||
@@ -105,9 +105,9 @@ class WebServer:
|
||||
case "mode":
|
||||
modes = query.get(k).split(",")
|
||||
spots = [s for s in spots if s.mode in modes]
|
||||
case "mode_family":
|
||||
case "mode_type":
|
||||
mode_families = query.get(k).split(",")
|
||||
spots = [s for s in spots if s.mode_family in mode_families]
|
||||
spots = [s for s in spots if s.mode_type in mode_families]
|
||||
case "dx_continent":
|
||||
dxconts = query.get(k).split(",")
|
||||
spots = [s for s in spots if s.dx_continent in dxconts]
|
||||
|
||||
@@ -122,7 +122,7 @@ paths:
|
||||
- PSK
|
||||
- BPSK31
|
||||
- OLIVIA
|
||||
- name: mode_family
|
||||
- name: mode_type
|
||||
in: query
|
||||
description: "Limit the spots to only ones from one or more mode families. To select more than one mode family, supply a comma-separated list."
|
||||
required: false
|
||||
@@ -338,7 +338,7 @@ components:
|
||||
- BPSK31
|
||||
- OLIVIA
|
||||
example: SSB
|
||||
mode_family:
|
||||
mode_type:
|
||||
type: string
|
||||
description: Inferred mode "family".
|
||||
enum:
|
||||
@@ -346,6 +346,14 @@ components:
|
||||
- PHONE
|
||||
- DATA
|
||||
example: PHONE
|
||||
mode_source:
|
||||
type: string
|
||||
description: Where we got the mode from. If this was from the spot itself, it's likely quite accurate, but if we had to fall back to the bandplan, it might not be correct.
|
||||
enum:
|
||||
- SPOT
|
||||
- COMMENT
|
||||
- BANDPLAN
|
||||
- NONE
|
||||
freq:
|
||||
type: number
|
||||
description: Frequency, in kHz
|
||||
|
||||
@@ -3,16 +3,3 @@ div#table-container {
|
||||
margin: 0 auto;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
div#table-container table, div#table-container th, div#table-container td {
|
||||
border: 1px solid;
|
||||
border-collapse: collapse
|
||||
}
|
||||
|
||||
div#table-container th, div#table-container td {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
div#table-container th {
|
||||
background-color: dodgerblue;
|
||||
}
|
||||
@@ -1,18 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>Unnamed spot tool</title>
|
||||
|
||||
<link rel="stylesheet" href="css/style.css" type="text/css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<h1>Unnamed spot tool</h1>
|
||||
<div id="filters">Some filters here</div>
|
||||
<div id="table-container"></div>
|
||||
<div class="container">
|
||||
<header class="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom">
|
||||
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
|
||||
<span class="fs-4">Unnamed Spot Tool</span>
|
||||
</a>
|
||||
|
||||
<script src="js/code.js"></script>
|
||||
<ul class="nav nav-pills">
|
||||
<li class="nav-item"><a href="#" class="nav-link">About</a></li>
|
||||
<li class="nav-item"><a href="/apidocs" class="nav-link">API</a></li>
|
||||
<li class="nav-item"><a href="#" class="nav-link">Status</a></li>
|
||||
<li class="nav-item"><a href="#" class="nav-link">Filters</a></li>
|
||||
</ul>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div id="table-container"></div>
|
||||
|
||||
<script src="js/code.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
$.getJSON('/api/spots', function(jsonData) {
|
||||
let headers = Object.keys(jsonData[0]);
|
||||
let table = $('<table>').append('<thead><tr></tr></thead><tbody></tbody>');
|
||||
let table = $('<table class="table table-striped table-hover">').append('<thead><tr class="table-primary"></tr></thead><tbody></tbody>');
|
||||
["Time", "DX", "Frequency", "Mode", "Comment", "Source", "DE"].forEach(header => table.find('thead tr').append(`<th>${header}</th>`));
|
||||
|
||||
jsonData.forEach(row => {
|
||||
let $tr = $('<tr>');
|
||||
$tr.append(`<td>${row["time"]}</td>`);
|
||||
$tr.append(`<td>${row["dx_call"]}</td>`);
|
||||
var time = moment(row["time"], moment.ISO_8601);
|
||||
var time_formatted = time.format("HH:mm")
|
||||
$tr.append(`<td>${time_formatted}</td>`);
|
||||
$tr.append(`<td>${row["dx_flag"]} ${row["dx_call"]}</td>`);
|
||||
$tr.append(`<td>${row["freq"]}</td>`);
|
||||
$tr.append(`<td>${row["mode"]}</td>`);
|
||||
$tr.append('<td>').append(escapeHtml(`${row["comment"]}`)).append('</td>');
|
||||
$tr.append('<td>' + escapeHtml(`${row["comment"]}`) + '</td>');
|
||||
$tr.append(`<td>${row["source"]}</td>`);
|
||||
$tr.append(`<td>${row["de_call"]}</td>`);
|
||||
$tr.append(`<td>${row["de_flag"]} ${row["de_call"]}</td>`);
|
||||
table.find('tbody').append($tr);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user