// // Map Locators Management // LocatorManager.strokeOpacity = 0.8; LocatorManager.fillOpacity = 0.35; function LocatorManager(spectral = true) { // Base colors used for the color scale var colors = spectral? ['darkviolet', 'blue', 'green', 'red'] : ['red', 'blue', 'green']; // Current locators this.locators = {}; this.bands = {}; this.modes = {}; // The color scale used this.colorScale = chroma.scale(colors).mode('hsl'); // Current coloring mode this.colorMode = 'band'; } LocatorManager.prototype.getColors = function() { var colors = this.colorMode === 'band'? this.bands : this.colorMode === 'mode'? this.modes : null; return colors; }; 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; // Make sure we have valid band and mode names if (!data.band) data.band = 'other'; if (!data.mode) data.mode = 'other'; // 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' + key + ''; }); $(".openwebrx-map-legend .content").html(''); } LocatorManager.prototype.setColorMode = function(newColorMode) { $('#openwebrx-map-colormode').val(newColorMode); LS.save('mapColorMode', newColorMode); this.colorMode = newColorMode; this.setFilter(); }; LocatorManager.prototype.getInfoHTML = function(id, pos, receiverMarker = null) { return id in this.locators? this.locators[id].getInfoHTML(id, pos, receiverMarker) : ''; } // // Generic Map Locator // Derived classes have to implement: // setMap(), setCenter(), setColor(), setOpacity() // function Locator() {} Locator.prototype.create = function(id) { // No callsigns yet this.callsigns = {}; this.lastseen = 0; this.colorKeys = null; this.colorMode = 'band'; // Center locator at its maidenhead id this.setCenter( (id.charCodeAt(1) - 65 - 9) * 10 + Number(id[3]) + 0.5, (id.charCodeAt(0) - 65 - 9) * 20 + Number(id[2]) * 2 + 1.0 ); } Locator.prototype.update = function(data, map) { // Update callsign information this.callsigns[data.callsign] = { callsign : data.callsign, lastseen : data.lastseen, mode : data.mode, band : data.band, weight : 1 }; // Keep track of the total last-seen for this locator this.lastseen = Math.max(data.lastseen, this.lastseen); // Update color and opacity this.map = map; this.reColor(); // Age locator this.age(new Date().getTime()); }; Locator.prototype.reColor = function() { var c = this.getColor(); if (!c) { this.setMap(); } else { this.setColor(c); this.setMap(this.map); } }; Locator.prototype.getColor = function() { var keys = this.colorKeys; if (!keys) return null; var attr = this.colorMode; var maxw = 0.0; var weight = []; var colors = $.map(this.callsigns, function(x) { var y = x[attr] in keys? keys[x[attr]] : null; if (y) { var w = x.weight; maxw = Math.max(maxw, w); weight.push(w); } return y; }); if (!colors.length) return null; return chroma.average(colors, 'lrgb', weight).alpha( maxw * (0.4 + Math.min(0.5, colors.length / 15)) ); }; Locator.prototype.age = function(now) { var newest = 0; var data = this.callsigns; var cnt0 = Object.keys(data).length; // Perform an initial check on the whole locator if (now - this.lastseen > retention_time) { this.callsigns = {}; this.setMap(); return false; } // Scan individual callsigns $.each(data, function(id, x) { var age = now - x.lastseen; if (age > retention_time) { delete data[id]; } else { x.weight = Utils.getOpacityScale(age); 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]]? x : null; }).sort(function(a, b){ return b.lastseen - a.lastseen; }); var odd = false; var list = inLocator.map(function(x) { var mc = self.colorMode === 'mode'? chroma(self.colorKeys[x.mode]).alpha(0.5) : 'inherit'; var bc = self.colorMode === 'band'? chroma(self.colorKeys[x.band]).alpha(0.5) : 'inherit'; var row = '' + Utils.linkifyCallsign(x.callsign) + '' + '' + moment(x.lastseen).fromNow() + '' + '' + x.mode + '' + '' + x.band + '' + ''; odd = !odd; return row; }).join(""); var distance = receiverMarker? " at " + Utils.distanceKm(receiverMarker.position, pos) + " km" : ""; var latest = inLocator[0]; var lastReport = moment(latest.lastseen).fromNow() + ' using ' + latest.mode + ( latest.band ? ' on ' + latest.band : '' ); return '

Locator ' + locator + distance + '

' + '
' + lastReport + '
' + Utils.makeListTitle('Active Callsigns') + '' + list + '
'; };