structured callsign data
This commit is contained in:
parent
975f5ffdf0
commit
258e41669e
@ -157,13 +157,17 @@ PacketMessagePanel.prototype.pushMessage = function(msg) {
|
|||||||
if (msg.type && msg.type === 'thirdparty' && msg.data) {
|
if (msg.type && msg.type === 'thirdparty' && msg.data) {
|
||||||
msg = msg.data;
|
msg = msg.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
var source = msg.source;
|
var source = msg.source;
|
||||||
if (msg.type) {
|
var callsign;
|
||||||
if (msg.type === 'item') {
|
if ('object' in source) {
|
||||||
source = msg.item;
|
callsign = source.object;
|
||||||
}
|
} else if ('item' in source) {
|
||||||
if (msg.type === 'object') {
|
callsign = source.item;
|
||||||
source = msg.object;
|
} else {
|
||||||
|
callsign = source.callsign;
|
||||||
|
if ('ssid' in source) {
|
||||||
|
callsign += '-' + source.ssid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +206,7 @@ PacketMessagePanel.prototype.pushMessage = function(msg) {
|
|||||||
'style="' + stylesToString(styles) + '"'
|
'style="' + stylesToString(styles) + '"'
|
||||||
].join(' ');
|
].join(' ');
|
||||||
if (msg.lat && msg.lon) {
|
if (msg.lat && msg.lon) {
|
||||||
link = '<a ' + attrs + ' href="map?callsign=' + encodeURIComponent(source) + '" target="openwebrx-map">' + overlay + '</a>';
|
link = '<a ' + attrs + ' href="map?' + new URLSearchParams(source).toString() + '" target="openwebrx-map">' + overlay + '</a>';
|
||||||
} else {
|
} else {
|
||||||
link = '<div ' + attrs + '>' + overlay + '</div>'
|
link = '<div ' + attrs + '>' + overlay + '</div>'
|
||||||
}
|
}
|
||||||
@ -210,7 +214,7 @@ PacketMessagePanel.prototype.pushMessage = function(msg) {
|
|||||||
$b.append($(
|
$b.append($(
|
||||||
'<tr>' +
|
'<tr>' +
|
||||||
'<td>' + timestamp + '</td>' +
|
'<td>' + timestamp + '</td>' +
|
||||||
'<td class="callsign">' + source + '</td>' +
|
'<td class="callsign">' + callsign + '</td>' +
|
||||||
'<td class="coord">' + link + '</td>' +
|
'<td class="coord">' + link + '</td>' +
|
||||||
'<td class="message">' + (msg.comment || msg.message || '') + '</td>' +
|
'<td class="message">' + (msg.comment || msg.message || '') + '</td>' +
|
||||||
'</tr>'
|
'</tr>'
|
||||||
|
104
htdocs/map.js
104
htdocs/map.js
@ -1,17 +1,12 @@
|
|||||||
$(function(){
|
$(function(){
|
||||||
var query = window.location.search.replace(/^\?/, '').split('&').map(function(v){
|
var query = new URLSearchParams(window.location.search);
|
||||||
var s = v.split('=');
|
|
||||||
var r = {};
|
|
||||||
r[s[0]] = s.slice(1).join('=');
|
|
||||||
return r;
|
|
||||||
}).reduce(function(a, b){
|
|
||||||
return a.assign(b);
|
|
||||||
});
|
|
||||||
|
|
||||||
var expectedCallsign;
|
var expectedCallsign;
|
||||||
if (query.callsign) expectedCallsign = decodeURIComponent(query.callsign);
|
if (query.has('callsign')) {
|
||||||
|
expectedCallsign = Object.fromEntries(query.entries());
|
||||||
|
}
|
||||||
var expectedLocator;
|
var expectedLocator;
|
||||||
if (query.locator) expectedLocator = query.locator;
|
if (query.has('locator')) expectedLocator = query.get('locator');
|
||||||
|
|
||||||
var protocol = window.location.protocol.match(/https/) ? 'wss' : 'ws';
|
var protocol = window.location.protocol.match(/https/) ? 'wss' : 'ws';
|
||||||
|
|
||||||
@ -102,6 +97,11 @@ $(function(){
|
|||||||
return '<li class="square' + disabled + '" data-selector="' + key + '"><span class="illustration" style="background-color:' + chroma(value).alpha(fillOpacity) + ';border-color:' + chroma(value).alpha(strokeOpacity) + ';"></span>' + key + '</li>';
|
return '<li class="square' + disabled + '" data-selector="' + key + '"><span class="illustration" style="background-color:' + chroma(value).alpha(fillOpacity) + ';border-color:' + chroma(value).alpha(strokeOpacity) + ';"></span>' + key + '</li>';
|
||||||
});
|
});
|
||||||
$(".openwebrx-map-legend .content").html('<ul>' + lis.join('') + '</ul>');
|
$(".openwebrx-map-legend .content").html('<ul>' + lis.join('') + '</ul>');
|
||||||
|
};
|
||||||
|
|
||||||
|
var shallowEquals = function(obj1, obj2) {
|
||||||
|
// basic shallow object comparison
|
||||||
|
return Object.entries(obj1).sort().toString() === Object.entries(obj2).sort().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
var processUpdates = function(updates) {
|
var processUpdates = function(updates) {
|
||||||
@ -110,6 +110,7 @@ $(function(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updates.forEach(function(update){
|
updates.forEach(function(update){
|
||||||
|
var key = sourceToKey(update.source);
|
||||||
|
|
||||||
switch (update.location.type) {
|
switch (update.location.type) {
|
||||||
case 'latlon':
|
case 'latlon':
|
||||||
@ -123,33 +124,33 @@ $(function(){
|
|||||||
aprsOptions.course = update.location.course;
|
aprsOptions.course = update.location.course;
|
||||||
aprsOptions.speed = update.location.speed;
|
aprsOptions.speed = update.location.speed;
|
||||||
}
|
}
|
||||||
if (markers[update.callsign]) {
|
if (markers[key]) {
|
||||||
marker = markers[update.callsign];
|
marker = markers[key];
|
||||||
} else {
|
} else {
|
||||||
marker = new markerClass();
|
marker = new markerClass();
|
||||||
marker.addListener('click', function(){
|
marker.addListener('click', function(){
|
||||||
showMarkerInfoWindow(update.callsign, pos);
|
showMarkerInfoWindow(update.source, pos);
|
||||||
});
|
});
|
||||||
markers[update.callsign] = marker;
|
markers[key] = marker;
|
||||||
}
|
}
|
||||||
marker.setOptions($.extend({
|
marker.setOptions($.extend({
|
||||||
position: pos,
|
position: pos,
|
||||||
map: map,
|
map: map,
|
||||||
title: update.callsign
|
title: sourceToString(update.source)
|
||||||
}, aprsOptions, getMarkerOpacityOptions(update.lastseen) ));
|
}, aprsOptions, getMarkerOpacityOptions(update.lastseen) ));
|
||||||
marker.lastseen = update.lastseen;
|
marker.lastseen = update.lastseen;
|
||||||
marker.mode = update.mode;
|
marker.mode = update.mode;
|
||||||
marker.band = update.band;
|
marker.band = update.band;
|
||||||
marker.comment = update.location.comment;
|
marker.comment = update.location.comment;
|
||||||
|
|
||||||
if (expectedCallsign && expectedCallsign == update.callsign) {
|
if (expectedCallsign && shallowEquals(expectedCallsign, update.source)) {
|
||||||
map.panTo(pos);
|
map.panTo(pos);
|
||||||
showMarkerInfoWindow(update.callsign, pos);
|
showMarkerInfoWindow(update.source, pos);
|
||||||
expectedCallsign = false;
|
expectedCallsign = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (infowindow && infowindow.callsign && infowindow.callsign == update.callsign) {
|
if (infowindow && infowindow.source && shallowEquals(infowindow.source, update.source)) {
|
||||||
showMarkerInfoWindow(infowindow.callsign, pos);
|
showMarkerInfoWindow(infowindow.source, pos);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'locator':
|
case 'locator':
|
||||||
@ -160,15 +161,16 @@ $(function(){
|
|||||||
var rectangle;
|
var rectangle;
|
||||||
// the accessor is designed to work on the rectangle... but it should work on the update object, too
|
// the accessor is designed to work on the rectangle... but it should work on the update object, too
|
||||||
var color = getColor(colorAccessor(update));
|
var color = getColor(colorAccessor(update));
|
||||||
if (rectangles[update.callsign]) {
|
if (rectangles[key]) {
|
||||||
rectangle = rectangles[update.callsign];
|
rectangle = rectangles[key];
|
||||||
} else {
|
} else {
|
||||||
rectangle = new google.maps.Rectangle();
|
rectangle = new google.maps.Rectangle();
|
||||||
rectangle.addListener('click', function(){
|
rectangle.addListener('click', function(){
|
||||||
showLocatorInfoWindow(this.locator, this.center);
|
showLocatorInfoWindow(this.locator, this.center);
|
||||||
});
|
});
|
||||||
rectangles[update.callsign] = rectangle;
|
rectangles[key] = rectangle;
|
||||||
}
|
}
|
||||||
|
rectangle.source = update.source;
|
||||||
rectangle.lastseen = update.lastseen;
|
rectangle.lastseen = update.lastseen;
|
||||||
rectangle.locator = update.location.locator;
|
rectangle.locator = update.location.locator;
|
||||||
rectangle.mode = update.mode;
|
rectangle.mode = update.mode;
|
||||||
@ -188,13 +190,13 @@ $(function(){
|
|||||||
}
|
}
|
||||||
}, getRectangleOpacityOptions(update.lastseen) ));
|
}, getRectangleOpacityOptions(update.lastseen) ));
|
||||||
|
|
||||||
if (expectedLocator && expectedLocator == update.location.locator) {
|
if (expectedLocator && expectedLocator === update.location.locator) {
|
||||||
map.panTo(center);
|
map.panTo(center);
|
||||||
showLocatorInfoWindow(expectedLocator, center);
|
showLocatorInfoWindow(expectedLocator, center);
|
||||||
expectedLocator = false;
|
expectedLocator = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (infowindow && infowindow.locator && infowindow.locator == update.location.locator) {
|
if (infowindow && infowindow.locator && infowindow.locator === update.location.locator) {
|
||||||
showLocatorInfoWindow(infowindow.locator, center);
|
showLocatorInfoWindow(infowindow.locator, center);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -203,7 +205,7 @@ $(function(){
|
|||||||
};
|
};
|
||||||
|
|
||||||
var clearMap = function(){
|
var clearMap = function(){
|
||||||
var reset = function(callsign, item) { item.setMap(); };
|
var reset = function(_, item) { item.setMap(); };
|
||||||
$.each(markers, reset);
|
$.each(markers, reset);
|
||||||
$.each(rectangles, reset);
|
$.each(rectangles, reset);
|
||||||
receiverMarker.setMap();
|
receiverMarker.setMap();
|
||||||
@ -336,21 +338,35 @@ $(function(){
|
|||||||
infowindow = new google.maps.InfoWindow();
|
infowindow = new google.maps.InfoWindow();
|
||||||
google.maps.event.addListener(infowindow, 'closeclick', function() {
|
google.maps.event.addListener(infowindow, 'closeclick', function() {
|
||||||
delete infowindow.locator;
|
delete infowindow.locator;
|
||||||
delete infowindow.callsign;
|
delete infowindow.source;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
delete infowindow.locator;
|
delete infowindow.locator;
|
||||||
delete infowindow.callsign;
|
delete infowindow.source;
|
||||||
return infowindow;
|
return infowindow;
|
||||||
};
|
};
|
||||||
|
|
||||||
var linkifyCallsign = function(callsign) {
|
var sourceToKey = function(source) {
|
||||||
if ((callsign_url == null) || (callsign_url == ''))
|
// special treatment for special entities
|
||||||
return callsign;
|
// not just for display but also in key treatment in order not to overlap with other locations sent by the same callsign
|
||||||
|
if ('item' in source) return source['item'];
|
||||||
|
if ('object' in source) return source['object'];
|
||||||
|
var key = source.callsign;
|
||||||
|
if ('ssid' in source) key += '-' + source.ssid;
|
||||||
|
return key;
|
||||||
|
};
|
||||||
|
|
||||||
|
// we can reuse the same logic for displaying and indexing
|
||||||
|
var sourceToString = sourceToKey;
|
||||||
|
|
||||||
|
var linkifySource = function(source) {
|
||||||
|
var callsignString = sourceToString(source);
|
||||||
|
if (callsign_url == null || callsign_url === '')
|
||||||
|
return callsignString;
|
||||||
else
|
else
|
||||||
return '<a target="callsign_info" href="' +
|
return '<a target="callsign_info" href="' +
|
||||||
callsign_url.replaceAll('{}', callsign.replace(new RegExp('-.*$'), '')) +
|
callsign_url.replaceAll('{}', source.callsign) +
|
||||||
'">' + callsign + '</a>';
|
'">' + callsignString + '</a>';
|
||||||
};
|
};
|
||||||
|
|
||||||
var distanceKm = function(p1, p2) {
|
var distanceKm = function(p1, p2) {
|
||||||
@ -374,10 +390,8 @@ $(function(){
|
|||||||
var showLocatorInfoWindow = function(locator, pos) {
|
var showLocatorInfoWindow = function(locator, pos) {
|
||||||
var infowindow = getInfoWindow();
|
var infowindow = getInfoWindow();
|
||||||
infowindow.locator = locator;
|
infowindow.locator = locator;
|
||||||
var inLocator = $.map(rectangles, function(r, callsign) {
|
var inLocator = Object.values(rectangles).filter(rectangleFilter).filter(function(d) {
|
||||||
return {callsign: callsign, locator: r.locator, lastseen: r.lastseen, mode: r.mode, band: r.band}
|
return d.locator === locator;
|
||||||
}).filter(rectangleFilter).filter(function(d) {
|
|
||||||
return d.locator == locator;
|
|
||||||
}).sort(function(a, b){
|
}).sort(function(a, b){
|
||||||
return b.lastseen - a.lastseen;
|
return b.lastseen - a.lastseen;
|
||||||
});
|
});
|
||||||
@ -389,7 +403,7 @@ $(function(){
|
|||||||
'<ul>' +
|
'<ul>' +
|
||||||
inLocator.map(function(i){
|
inLocator.map(function(i){
|
||||||
var timestring = moment(i.lastseen).fromNow();
|
var timestring = moment(i.lastseen).fromNow();
|
||||||
var message = linkifyCallsign(i.callsign) + ' (' + timestring + ' using ' + i.mode;
|
var message = linkifySource(i.source) + ' (' + timestring + ' using ' + i.mode;
|
||||||
if (i.band) message += ' on ' + i.band;
|
if (i.band) message += ' on ' + i.band;
|
||||||
message += ')';
|
message += ')';
|
||||||
return '<li>' + message + '</li>'
|
return '<li>' + message + '</li>'
|
||||||
@ -400,10 +414,10 @@ $(function(){
|
|||||||
infowindow.open(map);
|
infowindow.open(map);
|
||||||
};
|
};
|
||||||
|
|
||||||
var showMarkerInfoWindow = function(callsign, pos) {
|
var showMarkerInfoWindow = function(source, pos) {
|
||||||
var infowindow = getInfoWindow();
|
var infowindow = getInfoWindow();
|
||||||
infowindow.callsign = callsign;
|
infowindow.source = source;
|
||||||
var marker = markers[callsign];
|
var marker = markers[sourceToKey(source)];
|
||||||
var timestring = moment(marker.lastseen).fromNow();
|
var timestring = moment(marker.lastseen).fromNow();
|
||||||
var commentString = "";
|
var commentString = "";
|
||||||
var distance = "";
|
var distance = "";
|
||||||
@ -414,7 +428,7 @@ $(function(){
|
|||||||
distance = " at " + distanceKm(receiverMarker.position, marker.position) + " km";
|
distance = " at " + distanceKm(receiverMarker.position, marker.position) + " km";
|
||||||
}
|
}
|
||||||
infowindow.setContent(
|
infowindow.setContent(
|
||||||
'<h3>' + linkifyCallsign(callsign) + distance + '</h3>' +
|
'<h3>' + linkifySource(source) + distance + '</h3>' +
|
||||||
'<div>' + timestring + ' using ' + marker.mode + ( marker.band ? ' on ' + marker.band : '' ) + '</div>' +
|
'<div>' + timestring + ' using ' + marker.mode + ( marker.band ? ' on ' + marker.band : '' ) + '</div>' +
|
||||||
commentString
|
commentString
|
||||||
);
|
);
|
||||||
@ -457,19 +471,19 @@ $(function(){
|
|||||||
// fade out / remove positions after time
|
// fade out / remove positions after time
|
||||||
setInterval(function(){
|
setInterval(function(){
|
||||||
var now = new Date().getTime();
|
var now = new Date().getTime();
|
||||||
$.each(rectangles, function(callsign, m) {
|
Object.values(rectangles).forEach(function(m){
|
||||||
var age = now - m.lastseen;
|
var age = now - m.lastseen;
|
||||||
if (age > retention_time) {
|
if (age > retention_time) {
|
||||||
delete rectangles[callsign];
|
delete rectangles[sourceToKey(m.source)];
|
||||||
m.setMap();
|
m.setMap();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m.setOptions(getRectangleOpacityOptions(m.lastseen));
|
m.setOptions(getRectangleOpacityOptions(m.lastseen));
|
||||||
});
|
});
|
||||||
$.each(markers, function(callsign, m) {
|
Object.values(markers).forEach(function(m) {
|
||||||
var age = now - m.lastseen;
|
var age = now - m.lastseen;
|
||||||
if (age > retention_time) {
|
if (age > retention_time) {
|
||||||
delete markers[callsign];
|
delete markers[sourceToKey(m.source)];
|
||||||
m.setMap();
|
m.setMap();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ thirdpartyeRegex = re.compile("^([a-zA-Z0-9-]+)>((([a-zA-Z0-9-]+\\*?,)*)([a-zA-Z
|
|||||||
messageIdRegex = re.compile("^(.*){([0-9]{1,5})$")
|
messageIdRegex = re.compile("^(.*){([0-9]{1,5})$")
|
||||||
|
|
||||||
# regex to filter pseudo "WIDE" path elements
|
# regex to filter pseudo "WIDE" path elements
|
||||||
widePattern = re.compile("^WIDE[0-9]-[0-9]$")
|
widePattern = re.compile("^WIDE[0-9]$")
|
||||||
|
|
||||||
|
|
||||||
def decodeBase91(input):
|
def decodeBase91(input):
|
||||||
@ -67,12 +67,13 @@ class Ax25Parser(PickleModule):
|
|||||||
logger.exception("error parsing ax25 frame")
|
logger.exception("error parsing ax25 frame")
|
||||||
|
|
||||||
def extractCallsign(self, input):
|
def extractCallsign(self, input):
|
||||||
cs = bytes([b >> 1 for b in input[0:6]]).decode(encoding, "replace").strip()
|
cs = {
|
||||||
|
"callsign": bytes([b >> 1 for b in input[0:6]]).decode(encoding, "replace").strip(),
|
||||||
|
}
|
||||||
ssid = (input[6] & 0b00011110) >> 1
|
ssid = (input[6] & 0b00011110) >> 1
|
||||||
if ssid > 0:
|
if ssid > 0:
|
||||||
return "{callsign}-{ssid}".format(callsign=cs, ssid=ssid)
|
cs["ssid"] = ssid
|
||||||
else:
|
return cs
|
||||||
return cs
|
|
||||||
|
|
||||||
|
|
||||||
class WeatherMapping(object):
|
class WeatherMapping(object):
|
||||||
@ -178,7 +179,7 @@ class AprsParser(PickleModule):
|
|||||||
|
|
||||||
def isDirect(self, aprsData):
|
def isDirect(self, aprsData):
|
||||||
if "path" in aprsData and len(aprsData["path"]) > 0:
|
if "path" in aprsData and len(aprsData["path"]) > 0:
|
||||||
hops = [host for host in aprsData["path"] if widePattern.match(host) is None]
|
hops = [host for host in aprsData["path"] if widePattern.match(host["callsign"]) is None]
|
||||||
if len(hops) > 0:
|
if len(hops) > 0:
|
||||||
return False
|
return False
|
||||||
if "type" in aprsData and aprsData["type"] in ["thirdparty", "item", "object"]:
|
if "type" in aprsData and aprsData["type"] in ["thirdparty", "item", "object"]:
|
||||||
@ -207,12 +208,13 @@ class AprsParser(PickleModule):
|
|||||||
mapData = mapData["data"]
|
mapData = mapData["data"]
|
||||||
if "lat" in mapData and "lon" in mapData:
|
if "lat" in mapData and "lon" in mapData:
|
||||||
loc = AprsLocation(mapData)
|
loc = AprsLocation(mapData)
|
||||||
source = mapData["source"]
|
source = mapData["source"].copy()
|
||||||
|
# these are special packets, sent on behalf of other entities
|
||||||
if "type" in mapData:
|
if "type" in mapData:
|
||||||
if mapData["type"] == "item":
|
if mapData["type"] == "item" and "item" in mapData:
|
||||||
source = mapData["item"]
|
source["item"] = mapData["item"]
|
||||||
elif mapData["type"] == "object":
|
elif mapData["type"] == "object" and "object" in mapData:
|
||||||
source = mapData["object"]
|
source["object"] = mapData["object"]
|
||||||
Map.getSharedInstance().updateLocation(source, loc, "APRS", self.band)
|
Map.getSharedInstance().updateLocation(source, loc, "APRS", self.band)
|
||||||
|
|
||||||
def hasCompressedCoordinates(self, raw):
|
def hasCompressedCoordinates(self, raw):
|
||||||
@ -345,15 +347,24 @@ class AprsParser(PickleModule):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def parseThirdpartyAprsData(self, information):
|
def parseThirdpartyAprsData(self, information):
|
||||||
|
# in thirdparty packets, the callsign is passed as a string with -SSID suffix...
|
||||||
|
# this seems to be the only case where parsing is necessary, hence this function is inline
|
||||||
|
def parseCallsign(callsign):
|
||||||
|
el = callsign.split('-')
|
||||||
|
result = {"callsign": el[0]}
|
||||||
|
if len(el) > 1:
|
||||||
|
result["ssid"] = int(el[1])
|
||||||
|
return result
|
||||||
|
|
||||||
matches = thirdpartyeRegex.match(information)
|
matches = thirdpartyeRegex.match(information)
|
||||||
if matches:
|
if matches:
|
||||||
path = matches.group(2).split(",")
|
path = matches.group(2).split(",")
|
||||||
destination = next((c.strip("*").upper() for c in path if c.endswith("*")), None)
|
destination = next((c.strip("*").upper() for c in path if c.endswith("*")), None)
|
||||||
data = self.parseAprsData(
|
data = self.parseAprsData(
|
||||||
{
|
{
|
||||||
"source": matches.group(1).upper(),
|
"source": parseCallsign(matches.group(1).upper()),
|
||||||
"destination": destination,
|
"destination": parseCallsign(destination),
|
||||||
"path": path,
|
"path": [parseCallsign(c) for c in path],
|
||||||
"data": matches.group(6).encode(encoding),
|
"data": matches.group(6).encode(encoding),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -531,7 +542,7 @@ class MicEParser(object):
|
|||||||
|
|
||||||
def parse(self, data):
|
def parse(self, data):
|
||||||
information = data["data"]
|
information = data["data"]
|
||||||
destination = data["destination"]
|
destination = data["destination"]["callsign"]
|
||||||
|
|
||||||
rawLatitude = [self.extractNumber(c) for c in destination[0:6]]
|
rawLatitude = [self.extractNumber(c) for c in destination[0:6]]
|
||||||
lat = self.listToNumber(rawLatitude[0:2]) + self.listToNumber(rawLatitude[2:6]) / 6000
|
lat = self.listToNumber(rawLatitude[0:2]) + self.listToNumber(rawLatitude[2:6]) / 6000
|
||||||
|
@ -103,11 +103,11 @@ class Js8Parser(AudioChopperParser):
|
|||||||
|
|
||||||
if (isinstance(frame, Js8FrameHeartbeat) or isinstance(frame, Js8FrameCompound)) and frame.grid:
|
if (isinstance(frame, Js8FrameHeartbeat) or isinstance(frame, Js8FrameCompound)) and frame.grid:
|
||||||
Map.getSharedInstance().updateLocation(
|
Map.getSharedInstance().updateLocation(
|
||||||
frame.callsign, LocatorLocation(frame.grid), "JS8", band
|
frame.source, LocatorLocation(frame.grid), "JS8", band
|
||||||
)
|
)
|
||||||
ReportingEngine.getSharedInstance().spot(
|
ReportingEngine.getSharedInstance().spot(
|
||||||
{
|
{
|
||||||
"callsign": frame.callsign,
|
"source": frame.source,
|
||||||
"mode": "JS8",
|
"mode": "JS8",
|
||||||
"locator": frame.grid,
|
"locator": frame.grid,
|
||||||
"freq": freq + frame.freq,
|
"freq": freq + frame.freq,
|
||||||
|
35
owrx/map.py
35
owrx/map.py
@ -61,13 +61,13 @@ class Map(object):
|
|||||||
client.write_update(
|
client.write_update(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"callsign": callsign,
|
"source": record["source"],
|
||||||
"location": record["location"].__dict__(),
|
"location": record["location"].__dict__(),
|
||||||
"lastseen": record["updated"].timestamp() * 1000,
|
"lastseen": record["updated"].timestamp() * 1000,
|
||||||
"mode": record["mode"],
|
"mode": record["mode"],
|
||||||
"band": record["band"].getName() if record["band"] is not None else None,
|
"band": record["band"].getName() if record["band"] is not None else None,
|
||||||
}
|
}
|
||||||
for (callsign, record) in self.positions.items()
|
for record in self.positions.values()
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -77,14 +77,20 @@ class Map(object):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def updateLocation(self, callsign, loc: Location, mode: str, band: Band = None):
|
def _sourceToKey(self, source):
|
||||||
|
if "ssid" in source:
|
||||||
|
return "{callsign}-{ssid}".format(**source)
|
||||||
|
return source["callsign"]
|
||||||
|
|
||||||
|
def updateLocation(self, source, loc: Location, mode: str, band: Band = None):
|
||||||
ts = datetime.now()
|
ts = datetime.now()
|
||||||
|
key = self._sourceToKey(source)
|
||||||
with self.positionsLock:
|
with self.positionsLock:
|
||||||
self.positions[callsign] = {"location": loc, "updated": ts, "mode": mode, "band": band}
|
self.positions[key] = {"source": source, "location": loc, "updated": ts, "mode": mode, "band": band}
|
||||||
self.broadcast(
|
self.broadcast(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"callsign": callsign,
|
"source": source,
|
||||||
"location": loc.__dict__(),
|
"location": loc.__dict__(),
|
||||||
"lastseen": ts.timestamp() * 1000,
|
"lastseen": ts.timestamp() * 1000,
|
||||||
"mode": mode,
|
"mode": mode,
|
||||||
@ -93,17 +99,18 @@ class Map(object):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def touchLocation(self, callsign):
|
def touchLocation(self, source):
|
||||||
# not implemented on the client side yet, so do not use!
|
# not implemented on the client side yet, so do not use!
|
||||||
ts = datetime.now()
|
ts = datetime.now()
|
||||||
|
key = self._sourceToKey(source)
|
||||||
with self.positionsLock:
|
with self.positionsLock:
|
||||||
if callsign in self.positions:
|
if key in self.positions:
|
||||||
self.positions[callsign]["updated"] = ts
|
self.positions[key]["updated"] = ts
|
||||||
self.broadcast([{"callsign": callsign, "lastseen": ts.timestamp() * 1000}])
|
self.broadcast([{"source": source, "lastseen": ts.timestamp() * 1000}])
|
||||||
|
|
||||||
def removeLocation(self, callsign):
|
def removeLocation(self, key):
|
||||||
with self.positionsLock:
|
with self.positionsLock:
|
||||||
del self.positions[callsign]
|
del self.positions[key]
|
||||||
# TODO broadcast removal to clients
|
# TODO broadcast removal to clients
|
||||||
|
|
||||||
def removeOldPositions(self):
|
def removeOldPositions(self):
|
||||||
@ -111,9 +118,9 @@ class Map(object):
|
|||||||
retention = timedelta(seconds=pm["map_position_retention_time"])
|
retention = timedelta(seconds=pm["map_position_retention_time"])
|
||||||
cutoff = datetime.now() - retention
|
cutoff = datetime.now() - retention
|
||||||
|
|
||||||
to_be_removed = [callsign for (callsign, pos) in self.positions.items() if pos["updated"] < cutoff]
|
to_be_removed = [key for (key, pos) in self.positions.items() if pos["updated"] < cutoff]
|
||||||
for callsign in to_be_removed:
|
for key in to_be_removed:
|
||||||
self.removeLocation(callsign)
|
self.removeLocation(key)
|
||||||
|
|
||||||
def rebuildPositions(self):
|
def rebuildPositions(self):
|
||||||
logger.debug("rebuilding map storage; size before: %i", sys.getsizeof(self.positions))
|
logger.debug("rebuilding map storage; size before: %i", sys.getsizeof(self.positions))
|
||||||
|
@ -129,7 +129,7 @@ class DigihamEnricher(Enricher, metaclass=ABCMeta):
|
|||||||
callsign = self.getCallsign(meta)
|
callsign = self.getCallsign(meta)
|
||||||
if callsign is not None and "lat" in meta and "lon" in meta:
|
if callsign is not None and "lat" in meta and "lon" in meta:
|
||||||
loc = LatLngLocation(meta["lat"], meta["lon"])
|
loc = LatLngLocation(meta["lat"], meta["lon"])
|
||||||
Map.getSharedInstance().updateLocation(callsign, loc, mode, self.parser.getBand())
|
Map.getSharedInstance().updateLocation({"callsign": callsign}, loc, mode, self.parser.getBand())
|
||||||
return meta
|
return meta
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -202,7 +202,7 @@ class DStarEnricher(DigihamEnricher):
|
|||||||
if "ourcall" in meta:
|
if "ourcall" in meta:
|
||||||
# send location info to map as well (it will show up with the correct symbol there!)
|
# send location info to map as well (it will show up with the correct symbol there!)
|
||||||
loc = AprsLocation(data)
|
loc = AprsLocation(data)
|
||||||
Map.getSharedInstance().updateLocation(meta["ourcall"], loc, "DPRS", self.parser.getBand())
|
Map.getSharedInstance().updateLocation({"callsign": meta["ourcall"]}, loc, "DPRS", self.parser.getBand())
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Error while parsing DPRS data")
|
logger.exception("Error while parsing DPRS data")
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class PskReporter(Reporter):
|
|||||||
self.timer.start()
|
self.timer.start()
|
||||||
|
|
||||||
def spotEquals(self, s1, s2):
|
def spotEquals(self, s1, s2):
|
||||||
keys = ["callsign", "timestamp", "locator", "mode", "msg"]
|
keys = ["source", "timestamp", "locator", "mode", "msg"]
|
||||||
|
|
||||||
return reduce(and_, map(lambda key: s1[key] == s2[key], keys))
|
return reduce(and_, map(lambda key: s1[key] == s2[key], keys))
|
||||||
|
|
||||||
@ -141,7 +141,7 @@ class Uploader(object):
|
|||||||
def encodeSpot(self, spot):
|
def encodeSpot(self, spot):
|
||||||
try:
|
try:
|
||||||
return bytes(
|
return bytes(
|
||||||
self.encodeString(spot["callsign"])
|
self.encodeString(spot["source"]["callsign"])
|
||||||
+ list(int(spot["freq"]).to_bytes(4, "big"))
|
+ list(int(spot["freq"]).to_bytes(4, "big"))
|
||||||
+ list(int(spot["db"]).to_bytes(1, "big", signed=True))
|
+ list(int(spot["db"]).to_bytes(1, "big", signed=True))
|
||||||
+ self.encodeString(spot["mode"])
|
+ self.encodeString(spot["mode"])
|
||||||
|
@ -56,7 +56,7 @@ class Worker(threading.Thread):
|
|||||||
# FST4W does not have drift
|
# FST4W does not have drift
|
||||||
"drift": spot["drift"] if "drift" in spot else 0,
|
"drift": spot["drift"] if "drift" in spot else 0,
|
||||||
"tqrg": spot["freq"] / 1e6,
|
"tqrg": spot["freq"] / 1e6,
|
||||||
"tcall": spot["callsign"],
|
"tcall": spot["source"]["callsign"],
|
||||||
"tgrid": spot["locator"],
|
"tgrid": spot["locator"],
|
||||||
"dbm": spot["dbm"],
|
"dbm": spot["dbm"],
|
||||||
"version": openwebrx_version,
|
"version": openwebrx_version,
|
||||||
|
10
owrx/wsjt.py
10
owrx/wsjt.py
@ -276,9 +276,9 @@ class WsjtParser(AudioChopperParser):
|
|||||||
out["interval"] = profile.getInterval()
|
out["interval"] = profile.getInterval()
|
||||||
|
|
||||||
self.pushDecode(mode, band)
|
self.pushDecode(mode, band)
|
||||||
if "callsign" in out and "locator" in out:
|
if "source" in out and "locator" in out:
|
||||||
Map.getSharedInstance().updateLocation(
|
Map.getSharedInstance().updateLocation(
|
||||||
out["callsign"], LocatorLocation(out["locator"]), mode, band
|
out["source"], LocatorLocation(out["locator"]), mode, band
|
||||||
)
|
)
|
||||||
ReportingEngine.getSharedInstance().spot(out)
|
ReportingEngine.getSharedInstance().spot(out)
|
||||||
|
|
||||||
@ -342,8 +342,8 @@ class QsoMessageParser(MessageParser):
|
|||||||
# this is a valid locator in theory, but it's somewhere in the arctic ocean, near the north pole, so it's very
|
# this is a valid locator in theory, but it's somewhere in the arctic ocean, near the north pole, so it's very
|
||||||
# likely this just means roger roger goodbye.
|
# likely this just means roger roger goodbye.
|
||||||
if m.group(3) == "RR73":
|
if m.group(3) == "RR73":
|
||||||
return {"callsign": m.group(1)}
|
return {"source": {"callsign": m.group(1)}}
|
||||||
return {"callsign": m.group(1), "locator": m.group(3)}
|
return {"source": {"callsign": m.group(1)}, "locator": m.group(3)}
|
||||||
|
|
||||||
|
|
||||||
# Used in propagation reporting / beacon modes (WSPR / FST4W)
|
# Used in propagation reporting / beacon modes (WSPR / FST4W)
|
||||||
@ -354,7 +354,7 @@ class BeaconMessageParser(MessageParser):
|
|||||||
m = BeaconMessageParser.wspr_splitter_pattern.match(msg)
|
m = BeaconMessageParser.wspr_splitter_pattern.match(msg)
|
||||||
if m is None:
|
if m is None:
|
||||||
return {}
|
return {}
|
||||||
return {"callsign": m.group(1), "locator": m.group(2), "dbm": m.group(3)}
|
return {"source": {"callsign": m.group(1)}, "locator": m.group(2), "dbm": m.group(3)}
|
||||||
|
|
||||||
|
|
||||||
class Jt9Decoder(Decoder):
|
class Jt9Decoder(Decoder):
|
||||||
|
Loading…
Reference in New Issue
Block a user