diff --git a/htdocs/css/map.css b/htdocs/css/map.css index 70702b96..8d78995e 100644 --- a/htdocs/css/map.css +++ b/htdocs/css/map.css @@ -58,6 +58,17 @@ ul { border-style: solid; } +.openwebrx-map-legend li.square .feature { + display: inline-block; + width: 20px; + height: 20px; + margin-right: 10px; + border-width: 0; + border-style: none; + font-size: 24px; + text-alignment: center; +} + .openwebrx-map-legend select { background-color: #FFF; border-color: #DDD; diff --git a/htdocs/lib/FeatureMarker.js b/htdocs/lib/FeatureMarker.js new file mode 100644 index 00000000..4003923b --- /dev/null +++ b/htdocs/lib/FeatureMarker.js @@ -0,0 +1,58 @@ +function FeatureMarker() {} + +FeatureMarker.prototype = new google.maps.OverlayView(); + +FeatureMarker.prototype.draw = function() { + var div = this.div; + if (!div) return; + + div.style.color = this.color? this.color : '#000000'; + div.innerHTML = this.symbol? this.symbol : '●'; + + var point = this.getProjection().fromLatLngToDivPixel(this.position); + if (point) { + div.style.left = point.x - this.symWidth/2 + 'px'; + div.style.top = point.y - this.symHeight/2 + 'px'; + } +}; + +FeatureMarker.prototype.setOptions = function(options) { + google.maps.OverlayView.prototype.setOptions.apply(this, arguments); + this.draw(); +}; + +FeatureMarker.prototype.onAdd = function() { + var div = this.div = document.createElement('div'); + + // Marker size + this.symWidth = 16; + this.symHeight = 16; + + div.style.position = 'absolute'; + div.style.cursor = 'pointer'; + div.style.width = this.symWidth + 'px'; + div.style.height = this.symHeight + 'px'; + div.style.textAlign = 'center'; + div.style.fontSize = this.symHeight + 'px'; + div.style.lineHeight = this.symHeight + 'px'; + + var self = this; + google.maps.event.addDomListener(div, "click", function(event) { + event.stopPropagation(); + google.maps.event.trigger(self, "click", event); + }); + + var panes = this.getPanes(); + panes.overlayImage.appendChild(div); +}; + +FeatureMarker.prototype.remove = function() { + if (this.div) { + this.div.parentNode.removeChild(this.div); + this.div = null; + } +}; + +FeatureMarker.prototype.getAnchorPoint = function() { + return new google.maps.Point(0, -this.symHeight/2); +}; diff --git a/htdocs/map.html b/htdocs/map.html index f7644fcf..ee032a3e 100644 --- a/htdocs/map.html +++ b/htdocs/map.html @@ -22,6 +22,13 @@
+

Features

+ diff --git a/htdocs/map.js b/htdocs/map.js index d18a7ac1..dd3a4edc 100644 --- a/htdocs/map.js +++ b/htdocs/map.js @@ -41,6 +41,13 @@ $(function(){ var vessel_url = null; var flight_url = null; + // colors used for features + var featureColors = { + 'KiwiSDR' : '#800000', + 'WebSDR' : '#000080', + 'OpenWebRX' : '#006000' + }; + var colorKeys = {}; var colorScale = chroma.scale(['red', 'blue', 'green']).mode('hsl'); var getColor = function(id){ @@ -109,7 +116,7 @@ $(function(){ } var processUpdates = function(updates) { - if (typeof(AprsMarker) == 'undefined') { + if ((typeof(AprsMarker) == 'undefined') || (typeof(FeatureMarker) == 'undefined')) { updateQueue = updateQueue.concat(updates); return; } @@ -145,7 +152,6 @@ $(function(){ marker.mode = update.mode; marker.hops = update.hops; marker.band = update.band; - marker.url = update.location.url; marker.comment = update.location.comment; marker.weather = update.location.weather; marker.altitude = update.location.altitude; @@ -154,8 +160,6 @@ $(function(){ marker.gain = update.location.gain; marker.device = update.location.device; marker.aircraft = update.location.aircraft; - marker.antenna = update.location.antenna; - marker.users = update.location.users; marker.directivity = update.location.directivity; if (expectedCallsign && expectedCallsign == update.callsign) { @@ -168,6 +172,49 @@ $(function(){ showMarkerInfoWindow(infowindow.callsign, pos); } break; + case 'feature': + var pos = new google.maps.LatLng(update.location.lat, update.location.lon); + var marker; + var markerClass = google.maps.Marker; + var options = {} + if (update.location.symbol) { + markerClass = FeatureMarker; + options.symbol = update.location.symbol; + options.color = update.mode in featureColors? + featureColors[update.mode] : '#000000'; + } + if (markers[update.callsign]) { + marker = markers[update.callsign]; + } else { + marker = new markerClass(); + marker.addListener('click', function(){ + showMarkerInfoWindow(update.callsign, pos); + }); + markers[update.callsign] = marker; + } + marker.setOptions($.extend({ + position: pos, + map: map, + title: update.callsign + }, options, getMarkerOpacityOptions(update.lastseen) )); + marker.lastseen = update.lastseen; + marker.mode = update.mode; + marker.url = update.location.url; + marker.comment = update.location.comment; + marker.altitude = update.location.altitude; + marker.device = update.location.device; + marker.antenna = update.location.antenna; + + if (expectedCallsign && expectedCallsign == update.callsign) { + map.panTo(pos); + showMarkerInfoWindow(update.callsign, pos); + expectedCallsign = false; + } + + if (infowindow && infowindow.callsign && infowindow.callsign == update.callsign) { + showMarkerInfoWindow(infowindow.callsign, pos); + } + break; case 'locator': var loc = update.location.locator; var lat = (loc.charCodeAt(1) - 65 - 9) * 10 + Number(loc[3]); @@ -267,8 +314,16 @@ $(function(){ setInterval(function() { nite.refresh() }, 10000); // every 10s }); $.getScript('static/lib/AprsMarker.js').done(function(){ - processUpdates(updateQueue); - updateQueue = []; + if(typeof(FeatureMarker) != 'undefined') { + processUpdates(updateQueue); + updateQueue = []; + } + }); + $.getScript('static/lib/FeatureMarker.js').done(function(){ + if(typeof(AprsMarker) != 'undefined') { + processUpdates(updateQueue); + updateQueue = []; + } }); var $legend = $(".openwebrx-map-legend"); @@ -686,6 +741,25 @@ $(function(){ }); } }); + + $content = $legend.find('.features'); + $content.on('click', 'li', function() { + var $el = $(this); + $lis = $content.find('li'); + if ($lis.hasClass('disabled') && !$el.hasClass('disabled')) { + $lis.removeClass('disabled'); + // @@@ ADD CODE HERE + } else { + $el.removeClass('disabled'); + $lis.filter(function() { + return this != $el[0] + }).addClass('disabled'); + + var key = colorMode.slice(2); + var selector = $el.data('selector'); + // @@@ ADD CODE HERE + } + }); } }); diff --git a/owrx/receiverdb.py b/owrx/receiverdb.py index a4af1a31..92326e21 100644 --- a/owrx/receiverdb.py +++ b/owrx/receiverdb.py @@ -28,6 +28,9 @@ class ReceiverLocation(LatLngLocation): def getId(self): return re.sub(r"^.*://(.*)[/:].*$", r"\1", self.attrs["url"]) + def getMode(self): + return re.sub(r"^([A-Za-z]+).*$", r"\1", self.attrs["device"]) + def __dict__(self): return self.attrs @@ -90,6 +93,7 @@ class ReceiverDatabase(object): try: with open(file, "w") as f: json.dump(self, f, cls=ReceiverJSONEncoder, indent=2) + f.close() except Exception as e: logger.debug("Exception: {0}".format(e)) @@ -101,13 +105,13 @@ class ReceiverDatabase(object): logger.debug("Done refreshing receiver database.") self.thread = None - def getSymbol(self, type: str): + def getColor(self, type: str): if type.startswith("KiwiSDR"): - return getSymbolData('/', '/') + return "#800000" elif type.startswith("WebSDR"): - return getSymbolData('/', '\\') + return "#000080" else: - return getSymbolData('<', '\\') + return "#006000" def loadFromFile(self, fileName: str = None): # Get filename @@ -118,6 +122,7 @@ class ReceiverDatabase(object): try: with open(fileName, "r") as f: content = f.read() + f.close() if content: db = json.loads(content) except Exception as e: @@ -135,7 +140,7 @@ class ReceiverDatabase(object): def updateMap(self): for r in self.receivers.values(): - Map.getSharedInstance().updateLocation(r.getId(), r, "Internet") + Map.getSharedInstance().updateLocation(r.getId(), r, r.getMode()) def scrapeOWRX(self, url: str = "https://www.receiverbook.de/map"): patternJson = re.compile(r"^\s*var\s+receivers\s+=\s+(\[.*\]);\s*$") @@ -160,13 +165,14 @@ class ReceiverDatabase(object): else: dev = r["type"] rl = ReceiverLocation(lat, lon, { - "type" : "latlon", + "type" : "feature", "lat" : lat, "lon" : lon, "comment" : r["label"], "url" : r["url"], "device" : dev, - "symbol" : self.getSymbol(dev) + "symbol" : "◬", + "color" : self.getColor(dev) }) result[rl.getId()] = rl # Offset colocated receivers by ~500m @@ -190,14 +196,15 @@ class ReceiverDatabase(object): lat = entry["lat"] lon = entry["lon"] rl = ReceiverLocation(lat, lon, { - "type" : "latlon", + "type" : "feature", "lat" : lat, "lon" : lon, "comment" : entry["desc"], "url" : entry["url"], #"users" : int(entry["users"]), "device" : "WebSDR", - "symbol" : self.getSymbol("WebSDR") + "symbol" : "◬", + "color" : self.getColor("WebSDR") }) result[rl.getId()] = rl @@ -231,7 +238,7 @@ class ReceiverDatabase(object): lat = float(m.group(1)) lon = float(m.group(2)) rl = ReceiverLocation(lat, lon, { - "type" : "latlon", + "type" : "feature", "lat" : lat, "lon" : lon, "comment" : entry["name"], @@ -241,8 +248,9 @@ class ReceiverDatabase(object): "loc" : entry["loc"], "altitude": int(entry["asl"]), "antenna" : entry["antenna"], - "device" : entry["sw_version"], - "symbol" : self.getSymbol("KiwiSDR") + "device" : re.sub("_v", " ", entry["sw_version"]), + "symbol" : "◬", + "color" : self.getColor("KiwiSDR") }) result[rl.getId()] = rl # Clear current entry