openwebrxplus/htdocs/lib/MapLocators.js

295 lines
7.8 KiB
JavaScript

//
// Map Locators Management
//
LocatorManager.strokeOpacity = 0.8;
LocatorManager.fillOpacity = 0.35;
function LocatorManager() {
// 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 = '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;
// 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<keys.length ; ++j) {
colorMap[keys[j]] = colors[j];
}
};
LocatorManager.prototype.ageAll = function() {
var now = new Date().getTime();
var data = this.locators;
$.each(data, function(id, x) {
if (!x.age(now)) delete data[id];
});
};
LocatorManager.prototype.clear = function() {
// Remove all locators from the map
$.each(this.locators, function(_, x) { x.setMap(); });
// Delete all locators
this.locators = {};
};
LocatorManager.prototype.setFilter = function(filterBy = null) {
var colors = this.getColors();
this.assignColors(colors);
if (filterBy) {
$.each(colors, function(id, x) {
if (id !== filterBy) colors[id] = null;
});
}
this.reColor();
this.updateLegend();
};
LocatorManager.prototype.reColor = function() {
var mode = this.colorMode;
var keys = this.getColors();
$.each(this.locators, function(_, x) {
x.colorKeys = keys;
x.colorMode = mode;
x.reColor();
});
};
LocatorManager.prototype.updateLegend = function() {
var colors = this.getColors();
var keys = this.getSortedKeys(colors);
var list = $.map(keys, function(key) {
var value = colors[key]? colors[key] : '#000000';
return '<li class="square' + (colors[key]? '' : ' disabled')
+ '" data-selector="' + key
+ '"><span class="illustration" style="background-color:'
+ chroma(value).alpha(LocatorManager.fillOpacity) + ';border-color:'
+ chroma(value).alpha(LocatorManager.strokeOpacity) + ';"></span>'
+ key + '</li>';
});
$(".openwebrx-map-legend .content").html('<ul>' + list.join('') + '</ul>');
}
LocatorManager.prototype.setColorMode = function(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] = {
lastseen : data.lastseen,
mode : data.mode,
band : data.band,
age : 0
};
// 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();
};
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;
var count;
var color;
if (!this.colorKeys) return null;
var attr = this.colorMode;
var weight = [];
var colors = $.map(this.callsigns, function(x) {
var y = x[attr] in keys? keys[x[attr]] : null;
if (y) weight.push(Marker.getOpacityScale(x.age));
return y;
});
count = Object.keys(colors).length;
if (!count) return null;
return chroma.average(colors, 'lrgb', weight).alpha(0.25 + Math.min(0.6, count / 10));
};
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) {
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;
});
var distance = receiverMarker?
" at " + Marker.distanceKm(receiverMarker.position, pos) + " km" : "";
var list = inLocator.map(function(x) {
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 '<li>' + message + ')</li>';
}).join("");
return '<h3>Locator: ' + locator + distance +
'</h3><div>Active Callsigns:</div><ul>' + list + '</ul>';
};