diff --git a/CHANGELOG.md b/CHANGELOG.md index 440618e..0a6b7d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # CHANGELOG ## not released +- parser: Added optionally distance calculation (fixes #86) - parser: Added support for weather data from FANET ground stations - parser: Added support for latency in receiver messages (OGNSDR) (fixes #87) - parser: Added support for reference_timestamp with tzinfo (fixes #84) diff --git a/ogn/parser/parse.py b/ogn/parser/parse.py index bc23ca6..c420810 100644 --- a/ogn/parser/parse.py +++ b/ogn/parser/parse.py @@ -1,7 +1,7 @@ import re from datetime import datetime -from ogn.parser.utils import createTimestamp, parseAngle, KNOTS_TO_MS, KPH_TO_MS, FEETS_TO_METER, fahrenheit_to_celsius +from ogn.parser.utils import createTimestamp, parseAngle, KNOTS_TO_MS, KPH_TO_MS, FEETS_TO_METER, fahrenheit_to_celsius, CheapRuler from ogn.parser.pattern import PATTERN_APRS, PATTERN_APRS_POSITION, PATTERN_APRS_POSITION_WEATHER, PATTERN_APRS_STATUS, PATTERN_SERVER from ogn.parser.exceptions import AprsParseError @@ -18,8 +18,12 @@ from ogn.parser.aprs_comment.spot_parser import SpotParser from ogn.parser.aprs_comment.inreach_parser import InreachParser from ogn.parser.aprs_comment.generic_parser import GenericParser +positions = {} + + +def parse(aprs_message, reference_timestamp=None, calculate_distances=False): + global positions -def parse(aprs_message, reference_timestamp=None): if reference_timestamp is None: reference_timestamp = datetime.utcnow() @@ -28,6 +32,13 @@ def parse(aprs_message, reference_timestamp=None): message.update(parse_comment(message['comment'], dstcall=message['dstcall'], aprs_type=message['aprs_type'])) + + if message['aprs_type'].startswith('position') and calculate_distances is True: + positions[message['name']] = (message['longitude'], message['latitude']) + if message['receiver_name'] in positions: + cheap_ruler = CheapRuler((message['latitude'] + positions[message['receiver_name']][1]) / 2.0) + message['distance'] = cheap_ruler.distance((message['longitude'], message['latitude']), positions[message['receiver_name']]) + return message diff --git a/ogn/parser/utils.py b/ogn/parser/utils.py index e7dc014..b4733db 100644 --- a/ogn/parser/utils.py +++ b/ogn/parser/utils.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta, timezone +import math FEETS_TO_METER = 0.3048 # ratio feets to meter FPM_TO_MS = FEETS_TO_METER / 60 # ratio fpm to m/s @@ -52,3 +53,24 @@ def createTimestamp(time_string, reference_timestamp): result += timedelta(days=1) return result + + +class CheapRuler(): + """Extreme fast distance calculating for distances below 500km.""" + + def __init__(self, lat): + c = math.cos(lat * 3.14159265359 / 180) + c2 = 2 * c * c - 1 + c3 = 2 * c * c2 - c + c4 = 2 * c * c3 - c2 + c5 = 2 * c * c4 - c3 + + self.kx = 1000 * (111.41513 * c - 0.09455 * c3 + 0.00012 * c5) # longitude correction + self.ky = 1000 * (111.13209 - 0.56605 * c2 + 0.0012 * c4) # latitude correction + + def distance(self, a, b): + """Points a and b are from tuple(lon,lat).""" + + dx = (a[0] - b[0]) * self.kx + dy = (a[1] - b[1]) * self.ky + return math.sqrt(dx * dx + dy * dy) diff --git a/tests/parser/test_utils.py b/tests/parser/test_utils.py index 01b011c..f038b0a 100644 --- a/tests/parser/test_utils.py +++ b/tests/parser/test_utils.py @@ -1,7 +1,7 @@ import unittest from datetime import datetime, timezone -from ogn.parser.utils import parseAngle, createTimestamp +from ogn.parser.utils import parseAngle, createTimestamp, CheapRuler class TestStringMethods(unittest.TestCase): @@ -40,6 +40,14 @@ class TestStringMethods(unittest.TestCase): self.proceed_test_data(test_data) + def test_cheap_ruler(self): + koenigsdf = (11.465353, 47.829825) + hochkoenig = (13.062405, 47.420516) + + cheap_ruler = CheapRuler((koenigsdf[1] + hochkoenig[1]) / 2) + distance = cheap_ruler.distance(koenigsdf, hochkoenig) + self.assertEqual(distance, 128381.47612138899) + if __name__ == '__main__': unittest.main()