From bd7142ad3e31db42789e3d8ddb5910404a9a8c5c Mon Sep 17 00:00:00 2001 From: Marat Fayzullin Date: Tue, 10 Oct 2023 22:30:23 -0400 Subject: [PATCH] Rewritten map locators, one rectangle per maidenhead square now. --- htdocs/lib/GoogleMaps.js | 3 +- htdocs/lib/Leaflet.js | 6 +- htdocs/lib/MapLocators.js | 364 +++++++++++++++++++++++--------------- htdocs/lib/MapManager.js | 6 +- htdocs/map-google.html | 4 +- htdocs/map-google.js | 12 +- htdocs/map-leaflet.html | 4 +- htdocs/map-leaflet.js | 24 ++- 8 files changed, 251 insertions(+), 172 deletions(-) diff --git a/htdocs/lib/GoogleMaps.js b/htdocs/lib/GoogleMaps.js index ce2a6918..92f10126 100644 --- a/htdocs/lib/GoogleMaps.js +++ b/htdocs/lib/GoogleMaps.js @@ -129,7 +129,8 @@ function GLocator() { this.rect.setOptions({ strokeWeight : 0, strokeColor : "#FFFFFF", - fillColor : "#FFFFFF" + fillColor : "#FFFFFF", + fillOpacity : 1.0 }); } diff --git a/htdocs/lib/Leaflet.js b/htdocs/lib/Leaflet.js index 5b8d2021..3c637adc 100644 --- a/htdocs/lib/Leaflet.js +++ b/htdocs/lib/Leaflet.js @@ -76,7 +76,7 @@ function LSimpleMarker() { $.extend(this, new LMarker(), new AprsMarker()); } // function LLocator() { - this._rect = L.rectangle([[0,0], [1,1]], { color: '#FFFFFF', weight: 0, opacity: 1 }); + this._rect = L.rectangle([[0,0], [1,1]], { color: '#FFFFFF', weight: 0, fillOpacity: 1 }); } LLocator.prototype = new Locator(); @@ -97,8 +97,8 @@ LLocator.prototype.setColor = function(color) { LLocator.prototype.setOpacity = function(opacity) { this._rect.setStyle({ - opacity : LocatorManager.strokeOpacity * opacity, - fillOpacity : LocatorManager.fillOpacity * opacity + opacity : LocatorManager.strokeOpacity * opacity, + fillOpacity : LocatorManager.fillOpacity * opacity }); }; diff --git a/htdocs/lib/MapLocators.js b/htdocs/lib/MapLocators.js index 78bab783..75b90dc7 100644 --- a/htdocs/lib/MapLocators.js +++ b/htdocs/lib/MapLocators.js @@ -4,85 +4,144 @@ LocatorManager.strokeOpacity = 0.8; LocatorManager.fillOpacity = 0.35; -LocatorManager.allRectangles = function() { return true; }; function LocatorManager() { - // Current rectangles - this.rectangles = {}; - - // Current color allocations - this.colorKeys = {}; + // Current locators + this.locators = {}; + this.bands = {}; + this.modes = {}; // The color scale used this.colorScale = chroma.scale(['red', 'blue', 'green']).mode('hsl'); // Current coloring mode - this.colorMode = 'byband'; - - // Current filter - this.rectangleFilter = LocatorManager.allRectangles; + this.colorMode = 'band'; } -LocatorManager.prototype.filter = function(data) { - return this.rectangleFilter(data); -} - -LocatorManager.prototype.find = function(id) { - return id in this.rectangles? this.rectangles[id] : null; +LocatorManager.prototype.getColors = function() { + var colors = + this.colorMode === 'band'? this.bands + : this.colorMode === 'mode'? this.modes + : null; + return colors; }; -LocatorManager.prototype.add = function(id, rectangle) { - this.rectangles[id] = rectangle; +LocatorManager.prototype.find = function(id) { + return id in this.locators? this.locators[id] : null; +}; + +LocatorManager.prototype.add = function(id, locator) { + // Add a new locator, if missing + if (!(id in this.locators)) { + locator.create(id); + locator.colorKeys = this.getColors(); + locator.colorMode = this.colorMode; + this.locators[id] = locator; + } + + // Return locator + return this.locators[id]; +}; + +LocatorManager.prototype.update = function(id, data, map) { + // Do not update unless locator present + if (!(id in this.locators)) return false; + + // Keep track of bands + if(!(data.band in this.bands)) { + this.bands[data.band] = '#000000'; + this.assignColors(this.bands); + if (this.colorMode === 'band') { + this.reColor(); + this.updateLegend(); + } + } + + // Keep track modes + if(!(data.mode in this.modes)) { + this.modes[data.mode] = '#000000'; + this.assignColors(this.modes); + if (this.colorMode === 'mode') { + this.reColor(); + this.updateLegend(); + } + } + + // Update locator + this.locators[id].update(data, map); + return true; +}; + +LocatorManager.prototype.getSortedKeys = function(colorMap) { + var keys = colorMap? Object.keys(colorMap) : []; + + // Sort color keys + keys.sort(function(a, b) { + var pa = parseFloat(a); + var pb = parseFloat(b); + if (isNaN(pa) || isNaN(pb)) return a.localeCompare(b); + return pa - pb; + }); + + return keys; +}; + +LocatorManager.prototype.assignColors = function(colorMap) { + // Recompute colors + var keys = this.getSortedKeys(colorMap); + var colors = this.colorScale.colors(keys.length); + for(var j=0 ; j' @@ -92,64 +151,128 @@ LocatorManager.prototype.updateLegend = function() { $(".openwebrx-map-legend .content").html(''); } -LocatorManager.prototype.setColorMode = function(map, newColorMode) { +LocatorManager.prototype.setColorMode = function(newColorMode) { this.colorMode = newColorMode; - this.colorKeys = {}; - this.setFilter(map); - this.reColor(); - this.updateLegend(); + this.setFilter(); }; -LocatorManager.prototype.getType = function(data) { - switch (this.colorMode) { - case 'byband': - return data.band; - case 'bymode': - return data.mode; - default: - return ''; - } -}; - -LocatorManager.prototype.getColor = function(data) { - var type = this.getType(data); - if (!type) return '#ffffff00'; - - // If adding a new key... - if (!this.colorKeys[type]) { - var keys = Object.keys(this.colorKeys); - - // Add a new key - keys.push(type); - - // Sort color keys - keys.sort(function(a, b) { - var pa = parseFloat(a); - var pb = parseFloat(b); - if (isNaN(pa) || isNaN(pb)) return a.localeCompare(b); - return pa - pb; - }); - - // Recompute colors - var colors = this.colorScale.colors(keys.length); - this.colorKeys = {}; - for(var j=0 ; j retention_time) { + this.callsigns = {}; + this.setMap(); + return false; + } + + // Scan individual callsigns + $.each(data, function(id, x) { + x.age = now - x.lastseen; + if (x.age > retention_time) { + delete data[id]; + } else { + newest = Math.max(newest, x.lastseen); + } + }); + + // Keep track of the total last-seen for this locator + this.lastseen = newest; + + // Update locator's color and opacity + var cnt1 = Object.keys(data).length; +// if (cnt1 == cnt0) return true; + if (cnt1 > 0) { + this.reColor(); + return true; + } else { + this.setMap(); + return false; + } +}; + +Locator.prototype.getInfoHTML = function(locator, pos, receiverMarker = null) { + // Filter out currently hidden bands/modes, sort by recency + var self = this; + var inLocator = $.map(this.callsigns, function(x, id) { + return self.colorKeys && self.colorKeys[x[self.colorMode]]? + { callsign: id, lastseen: x.lastseen, mode: x.mode, band: x.band } : null; }).sort(function(a, b){ return b.lastseen - a.lastseen; }); @@ -161,6 +284,7 @@ LocatorManager.prototype.getInfoHTML = function(locator, pos, receiverMarker = n var timestring = moment(x.lastseen).fromNow(); var message = Marker.linkify(x.callsign, callsign_url) + ' (' + timestring + ' using ' + x.mode; + if (x.band) message += ' on ' + x.band; return '
  • ' + message + ')
  • '; }).join(""); @@ -168,41 +292,3 @@ LocatorManager.prototype.getInfoHTML = function(locator, pos, receiverMarker = n return '

    Locator: ' + locator + distance + '

    Active Callsigns:
      ' + list + '
    '; }; - -// -// Generic Map Locator -// Derived classes have to implement: -// setMap(), setCenter(), setColor(), setOpacity() -// - -function Locator() {} - -Locator.prototype = new Locator(); - -Locator.prototype.update = function(update) { - this.lastseen = update.lastseen; - this.locator = update.location.locator; - this.mode = update.mode; - this.band = update.band; - - // Get locator's lat/lon - const loc = update.location.locator; - const lat = (loc.charCodeAt(1) - 65 - 9) * 10 + Number(loc[3]) + 0.5; - const lon = (loc.charCodeAt(0) - 65 - 9) * 20 + Number(loc[2]) * 2 + 1.0; - - // Implementation-dependent function call - this.setCenter(lat, lon); - - // Age locator - this.age(new Date().getTime() - update.lastseen); -}; - -Locator.prototype.age = function(age) { - if (age <= retention_time) { - this.setOpacity(Marker.getOpacityScale(age)); - return true; - } else { - this.setMap(); - return false; - } -}; diff --git a/htdocs/lib/MapManager.js b/htdocs/lib/MapManager.js index 28d0ebb2..9aacb0d9 100644 --- a/htdocs/lib/MapManager.js +++ b/htdocs/lib/MapManager.js @@ -43,7 +43,7 @@ function MapManager() { // Toggle color modes on click $('#openwebrx-map-colormode').on('change', function() { - self.lman.setColorMode(map, $(this).val()); + self.lman.setColorMode($(this).val()); }); }); @@ -178,13 +178,13 @@ MapManager.prototype.setupLegendFilters = function($legend) { $lis = $content.find('li'); if ($lis.hasClass('disabled') && !$el.hasClass('disabled')) { $lis.removeClass('disabled'); - self.lman.setFilter(map); + self.lman.setFilter(); } else { $el.removeClass('disabled'); $lis.filter(function() { return this != $el[0] }).addClass('disabled'); - self.lman.setFilter(map, $el.data('selector')); + self.lman.setFilter($el.data('selector')); } }); diff --git a/htdocs/map-google.html b/htdocs/map-google.html index 570a649b..ff73ec73 100644 --- a/htdocs/map-google.html +++ b/htdocs/map-google.html @@ -18,8 +18,8 @@

    Colors

    diff --git a/htdocs/map-google.js b/htdocs/map-google.js index 5b8220a2..4df0a460 100644 --- a/htdocs/map-google.js +++ b/htdocs/map-google.js @@ -218,23 +218,19 @@ MapManager.prototype.processUpdates = function(updates) { break; case 'locator': - var rectangle = self.lman.find(update.callsign); + var rectangle = self.lman.find(update.location.locator); // If new item, create a new locator for it if (!rectangle) { rectangle = new GLocator(); - self.lman.add(update.callsign, rectangle); + self.lman.add(update.location.locator, rectangle); rectangle.rect.addListener('click', function() { - showLocatorInfoWindow(rectangle.locator, rectangle.center); + showLocatorInfoWindow(update.location.locator, rectangle.center); }); } // Update locator attributes, center, age - rectangle.update(update); - - // Assign locator to map and set its color - rectangle.setMap(self.lman.filter(rectangle)? map : undefined); - rectangle.setColor(self.lman.getColor(rectangle)); + self.lman.update(update.location.locator, update, map); if (expectedLocator && expectedLocator === update.location.locator) { map.panTo(rectangle.center); diff --git a/htdocs/map-leaflet.html b/htdocs/map-leaflet.html index 1e24bfff..9251cc93 100644 --- a/htdocs/map-leaflet.html +++ b/htdocs/map-leaflet.html @@ -21,8 +21,8 @@

    Colors

    diff --git a/htdocs/map-leaflet.js b/htdocs/map-leaflet.js index 608b49f0..22dfa252 100644 --- a/htdocs/map-leaflet.js +++ b/htdocs/map-leaflet.js @@ -207,15 +207,15 @@ function getInfoWindow(name = null) { }; // Show information bubble for a locator -function showLocatorInfoWindow(rectangle) { +function showLocatorInfoWindow(locator, rectangle) { // Bind information bubble to the rectangle - infoWindow = getInfoWindow(rectangle.locator); + infoWindow = getInfoWindow(locator); rectangle._rect.unbindPopup().bindPopup(infoWindow).openPopup(); // Update information inside the bubble var p = new posObj(rectangle.center); infoWindow.setContent( - mapManager.lman.getInfoHTML(rectangle.locator, p, receiverMarker) + mapManager.lman.getInfoHTML(locator, p, receiverMarker) ); }; @@ -302,7 +302,7 @@ MapManager.prototype.initializeMap = function(receiver_gps, api_key, weather_key updateQueue = []; if (!receiverMarker) { - receiverMarker = new LMarker(); + receiverMarker = new LSimpleMarker(); receiverMarker.setMarkerPosition(self.config['receiver_name'], receiver_gps.lat, receiver_gps.lon); receiverMarker.addListener('click', function () { L.popup(receiverMarker.getPos(), { @@ -442,7 +442,7 @@ MapManager.prototype.processUpdates = function(updates) { if (!update.location.color) update.location.color = self.mman.getColor(update.mode); break; default: - marker = new LSimpleMarker(); + marker = new LMarker(); break; } @@ -495,27 +495,23 @@ MapManager.prototype.processUpdates = function(updates) { // If new item, create a new locator for it if (!rectangle) { rectangle = new LLocator(); - self.lman.add(update.callsign, rectangle); + self.lman.add(update.location.locator, rectangle); rectangle.addListener('click', function() { - showLocatorInfoWindow(rectangle); + showLocatorInfoWindow(update.location.locator, rectangle); }); } // Update locator attributes, center, age - rectangle.update(update); - - // Assign locator to map and set its color - rectangle.setMap(self.lman.filter(rectangle)? map : undefined); - rectangle.setColor(self.lman.getColor(rectangle)); + self.lman.update(update.location.locator, update, map); if (expectedLocator && expectedLocator === update.location.locator) { map.setView(rectangle.center); - showLocatorInfoWindow(rectangle); + showLocatorInfoWindow(update.location.locator, rectangle); expectedLocator = false; } if (infoWindow && infoWindow.name && infoWindow.name === rectangle.locator) { - showMarkerInfoWindow(rectangle); + showMarkerInfoWindow(update.location.locator, rectangle); } break; }