1
0
mirror of https://github.com/craigerl/aprsd.git synced 2024-12-12 01:45:25 -05:00

Update WebChat

This patch changes the location string to include Compass rose
instead of bearing degrees.  Also adds the time timeago package
for calculating how much time since the beacon to format to something
like "12 days ago".
This commit is contained in:
Hemna 2024-11-20 11:47:56 -05:00
parent d42638efe3
commit 9f7d169c18
6 changed files with 58 additions and 73 deletions

View File

@ -1,7 +1,6 @@
import datetime import datetime
import json import json
import logging import logging
import math
import signal import signal
import sys import sys
import threading import threading
@ -14,13 +13,14 @@ from flask_httpauth import HTTPBasicAuth
from flask_socketio import Namespace, SocketIO from flask_socketio import Namespace, SocketIO
from geopy.distance import geodesic from geopy.distance import geodesic
from oslo_config import cfg from oslo_config import cfg
import timeago
from werkzeug.security import check_password_hash, generate_password_hash from werkzeug.security import check_password_hash, generate_password_hash
import wrapt import wrapt
import aprsd import aprsd
from aprsd import ( from aprsd import cli_helper, client, packets, plugin_utils, stats, threads
cli_helper, client, packets, plugin_utils, stats, threads, utils, from aprsd import utils
) from aprsd import utils as aprsd_utils
from aprsd.client import client_factory, kiss from aprsd.client import client_factory, kiss
from aprsd.main import cli from aprsd.main import cli
from aprsd.threads import aprsd as aprsd_threads from aprsd.threads import aprsd as aprsd_threads
@ -131,47 +131,6 @@ def verify_password(username, password):
return username return username
def calculate_initial_compass_bearing(point_a, point_b):
"""
Calculates the bearing between two points.
The formulae used is the following:
θ = atan2(sin(Δlong).cos(lat2),
cos(lat1).sin(lat2) sin(lat1).cos(lat2).cos(Δlong))
:Parameters:
- `pointA: The tuple representing the latitude/longitude for the
first point. Latitude and longitude must be in decimal degrees
- `pointB: The tuple representing the latitude/longitude for the
second point. Latitude and longitude must be in decimal degrees
:Returns:
The bearing in degrees
:Returns Type:
float
"""
if (type(point_a) is not tuple) or (type(point_b) is not tuple):
raise TypeError("Only tuples are supported as arguments")
lat1 = math.radians(point_a[0])
lat2 = math.radians(point_b[0])
diff_long = math.radians(point_b[1] - point_a[1])
x = math.sin(diff_long) * math.cos(lat2)
y = math.cos(lat1) * math.sin(lat2) - (
math.sin(lat1)
* math.cos(lat2) * math.cos(diff_long)
)
initial_bearing = math.atan2(x, y)
# Now we have the initial bearing but math.atan2 return values
# from -180° to + 180° which is not what we want for a compass bearing
# The solution is to normalize the initial bearing as shown below
initial_bearing = math.degrees(initial_bearing)
compass_bearing = (initial_bearing + 360) % 360
return compass_bearing
def _build_location_from_repeat(message): def _build_location_from_repeat(message):
# This is a location message Format is # This is a location message Format is
# ^ld^callsign:latitude,longitude,altitude,course,speed,timestamp # ^ld^callsign:latitude,longitude,altitude,course,speed,timestamp
@ -188,16 +147,19 @@ def _build_location_from_repeat(message):
course = float(b[3]) course = float(b[3])
speed = float(b[4]) speed = float(b[4])
time = int(b[5]) time = int(b[5])
compass_bearing = aprsd_utils.degrees_to_cardinal(course)
data = { data = {
"callsign": callsign, "callsign": callsign,
"lat": lat, "lat": lat,
"lon": lon, "lon": lon,
"altitude": alt, "altitude": alt,
"course": course, "course": course,
"compass_bearing": compass_bearing,
"speed": speed, "speed": speed,
"lasttime": time, "lasttime": time,
"timeago": timeago.format(time),
} }
LOG.warning(f"Location data from REPEAT {data}") LOG.debug(f"Location data from REPEAT {data}")
return data return data
@ -208,25 +170,32 @@ def _calculate_location_data(location_data):
alt = location_data["altitude"] alt = location_data["altitude"]
speed = location_data["speed"] speed = location_data["speed"]
lasttime = location_data["lasttime"] lasttime = location_data["lasttime"]
timeago_str = location_data.get(
"timeago",
timeago.format(lasttime),
)
# now calculate distance from our own location # now calculate distance from our own location
distance = 0 distance = 0
if CONF.webchat.latitude and CONF.webchat.longitude: if CONF.webchat.latitude and CONF.webchat.longitude:
our_lat = float(CONF.webchat.latitude) our_lat = float(CONF.webchat.latitude)
our_lon = float(CONF.webchat.longitude) our_lon = float(CONF.webchat.longitude)
distance = geodesic((our_lat, our_lon), (lat, lon)).kilometers distance = geodesic((our_lat, our_lon), (lat, lon)).kilometers
bearing = calculate_initial_compass_bearing( bearing = aprsd_utils.calculate_initial_compass_bearing(
(our_lat, our_lon), (our_lat, our_lon),
(lat, lon), (lat, lon),
) )
compass_bearing = aprsd_utils.degrees_to_cardinal(bearing)
return { return {
"callsign": location_data["callsign"], "callsign": location_data["callsign"],
"lat": lat, "lat": lat,
"lon": lon, "lon": lon,
"altitude": alt, "altitude": alt,
"course": f"{bearing:0.1f}", "course": f"{bearing:0.1f}",
"compass_bearing": compass_bearing,
"speed": speed, "speed": speed,
"lasttime": lasttime, "lasttime": lasttime,
"distance": f"{distance:0.3f}", "timeago": timeago_str,
"distance": f"{distance:0.1f}",
} }

View File

@ -174,14 +174,29 @@ def load_entry_points(group):
print(traceback.format_exc(), file=sys.stderr) print(traceback.format_exc(), file=sys.stderr)
def calculate_initial_compass_bearing(start, end): def calculate_initial_compass_bearing(point_a, point_b):
if (type(start) != tuple) or (type(end) != tuple): # noqa: E721 """
Calculates the bearing between two points.
The formulae used is the following:
θ = atan2(sin(Δlong).cos(lat2),
cos(lat1).sin(lat2) sin(lat1).cos(lat2).cos(Δlong))
:Parameters:
- `pointA: The tuple representing the latitude/longitude for the
first point. Latitude and longitude must be in decimal degrees
- `pointB: The tuple representing the latitude/longitude for the
second point. Latitude and longitude must be in decimal degrees
:Returns:
The bearing in degrees
:Returns Type:
float
"""
if (type(point_a) != tuple) or (type(point_b) != tuple): # noqa: E721
raise TypeError("Only tuples are supported as arguments") raise TypeError("Only tuples are supported as arguments")
lat1 = math.radians(float(start[0])) lat1 = math.radians(float(point_a[0]))
lat2 = math.radians(float(end[0])) lat2 = math.radians(float(point_b[0]))
diff_long = math.radians(float(end[1]) - float(start[1])) diff_long = math.radians(float(point_b[1]) - float(point_a[1]))
x = math.sin(diff_long) * math.cos(lat2) x = math.sin(diff_long) * math.cos(lat2)
y = math.cos(lat1) * math.sin(lat2) - ( y = math.cos(lat1) * math.sin(lat2) - (

View File

@ -22,20 +22,19 @@ function build_location_string(msg) {
loc += "<br>Latitude: " + msg['lat'] + "<br>Longitude: " + msg['lon']; loc += "<br>Latitude: " + msg['lat'] + "<br>Longitude: " + msg['lon'];
loc += "<br>" + "Altitude: " + msg['altitude'] + " m"; loc += "<br>" + "Altitude: " + msg['altitude'] + " m";
loc += "<br>" + "Speed: " + msg['speed'] + " kph"; loc += "<br>" + "Speed: " + msg['speed'] + " kph";
loc += "<br>" + "Bearing: " + msg['course'] + "°"; loc += "<br>" + "Bearing: " + msg['compass_bearing'];
loc += "<br>" + "distance: " + msg['distance'] + " km"; loc += "<br>" + "distance: " + msg['distance'] + " km";
return loc; return loc;
} }
function build_location_string_small(msg) { function build_location_string_small(msg) {
dt = new Date(parseInt(msg['lasttime']) * 1000); dt = new Date(parseInt(msg['lasttime']) * 1000);
loc = "" + msg['distance'] + "km"; loc = "" + msg['distance'] + "km";
//loc += "Lat " + msg['lat'] + "&nbsp;Lon " + msg['lon']; //loc += "Lat " + msg['lat'] + "&nbsp;Lon " + msg['lon'];
loc += "@" + msg['course'] + "°"; loc += "&nbsp;" + msg['compass_bearing'];
//loc += "&nbsp;Distance " + msg['distance'] + " km"; //loc += "&nbsp;Distance " + msg['distance'] + " km";
loc += "&nbsp;" + dt.toLocaleString(); //loc += "&nbsp;" + dt.toLocaleString();
loc += "&nbsp;" + msg['timeago'];
return loc; return loc;
} }
@ -346,9 +345,9 @@ function create_callsign_tab_content(callsign, active=false) {
item_html += '<div class="" style="border: 1px solid #999999;background-color:#aaaaaa;">'; item_html += '<div class="" style="border: 1px solid #999999;background-color:#aaaaaa;">';
item_html += '<div class="row" style="padding-top:4px;padding-bottom:4px;background-color:#aaaaaa;margin:0px;">'; item_html += '<div class="row" style="padding-top:4px;padding-bottom:4px;background-color:#aaaaaa;margin:0px;">';
item_html += '<div class="d-flex col-md-10 justify-content-left" style="padding:0px;margin:0px;">'; item_html += '<div class="d-flex col-md-10 justify-content-left" style="padding:0px;margin:0px;">';
item_html += '<button onclick="call_callsign_location(\''+callsign+'\');" style="margin-left:2px;padding: 0px 4px 0px 4px;" type="button" class="btn btn-primary">'; item_html += '<button onclick="call_callsign_location(\''+callsign+'\');" style="margin-left:2px;padding: 0px 4px 0px 4px;font-size: .9rem" type="button" class="btn btn-primary">';
item_html += '<span id="'+location_id+'Spinner" class="d-none spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>Update</button>'; item_html += '<span id="'+location_id+'Spinner" class="d-none spinner-border spinner-border-sm" role="status" aria-hidden="true" style="font-size: .9rem"></span>Update</button>';
item_html += '&nbsp;<span id="'+location_id+'">'+location_str+'</span></div>'; item_html += '&nbsp;<span id="'+location_id+'" style="font-size: .9rem">'+location_str+'</span></div>';
item_html += '</div>'; item_html += '</div>';
item_html += '<div class="speech-wrapper" id="'+wrapper_id+'"></div>'; item_html += '<div class="speech-wrapper" id="'+wrapper_id+'"></div>';
item_html += '</div>'; item_html += '</div>';

View File

@ -34,7 +34,7 @@ imagesize==1.4.1 # via sphinx
iniconfig==2.0.0 # via pytest iniconfig==2.0.0 # via pytest
isort==5.13.2 # via -r requirements-dev.in, gray isort==5.13.2 # via -r requirements-dev.in, gray
jinja2==3.1.4 # via sphinx jinja2==3.1.4 # via sphinx
libcst==1.5.0 # via fixit libcst==1.5.1 # via fixit
m2r==0.3.1 # via -r requirements-dev.in m2r==0.3.1 # via -r requirements-dev.in
markupsafe==3.0.2 # via jinja2 markupsafe==3.0.2 # via jinja2
mccabe==0.7.0 # via flake8 mccabe==0.7.0 # via flake8

View File

@ -27,3 +27,4 @@ tzlocal
update_checker update_checker
wrapt wrapt
pytz pytz
timeago

View File

@ -37,8 +37,8 @@ markupsafe==3.0.2 # via jinja2, werkzeug
marshmallow==3.23.1 # via dataclasses-json marshmallow==3.23.1 # via dataclasses-json
mypy-extensions==1.0.0 # via typing-inspect mypy-extensions==1.0.0 # via typing-inspect
netaddr==1.3.0 # via oslo-config netaddr==1.3.0 # via oslo-config
oslo-config==9.6.0 # via -r requirements.in oslo-config==9.7.0 # via -r requirements.in
oslo-i18n==6.4.0 # via oslo-config oslo-i18n==6.5.0 # via oslo-config
packaging==24.2 # via marshmallow packaging==24.2 # via marshmallow
pbr==6.1.0 # via oslo-i18n, stevedore pbr==6.1.0 # via oslo-i18n, stevedore
pluggy==1.5.0 # via -r requirements.in pluggy==1.5.0 # via -r requirements.in
@ -57,9 +57,10 @@ shellingham==1.5.4 # via -r requirements.in
simple-websocket==1.1.0 # via python-engineio simple-websocket==1.1.0 # via python-engineio
six==1.16.0 # via -r requirements.in six==1.16.0 # via -r requirements.in
soupsieve==2.6 # via beautifulsoup4 soupsieve==2.6 # via beautifulsoup4
stevedore==5.3.0 # via oslo-config stevedore==5.4.0 # via oslo-config
tabulate==0.9.0 # via -r requirements.in tabulate==0.9.0 # via -r requirements.in
thesmuggler==1.0.1 # via -r requirements.in thesmuggler==1.0.1 # via -r requirements.in
timeago==1.0.16 # via -r requirements.in
typing-extensions==4.12.2 # via typing-inspect typing-extensions==4.12.2 # via typing-inspect
typing-inspect==0.9.0 # via dataclasses-json typing-inspect==0.9.0 # via dataclasses-json
tzlocal==5.2 # via -r requirements.in tzlocal==5.2 # via -r requirements.in