diff --git a/owrx/aprs/__init__.py b/owrx/aprs/__init__.py index 31a12ba6..b2facc05 100644 --- a/owrx/aprs/__init__.py +++ b/owrx/aprs/__init__.py @@ -32,8 +32,8 @@ thirdpartyeRegex = re.compile("^([a-zA-Z0-9-]+)>((([a-zA-Z0-9-]+\\*?,)*)([a-zA-Z # regex for getting the message id out of message messageIdRegex = re.compile("^(.*){([0-9]{1,5})$") -# regex to filter pseudo path elements -widePattern = re.compile("^(ECHO|RELAY|TRACE|GATE|WIDE[0-9]?(-[0-9])?)$") +# regex to filter pseudo path elements and anything without asterisk +noHopPattern = re.compile("^(.*[^*]|WIDE[0-9]?(-[0-9])?|ECHO|RELAY|TRACE|GATE)$") def decodeBase91(input): @@ -67,12 +67,15 @@ class Ax25Parser(PickleModule): logger.exception("error parsing ax25 frame") def extractCallsign(self, input): + # extract callsign and SSID cs = bytes([b >> 1 for b in input[0:6]]).decode(encoding, "replace").strip() ssid = (input[6] & 0b00011110) >> 1 + # add asterisks to traversed callsigns + done = "*" if (input[6] & 0b10000000) is not 0 else "" if ssid > 0: - return "{callsign}-{ssid}".format(callsign=cs, ssid=ssid) + return "{callsign}-{ssid}{done}".format(callsign=cs, ssid=ssid, done=done) else: - return cs + return "{callsign}{done}".format(callsign=cs, done=done) class WeatherMapping(object): @@ -177,25 +180,22 @@ class AprsParser(PickleModule): return self.metrics[category] def isDirect(self, aprsData): - if "source" in aprsData and aprsData["source"] == "AIS": - return True; - if "path" in aprsData and len(aprsData["path"]) > 0: - hops = [host for host in aprsData["path"] if widePattern.match(host) is None] - if len(hops) > 0: - return False - if "type" in aprsData and aprsData["type"] in ["thirdparty", "item", "object"]: - return False - return True + return len(self.getPath(aprsData)) == 0 def getPath(self, aprsData): path = [] if "source" in aprsData: + # AIS reports have no path if aprsData["source"] == "AIS": return path; + # encapsulated messages' path starts with the source callsign if "type" in aprsData and aprsData["type"] in ["thirdparty", "item", "object"]: - path += [ aprsData["source"] ] + path += [ aprsData["source"].replace("*","") ] + # filter out special aliases and anything without asterisk if "path" in aprsData and len(aprsData["path"]) > 0: - path += [host for host in aprsData["path"] if widePattern.match(host) is None] + path += [hop.replace("*","") for hop in aprsData["path"] + if not noHopPattern.match(hop)] + # return path with all the asterisks removed return path def process(self, data): @@ -220,12 +220,11 @@ class AprsParser(PickleModule): def updateMap(self, mapData): mode = mapData["mode"] if "mode" in mapData else "APRS" path = self.getPath(mapData) - direct = self.isDirect(mapData) if "type" in mapData and mapData["type"] == "thirdparty" and "data" in mapData: mapData = mapData["data"] if "lat" in mapData and "lon" in mapData: loc = AprsLocation(mapData) - source = mapData["source"] + source = mapData["source"].replace("*","") if "type" in mapData: if mapData["type"] == "item": source = mapData["item"] diff --git a/owrx/config/defaults.py b/owrx/config/defaults.py index 5ed682a8..029e762c 100644 --- a/owrx/config/defaults.py +++ b/owrx/config/defaults.py @@ -161,6 +161,7 @@ defaultConfig = PropertyLayer( squelch_auto_margin=10, google_maps_api_key="", map_position_retention_time=2 * 60 * 60, + map_prefer_recent_reports=True, callsign_url="https://www.qrzcq.com/call/{}", vessel_url="https://www.vesselfinder.com/vessels/details/{}", usage_policy_url="policy", diff --git a/owrx/connection.py b/owrx/connection.py index 98e65bd5..709dfe47 100644 --- a/owrx/connection.py +++ b/owrx/connection.py @@ -459,6 +459,7 @@ class MapConnection(OpenWebRxClient): "google_maps_api_key", "receiver_gps", "map_position_retention_time", + "map_prefer_recent_reports", "callsign_url", "vessel_url", "receiver_name", diff --git a/owrx/controllers/settings/general.py b/owrx/controllers/settings/general.py index 4697960e..b5a378b9 100644 --- a/owrx/controllers/settings/general.py +++ b/owrx/controllers/settings/general.py @@ -200,6 +200,10 @@ class GeneralSettingsController(SettingsFormController): infotext="Specifies how log markers / grids will remain visible on the map", append="s", ), + CheckboxInput( + "map_prefer_recent_reports", + "Prefer more recent position reports to shorter path reports", + ), TextInput( "callsign_url", "Callsign database URL", diff --git a/owrx/map.py b/owrx/map.py index 2f1d1210..8a72e7f6 100644 --- a/owrx/map.py +++ b/owrx/map.py @@ -79,12 +79,14 @@ class Map(object): pass def updateLocation(self, callsign, loc: Location, mode: str, band: Band = None, path: list[str] = []): + pm = Config.get() + preferRecent = pm["map_prefer_recent_reports"] needBroadcast = False ts = datetime.now() with self.positionsLock: - # Ignore indirect messages if there is a prior direct message - if len(path)>0 or callsign not in self.positions or not self.positions[callsign]["direct"]: + # prefer messages with shorter path unless preferRecent set + if preferRecent or callsign not in self.positions or len(path) <= len(self.positions[callsign]["path"]): self.positions[callsign] = {"location": loc, "updated": ts, "mode": mode, "band": band, "path": path } needBroadcast = True