From 9f7d169c18c6ac593d49efd9dde42bc40b28bc70 Mon Sep 17 00:00:00 2001 From: Hemna Date: Wed, 20 Nov 2024 11:47:56 -0500 Subject: [PATCH] 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". --- aprsd/cmds/webchat.py | 65 +++++++----------------- aprsd/utils/__init__.py | 25 +++++++-- aprsd/web/chat/static/js/send-message.js | 31 ++++++----- requirements-dev.txt | 2 +- requirements.in | 1 + requirements.txt | 7 +-- 6 files changed, 58 insertions(+), 73 deletions(-) diff --git a/aprsd/cmds/webchat.py b/aprsd/cmds/webchat.py index e090787..1900b95 100644 --- a/aprsd/cmds/webchat.py +++ b/aprsd/cmds/webchat.py @@ -1,7 +1,6 @@ import datetime import json import logging -import math import signal import sys import threading @@ -14,13 +13,14 @@ from flask_httpauth import HTTPBasicAuth from flask_socketio import Namespace, SocketIO from geopy.distance import geodesic from oslo_config import cfg +import timeago from werkzeug.security import check_password_hash, generate_password_hash import wrapt import aprsd -from aprsd import ( - cli_helper, client, packets, plugin_utils, stats, threads, utils, -) +from aprsd import cli_helper, client, packets, plugin_utils, stats, threads +from aprsd import utils +from aprsd import utils as aprsd_utils from aprsd.client import client_factory, kiss from aprsd.main import cli from aprsd.threads import aprsd as aprsd_threads @@ -131,47 +131,6 @@ def verify_password(username, password): 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): # This is a location message Format is # ^ld^callsign:latitude,longitude,altitude,course,speed,timestamp @@ -188,16 +147,19 @@ def _build_location_from_repeat(message): course = float(b[3]) speed = float(b[4]) time = int(b[5]) + compass_bearing = aprsd_utils.degrees_to_cardinal(course) data = { "callsign": callsign, "lat": lat, "lon": lon, "altitude": alt, "course": course, + "compass_bearing": compass_bearing, "speed": speed, "lasttime": time, + "timeago": timeago.format(time), } - LOG.warning(f"Location data from REPEAT {data}") + LOG.debug(f"Location data from REPEAT {data}") return data @@ -208,25 +170,32 @@ def _calculate_location_data(location_data): alt = location_data["altitude"] speed = location_data["speed"] lasttime = location_data["lasttime"] + timeago_str = location_data.get( + "timeago", + timeago.format(lasttime), + ) # now calculate distance from our own location distance = 0 if CONF.webchat.latitude and CONF.webchat.longitude: our_lat = float(CONF.webchat.latitude) our_lon = float(CONF.webchat.longitude) 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), (lat, lon), ) + compass_bearing = aprsd_utils.degrees_to_cardinal(bearing) return { "callsign": location_data["callsign"], "lat": lat, "lon": lon, "altitude": alt, "course": f"{bearing:0.1f}", + "compass_bearing": compass_bearing, "speed": speed, "lasttime": lasttime, - "distance": f"{distance:0.3f}", + "timeago": timeago_str, + "distance": f"{distance:0.1f}", } diff --git a/aprsd/utils/__init__.py b/aprsd/utils/__init__.py index e3ca433..6998d82 100644 --- a/aprsd/utils/__init__.py +++ b/aprsd/utils/__init__.py @@ -174,14 +174,29 @@ def load_entry_points(group): print(traceback.format_exc(), file=sys.stderr) -def calculate_initial_compass_bearing(start, end): - if (type(start) != tuple) or (type(end) != tuple): # noqa: E721 +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) != tuple) or (type(point_b) != tuple): # noqa: E721 raise TypeError("Only tuples are supported as arguments") - lat1 = math.radians(float(start[0])) - lat2 = math.radians(float(end[0])) + lat1 = math.radians(float(point_a[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) y = math.cos(lat1) * math.sin(lat2) - ( diff --git a/aprsd/web/chat/static/js/send-message.js b/aprsd/web/chat/static/js/send-message.js index dfd7d88..f93d1a8 100644 --- a/aprsd/web/chat/static/js/send-message.js +++ b/aprsd/web/chat/static/js/send-message.js @@ -17,25 +17,24 @@ function reload_popovers() { } function build_location_string(msg) { - dt = new Date(parseInt(msg['lasttime']) * 1000); - loc = "Last Location Update: " + dt.toLocaleString(); - loc += "
Latitude: " + msg['lat'] + "
Longitude: " + msg['lon']; - loc += "
" + "Altitude: " + msg['altitude'] + " m"; - loc += "
" + "Speed: " + msg['speed'] + " kph"; - loc += "
" + "Bearing: " + msg['course'] + "°"; - loc += "
" + "distance: " + msg['distance'] + " km"; - return loc; + dt = new Date(parseInt(msg['lasttime']) * 1000); + loc = "Last Location Update: " + dt.toLocaleString(); + loc += "
Latitude: " + msg['lat'] + "
Longitude: " + msg['lon']; + loc += "
" + "Altitude: " + msg['altitude'] + " m"; + loc += "
" + "Speed: " + msg['speed'] + " kph"; + loc += "
" + "Bearing: " + msg['compass_bearing']; + loc += "
" + "distance: " + msg['distance'] + " km"; + return loc; } 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 += "Lat " + msg['lat'] + " Lon " + msg['lon']; - loc += "@" + msg['course'] + "°"; + loc += " " + msg['compass_bearing']; //loc += " Distance " + msg['distance'] + " km"; - loc += " " + dt.toLocaleString(); + //loc += " " + dt.toLocaleString(); + loc += " " + msg['timeago']; return loc; } @@ -346,9 +345,9 @@ function create_callsign_tab_content(callsign, active=false) { item_html += '
'; item_html += '
'; item_html += '
'; - item_html += ''; - item_html += ' '+location_str+'
'; + item_html += ''; + item_html += ' '+location_str+'
'; item_html += '
'; item_html += '
'; item_html += ''; diff --git a/requirements-dev.txt b/requirements-dev.txt index f741215..2683e49 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -34,7 +34,7 @@ imagesize==1.4.1 # via sphinx iniconfig==2.0.0 # via pytest isort==5.13.2 # via -r requirements-dev.in, gray 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 markupsafe==3.0.2 # via jinja2 mccabe==0.7.0 # via flake8 diff --git a/requirements.in b/requirements.in index e7b4e4d..0491d16 100644 --- a/requirements.in +++ b/requirements.in @@ -27,3 +27,4 @@ tzlocal update_checker wrapt pytz +timeago diff --git a/requirements.txt b/requirements.txt index ae25fb0..11c0bed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,8 +37,8 @@ markupsafe==3.0.2 # via jinja2, werkzeug marshmallow==3.23.1 # via dataclasses-json mypy-extensions==1.0.0 # via typing-inspect netaddr==1.3.0 # via oslo-config -oslo-config==9.6.0 # via -r requirements.in -oslo-i18n==6.4.0 # via oslo-config +oslo-config==9.7.0 # via -r requirements.in +oslo-i18n==6.5.0 # via oslo-config packaging==24.2 # via marshmallow pbr==6.1.0 # via oslo-i18n, stevedore 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 six==1.16.0 # via -r requirements.in 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 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-inspect==0.9.0 # via dataclasses-json tzlocal==5.2 # via -r requirements.in