mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-24 08:14:32 +00:00
Improve the way hamsat alerts work, and add the ability to detect satellite names from spot comments. Closes #149
This commit is contained in:
@@ -52,11 +52,11 @@ def get_activity_ref_info(activity_name, ref_id):
|
||||
if activity_name.upper() == ActivityName.DTMBA:
|
||||
ref_id = ref_id.replace("-", "").replace(" ", "")
|
||||
|
||||
### NO DATA ACTIVITIES ###
|
||||
### NO REFERENCE ACTIVITIES ###
|
||||
#
|
||||
# If the activity is HEMA or BIWOTA, we have no way to either generate useful data or look it up on a
|
||||
# If the activity doesn't have references, we have no way to either generate useful data or look it up on a
|
||||
# reference list, so just skip the lookup here.
|
||||
if activity_name.upper() == ActivityName.HEMA or activity_name.upper() == ActivityName.BIWOTA:
|
||||
if not activity.has_refs:
|
||||
return activity_ref
|
||||
|
||||
### PROGRAMMATIC DATA GENERATION INSTEAD OF LOOKUPS ###
|
||||
|
||||
@@ -163,6 +163,7 @@ class ActivityRefType(str, Enum):
|
||||
BUILDING = "BUILDING"
|
||||
REGION = "REGION"
|
||||
GRID = "GRID"
|
||||
SATELLITE = "SATELLITE"
|
||||
TOILET = "TOILET"
|
||||
|
||||
|
||||
|
||||
+7
-5
@@ -20,9 +20,7 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
sig_type=ActivityType.TRADITIONAL,
|
||||
has_refs=False,
|
||||
refs_globally_unique=False,
|
||||
# DXpedition stations are never really spotted with "DXpedition" in the comments, but we can assign
|
||||
# this activity to a spot other ways.
|
||||
comment_names=[],
|
||||
comment_names=["DXPEDITION"],
|
||||
icon="fa-book-atlas",
|
||||
alerts_possible=True,
|
||||
),
|
||||
@@ -30,8 +28,12 @@ ACTIVITIES: dict[ActivityName, Activity] = {
|
||||
name=ActivityName.SATELLITE,
|
||||
description="Amateur Radio Satellite",
|
||||
sig_type=ActivityType.TRADITIONAL,
|
||||
has_refs=False,
|
||||
refs_globally_unique=False,
|
||||
# Satellite "references" are the names of the satellites themselves. This is not an exhaustive list, it just
|
||||
# matches some of the most commonly used amateur radio satellites so they can be picked out of spot comments.
|
||||
has_refs=True,
|
||||
refs_globally_unique=True,
|
||||
ref_type=ActivityRefType.SATELLITE,
|
||||
ref_regex=r"ISS|AO-(?:7|27|73|91|95|123)|QO-100|QO100|QO 100|RS-44|SO-50",
|
||||
comment_names=[],
|
||||
icon="fa-satellite",
|
||||
alerts_possible=True,
|
||||
|
||||
+34
-6
@@ -5,6 +5,7 @@ from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytz
|
||||
from pyhamtools.locator import locator_to_latlong, latlong_to_locator
|
||||
|
||||
from core.activity_lookup_helper import populate_missing_activity_ref_info
|
||||
from core.activity_utils import get_icon_for_activity
|
||||
@@ -40,6 +41,11 @@ class Alert:
|
||||
dx_cq_zone: int | None = None
|
||||
# ITU zone of the DX operator
|
||||
dx_itu_zone: int | None = None
|
||||
# Maidenhead grid locator for the DX. This could be from a geographical reference e.g. POTA or grid.
|
||||
dx_grid: str | None = None
|
||||
# Latitude & longitude of the DX, in degrees. This could be from a geographical reference e.g. POTA or grid.
|
||||
dx_latitude: float | None = None
|
||||
dx_longitude: float | None = None
|
||||
|
||||
# General alert info
|
||||
|
||||
@@ -108,8 +114,7 @@ class Alert:
|
||||
if self.received_time and not self.received_time_iso:
|
||||
self.received_time_iso = datetime.fromtimestamp(self.received_time, pytz.UTC).isoformat()
|
||||
|
||||
# DX country, continent, zones etc. from callsign. CQ/ITU zone are better looked up with a location but we don't
|
||||
# have a real location for alerts.
|
||||
# DX country, continent, zones etc. from callsign.
|
||||
if self.dx_calls and self.dx_calls[0]:
|
||||
call_info = get_call_info(self.dx_calls[0], credentials)
|
||||
if self.dx_calls and self.dx_calls[0] and not self.dx_country:
|
||||
@@ -125,18 +130,41 @@ class Alert:
|
||||
if self.dx_dxcc_id and not self.dx_flag:
|
||||
self.dx_flag = get_flag_for_dxcc(self.dx_dxcc_id)
|
||||
|
||||
# Fetch activity data. In case a particular API doesn't provide a full set of name, lat, lon & grid for a
|
||||
# reference in its initial call, we use this code to populate the rest of the data. This includes working
|
||||
# out grid refs from WAB and WAI, which count as an activity even though there's no real lookup, just maths
|
||||
# Fetch activity data, and set a real position if we can get one.
|
||||
if self.sig_refs:
|
||||
for activity_ref in self.sig_refs:
|
||||
populate_missing_activity_ref_info(activity_ref)
|
||||
activity_ref = populate_missing_activity_ref_info(activity_ref)
|
||||
# If the alert itself doesn't have location yet, but the activity ref does, extract it
|
||||
if activity_ref.grid and not self.dx_grid:
|
||||
self.dx_grid = activity_ref.grid
|
||||
if (
|
||||
activity_ref.latitude
|
||||
and not self.dx_latitude
|
||||
and activity_ref.longitude
|
||||
and not self.dx_longitude
|
||||
):
|
||||
self.dx_latitude = activity_ref.latitude
|
||||
self.dx_longitude = activity_ref.longitude
|
||||
|
||||
# If the spot itself doesn't have an activity yet, but we have at least one activity reference, take that
|
||||
# reference's activity and apply it to the whole spot.
|
||||
if self.sig_refs and self.sig_refs[0] and not self.sig:
|
||||
self.sig = self.sig_refs[0].sig
|
||||
|
||||
# DX Grid to lat/lon and vice versa in case one is missing
|
||||
if self.dx_grid and (not self.dx_latitude or not self.dx_longitude):
|
||||
try:
|
||||
ll = locator_to_latlong(self.dx_grid)
|
||||
self.dx_latitude = ll[0]
|
||||
self.dx_longitude = ll[1]
|
||||
except Exception:
|
||||
logger.debug("Invalid grid received for spot", exc_info=True)
|
||||
if self.dx_latitude and self.dx_longitude and not self.dx_grid:
|
||||
try:
|
||||
self.dx_grid = latlong_to_locator(self.dx_latitude, self.dx_longitude, 8)
|
||||
except Exception:
|
||||
logger.debug("Invalid lat/lon received for spot", exc_info=True)
|
||||
|
||||
# Create an ID based on the source and source ID if possible, as these guaranee uniqueness. If there is no
|
||||
# source ID, use a combination of callsign and start time. Excluding things like the comment here allows for
|
||||
# user updates of their alert comments without duplicating in the system.
|
||||
|
||||
@@ -22,17 +22,20 @@ class Hamsat(HTTPAlertProvider):
|
||||
# Iterate through source data
|
||||
for source_alert in http_response.json()["data"]:
|
||||
# Convert to our alert format
|
||||
freqs_modes = source_alert.get("mode", "")
|
||||
if "mhz" in source_alert:
|
||||
if "mhz_direction" in source_alert:
|
||||
freqs_modes = f"{source_alert['mhz']!s} {source_alert['mhz_direction']}, {freqs_modes}"
|
||||
freqs_modes = source_alert.get("mode") or ""
|
||||
mhz = source_alert.get("mhz")
|
||||
if mhz is not None:
|
||||
mhz_direction = source_alert.get("mhz_direction")
|
||||
if mhz_direction is not None:
|
||||
freqs_modes = f"{mhz!s} {mhz_direction}, {freqs_modes}"
|
||||
else:
|
||||
freqs_modes = f"{source_alert['mhz']!s}, {freqs_modes}"
|
||||
freqs_modes = f"{mhz!s}, {freqs_modes}"
|
||||
|
||||
alert = Alert(
|
||||
source=self.name,
|
||||
source_id=source_alert["id"],
|
||||
dx_calls=[source_alert["callsign"].upper()],
|
||||
dx_grid=source_alert["grids"][0],
|
||||
freqs_modes=freqs_modes,
|
||||
comment=source_alert["comment"],
|
||||
sig=ActivityName.SATELLITE,
|
||||
@@ -40,7 +43,7 @@ class Hamsat(HTTPAlertProvider):
|
||||
sig_refs=[
|
||||
ActivityRef(
|
||||
sig=ActivityName.SATELLITE,
|
||||
id=f"{source_alert['satellite']['name']} from {source_alert['grids'][0]}",
|
||||
id=source_alert["satellite"]["name"],
|
||||
)
|
||||
],
|
||||
start_time=datetime.strptime(source_alert["aos_at"], "%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
@@ -21,6 +21,7 @@ info:
|
||||
* Removed `alert_type` from alert data. Contest, DXpedition and Satellite alerts now give those values in `sig` instead, alongside the existing outdoor activity programmes. Teeeeechnically a breaking change but AlertType is so new I doubt anyone is using it yet, so slipped this one in anyway. Sorry :)
|
||||
* Added QRP, RaDAR Rally, /AM and /MM activities
|
||||
* Added `has_refs` and `alerts_possible` to Activity data
|
||||
* Added `dx_grid`, `dx_latitude` and `dx_longitude` to alert data
|
||||
|
||||
### 2.1
|
||||
|
||||
@@ -1452,6 +1453,21 @@ components:
|
||||
type: integer
|
||||
description: ITU zone of the DX operator
|
||||
example: 14
|
||||
dx_grid:
|
||||
type: string
|
||||
description: >
|
||||
Maidenhead grid locator for the activator's location, if known.
|
||||
example: IO91aa
|
||||
dx_latitude:
|
||||
type: number
|
||||
description: >
|
||||
Latitude of the activator's location, if known, in degrees.
|
||||
example: 51.2345
|
||||
dx_longitude:
|
||||
type: number
|
||||
description: >
|
||||
Longitude of the activator's location, if known, in degrees.
|
||||
example: -1.2345
|
||||
freqs_modes:
|
||||
type: string
|
||||
description: An indication of the frequencies and modes that the activation will use, if provided.
|
||||
|
||||
@@ -266,6 +266,11 @@ function addAlertRowsToTable(tbody, alerts) {
|
||||
} else {
|
||||
items[i] = `${escapeHtml(a["sig_refs"][i]["id"])}`
|
||||
}
|
||||
// If this is a satellite alert the ref will just be the satellite, but DX grid is also important, so
|
||||
// show that if we can.
|
||||
if (a["sig_refs"][i]["sig"] === "Satellite" && a["dx_grid"]) {
|
||||
items[i] += " from " + a["sig_refs"][i]["dx_grid"];
|
||||
}
|
||||
}
|
||||
activityRefs = items.join(", ");
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/static/js/add-spot.js?v=1790196142"></script>
|
||||
<script src="/static/js/add-spot.js?v=1790197716"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-add-spot").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/static/js/alerts.js?v=1790196142"></script>
|
||||
<script src="/static/js/alerts.js?v=1790197717"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-alerts").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -76,8 +76,8 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1790196142"></script>
|
||||
<script src="/static/js/bands.js?v=1790196142"></script>
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1790197716"></script>
|
||||
<script src="/static/js/bands.js?v=1790197716"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-bands").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
{% extends "skeleton.html" %}
|
||||
{% block head_extra %}
|
||||
<link rel="stylesheet" href="/static/css/style.css?v=1790196142" type="text/css">
|
||||
<link rel="stylesheet" href="/static/css/style.css?v=1790197716" type="text/css">
|
||||
<link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet">
|
||||
<link href="/static/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet">
|
||||
<link href="/static/vendor/css/solid-6.7.2.min.css" rel="stylesheet">
|
||||
@@ -16,10 +16,10 @@
|
||||
window.fetchEventSource = fetchEventSource;
|
||||
</script>
|
||||
|
||||
<script src="/static/js/utils.js?v=1790196142"></script>
|
||||
<script src="/static/js/ui-ham.js?v=1790196142"></script>
|
||||
<script src="/static/js/geo.js?v=1790196142"></script>
|
||||
<script src="/static/js/common.js?v=1790196142"></script>
|
||||
<script src="/static/js/utils.js?v=1790197716"></script>
|
||||
<script src="/static/js/ui-ham.js?v=1790197716"></script>
|
||||
<script src="/static/js/geo.js?v=1790197716"></script>
|
||||
<script src="/static/js/common.js?v=1790197716"></script>
|
||||
{% end %}
|
||||
{% block body %}
|
||||
<div class="container">
|
||||
|
||||
@@ -284,7 +284,7 @@
|
||||
</div>
|
||||
|
||||
<script src="/static/vendor/js/chart-4.4.9.umd.min.js"></script>
|
||||
<script src="/static/js/conditions.js?v=1790196142"></script>
|
||||
<script src="/static/js/conditions.js?v=1790197716"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-conditions").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
+2
-2
@@ -113,8 +113,8 @@
|
||||
const CARTODB_API_KEY = "{{ web_ui_options.get('cartodb_api_key', '') }}";
|
||||
</script>
|
||||
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1790196142"></script>
|
||||
<script src="/static/js/map.js?v=1790196142"></script>
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1790197717"></script>
|
||||
<script src="/static/js/map.js?v=1790197717"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-map").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -125,8 +125,8 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1790196142"></script>
|
||||
<script src="/static/js/spots.js?v=1790196142"></script>
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1790197716"></script>
|
||||
<script src="/static/js/spots.js?v=1790197716"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-spots").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/status.js?v=1790196142"></script>
|
||||
<script src="/static/js/status.js?v=1790197716"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$("#nav-link-status").addClass("active");
|
||||
|
||||
Reference in New Issue
Block a user