Separated locators from the map implementation.
This commit is contained in:
parent
eb11d65f92
commit
7e0239d9b5
|
|
@ -0,0 +1,232 @@
|
|||
//
|
||||
// Map Locators Management
|
||||
//
|
||||
|
||||
LocatorManager.strokeOpacity = 0.8;
|
||||
LocatorManager.fillOpacity = 0.35;
|
||||
LocatorManager.allRectangles = function() { return true; };
|
||||
|
||||
function LocatorManager() {
|
||||
// Current rectangles
|
||||
this.rectangles = {};
|
||||
|
||||
// Current color allocations
|
||||
this.colorKeys = {};
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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.add = function(id, rectangle) {
|
||||
this.rectangles[id] = rectangle;
|
||||
};
|
||||
|
||||
LocatorManager.prototype.ageAll = function() {
|
||||
var now = new Date().getTime();
|
||||
var data = this.rectangles;
|
||||
$.each(data, function(id, x) {
|
||||
if (!x.age(now - x.lastseen)) delete data[id];
|
||||
});
|
||||
};
|
||||
|
||||
LocatorManager.prototype.clear = function() {
|
||||
// Remove all rectangles from the map
|
||||
$.each(this.markers, function(_, x) { x.setMap(); });
|
||||
// Delete all rectangles
|
||||
this.rectangles = {};
|
||||
};
|
||||
|
||||
LocatorManager.prototype.setFilter = function(map, filterBy = null) {
|
||||
if (!filterBy) {
|
||||
this.rectangleFilter = LocatorManager.allRectangles;
|
||||
} else {
|
||||
var key = this.colorMode.slice(2);
|
||||
this.rectangleFilter = function(x) {
|
||||
return x[key] === filterBy;
|
||||
};
|
||||
}
|
||||
|
||||
var filter = this.rectangleFilter;
|
||||
$.each(this.rectangles, function(_, x) {
|
||||
x.setMap(filter(x) ? map : undefined);
|
||||
});
|
||||
};
|
||||
|
||||
LocatorManager.prototype.reColor = function() {
|
||||
var self = this;
|
||||
$.each(this.rectangles, function(_, x) {
|
||||
var color = self.getColor(x);
|
||||
x.setOptions({ strokeColor: color, fillColor: color });
|
||||
});
|
||||
};
|
||||
|
||||
LocatorManager.prototype.updateLegend = function() {
|
||||
if (!this.colorKeys) return;
|
||||
var filter = this.rectangleFilter;
|
||||
var mode = this.colorMode.slice(2);
|
||||
var list = $.map(this.colorKeys, function(value, key) {
|
||||
// Fake rectangle to test if the filter would match
|
||||
var fakeRectangle = Object.fromEntries([[mode, key]]);
|
||||
var disabled = filter(fakeRectangle) ? '' : ' disabled';
|
||||
|
||||
return '<li class="square' + 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(map, newColorMode) {
|
||||
this.colorMode = newColorMode;
|
||||
this.colorKeys = {};
|
||||
this.setFilter(map);
|
||||
this.reColor();
|
||||
this.updateLegend();
|
||||
};
|
||||
|
||||
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<keys.length ; ++j) {
|
||||
this.colorKeys[keys[j]] = colors[j];
|
||||
}
|
||||
|
||||
this.reColor();
|
||||
this.updateLegend();
|
||||
}
|
||||
|
||||
// Return color for the key
|
||||
return this.colorKeys[type];
|
||||
}
|
||||
|
||||
LocatorManager.prototype.getInfoHTML = function(locator, pos, receiverMarker = null) {
|
||||
var inLocator = $.map(this.rectangles, function(x, callsign) {
|
||||
return { callsign: callsign, locator: x.locator, lastseen: x.lastseen, mode: x.mode, band: x.band }
|
||||
}).filter(this.rectangleFilter).filter(function(d) {
|
||||
return d.locator == locator;
|
||||
}).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) + ' (' + 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>';
|
||||
};
|
||||
|
||||
//
|
||||
// Generic locator functionality
|
||||
//
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
//
|
||||
// GoogleMaps-Specific Locators (derived from generic locators)
|
||||
//
|
||||
|
||||
function GLocator() { $.extend(this, new Locator()); }
|
||||
|
||||
GLocator.prototype = new google.maps.Rectangle();
|
||||
|
||||
GLocator.prototype.setOptions = function(options) {
|
||||
google.maps.Rectangle.prototype.setOptions.apply(this, arguments);
|
||||
};
|
||||
|
||||
GLocator.prototype.setCenter = function(lat, lon) {
|
||||
this.center = new google.maps.LatLng({lat: lat, lng: lon});
|
||||
|
||||
this.setOptions({ bounds : {
|
||||
north : lat - 0.5,
|
||||
south : lat + 0.5,
|
||||
west : lon - 1.0,
|
||||
east : lon + 1.0
|
||||
}});
|
||||
}
|
||||
|
||||
GLocator.prototype.age = function(age) {
|
||||
if (age <= retention_time) {
|
||||
var scale = Marker.getOpacityScale(age);
|
||||
var stroke = LocatorManager.strokeOpacity * scale;
|
||||
var fill = LocatorManager.fillOpacity * scale;
|
||||
// this.setOptions({
|
||||
// strokeOpacity : stroke,
|
||||
// fillOpacity : fill
|
||||
// });
|
||||
return true;
|
||||
} else {
|
||||
this.setMap();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
@ -98,8 +98,9 @@ MarkerManager.prototype.add = function(id, marker) {
|
|||
|
||||
MarkerManager.prototype.ageAll = function() {
|
||||
var now = new Date().getTime();
|
||||
$.each(this.markers, function(id, x) {
|
||||
if (!x.age(now - x.lastseen)) delete this.markers[id];
|
||||
var data = this.markers;
|
||||
$.each(data, function(id, x) {
|
||||
if (!x.age(now - x.lastseen)) delete data[id];
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
227
htdocs/map.js
227
htdocs/map.js
|
|
@ -42,93 +42,28 @@ $(function(){
|
|||
var ws_url = href + "ws/";
|
||||
|
||||
var map;
|
||||
var rectangles = {};
|
||||
var receiverMarker;
|
||||
var updateQueue = [];
|
||||
|
||||
var strokeOpacity = 0.8;
|
||||
var fillOpacity = 0.35;
|
||||
|
||||
// marker manager
|
||||
// marker and locator managers
|
||||
var markmanager = null;
|
||||
var locmanager = null;
|
||||
|
||||
// clock
|
||||
var clock = new Clock($("#openwebrx-clock-utc"));
|
||||
|
||||
var colorKeys = {};
|
||||
var colorScale = chroma.scale(['red', 'blue', 'green']).mode('hsl');
|
||||
var getColor = function(id){
|
||||
if (!id) return "#ffffff00";
|
||||
if (!colorKeys[id]) {
|
||||
var keys = Object.keys(colorKeys);
|
||||
keys.push(id);
|
||||
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;
|
||||
});
|
||||
var colors = colorScale.colors(keys.length);
|
||||
colorKeys = {};
|
||||
keys.forEach(function(key, index) {
|
||||
colorKeys[key] = colors[index];
|
||||
});
|
||||
reColor();
|
||||
updateLegend();
|
||||
}
|
||||
return colorKeys[id];
|
||||
}
|
||||
|
||||
// when the color palette changes, update all grid squares with new color
|
||||
var reColor = function() {
|
||||
$.each(rectangles, function(_, r) {
|
||||
var color = getColor(colorAccessor(r));
|
||||
r.setOptions({
|
||||
strokeColor: color,
|
||||
fillColor: color
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var colorMode = 'byband';
|
||||
var colorAccessor = function(r) {
|
||||
switch (colorMode) {
|
||||
case 'byband':
|
||||
return r.band;
|
||||
case 'bymode':
|
||||
return r.mode;
|
||||
case 'off':
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
$(function(){
|
||||
$('#openwebrx-map-colormode').on('change', function(){
|
||||
colorMode = $(this).val();
|
||||
colorKeys = {};
|
||||
filterRectangles(allRectangles);
|
||||
reColor();
|
||||
updateLegend();
|
||||
$(function() {
|
||||
$('#openwebrx-map-colormode').on('change', function() {
|
||||
locmanager.setColorMode(map, $(this).val());
|
||||
});
|
||||
});
|
||||
|
||||
var updateLegend = function() {
|
||||
var lis = $.map(colorKeys, function(value, key) {
|
||||
// fake rectangle to test if the filter would match
|
||||
var fakeRectangle = Object.fromEntries([[colorMode.slice(2), key]]);
|
||||
var disabled = rectangleFilter(fakeRectangle) ? '' : ' disabled';
|
||||
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>');
|
||||
}
|
||||
|
||||
var processUpdates = function(updates) {
|
||||
if ((typeof(AprsMarker) == 'undefined') || (typeof(FeatureMarker) == 'undefined')) {
|
||||
if (!markmanager || !locmanager) {
|
||||
updateQueue = updateQueue.concat(updates);
|
||||
return;
|
||||
}
|
||||
updates.forEach(function(update){
|
||||
|
||||
updates.forEach(function(update) {
|
||||
switch (update.location.type) {
|
||||
case 'latlon':
|
||||
var pos = new google.maps.LatLng(update.location.lat, update.location.lon);
|
||||
|
|
@ -173,6 +108,7 @@ $(function(){
|
|||
showMarkerInfoWindow(infowindow.callsign, pos);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'feature':
|
||||
var pos = new google.maps.LatLng(update.location.lat, update.location.lon);
|
||||
var marker = markmanager.find(update.callsign);
|
||||
|
|
@ -221,50 +157,40 @@ $(function(){
|
|||
showMarkerInfoWindow(infowindow.callsign, pos);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'locator':
|
||||
var loc = update.location.locator;
|
||||
var lat = (loc.charCodeAt(1) - 65 - 9) * 10 + Number(loc[3]);
|
||||
var lon = (loc.charCodeAt(0) - 65 - 9) * 20 + Number(loc[2]) * 2;
|
||||
var center = new google.maps.LatLng({lat: lat + .5, lng: lon + 1});
|
||||
var rectangle;
|
||||
// the accessor is designed to work on the rectangle... but it should work on the update object, too
|
||||
var color = getColor(colorAccessor(update));
|
||||
if (rectangles[update.callsign]) {
|
||||
rectangle = rectangles[update.callsign];
|
||||
} else {
|
||||
rectangle = new google.maps.Rectangle();
|
||||
rectangle.addListener('click', function(){
|
||||
var rectangle = locmanager.find(update.callsign);
|
||||
|
||||
// If new item, create a new locator for it
|
||||
if (!rectangle) {
|
||||
rectangle = new GLocator();
|
||||
locmanager.add(update.callsign, rectangle);
|
||||
rectangle.addListener('click', function() {
|
||||
showLocatorInfoWindow(this.locator, this.center);
|
||||
});
|
||||
rectangles[update.callsign] = rectangle;
|
||||
}
|
||||
rectangle.lastseen = update.lastseen;
|
||||
rectangle.locator = update.location.locator;
|
||||
rectangle.mode = update.mode;
|
||||
rectangle.band = update.band;
|
||||
rectangle.center = center;
|
||||
|
||||
rectangle.setOptions($.extend({
|
||||
strokeColor: color,
|
||||
strokeWeight: 2,
|
||||
fillColor: color,
|
||||
map: rectangleFilter(rectangle) ? map : undefined,
|
||||
bounds:{
|
||||
north: lat,
|
||||
south: lat + 1,
|
||||
west: lon,
|
||||
east: lon + 2
|
||||
}
|
||||
}, getRectangleOpacityOptions(update.lastseen) ));
|
||||
// Update locator attributes, center, and age
|
||||
rectangle.age(new Date().getTime() - update.lastseen);
|
||||
rectangle.update(update);
|
||||
|
||||
// Apply locator options
|
||||
var color = locmanager.getColor(rectangle);
|
||||
rectangle.setOptions({
|
||||
map : locmanager.filter(rectangle)? map : undefined,
|
||||
strokeColor : color,
|
||||
strokeWeight : 2,
|
||||
fillColor : color
|
||||
});
|
||||
|
||||
if (expectedLocator && expectedLocator == update.location.locator) {
|
||||
map.panTo(center);
|
||||
showLocatorInfoWindow(expectedLocator, center);
|
||||
map.panTo(rectangle.center);
|
||||
showLocatorInfoWindow(expectedLocator, rectangle.center);
|
||||
expectedLocator = false;
|
||||
}
|
||||
|
||||
if (infowindow && infowindow.locator && infowindow.locator == update.location.locator) {
|
||||
showLocatorInfoWindow(infowindow.locator, center);
|
||||
showLocatorInfoWindow(infowindow.locator, rectangle.center);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -275,8 +201,7 @@ $(function(){
|
|||
var reset = function(callsign, item) { item.setMap(); };
|
||||
receiverMarker.setMap();
|
||||
markmanager.clear();
|
||||
$.each(rectangles, reset);
|
||||
rectangles = {};
|
||||
locmanager.clear();
|
||||
};
|
||||
|
||||
var reconnect_timeout = false;
|
||||
|
|
@ -321,8 +246,18 @@ $(function(){
|
|||
|
||||
$.getScript('static/lib/MarkerManager.js').done(function(){
|
||||
markmanager = new MarkerManager();
|
||||
processUpdates(updateQueue);
|
||||
updateQueue = [];
|
||||
if (locmanager) {
|
||||
processUpdates(updateQueue);
|
||||
updateQueue = [];
|
||||
}
|
||||
});
|
||||
|
||||
$.getScript('static/lib/LocatorManager.js').done(function(){
|
||||
locmanager = new LocatorManager();
|
||||
if (markmanager) {
|
||||
processUpdates(updateQueue);
|
||||
updateQueue = [];
|
||||
}
|
||||
});
|
||||
|
||||
var $legend = $(".openwebrx-map-legend");
|
||||
|
|
@ -341,6 +276,7 @@ $(function(){
|
|||
title: config['receiver_name'],
|
||||
config: config
|
||||
});
|
||||
|
||||
}); else {
|
||||
receiverMarker.setOptions({
|
||||
map: map,
|
||||
|
|
@ -424,28 +360,7 @@ $(function(){
|
|||
var showLocatorInfoWindow = function(locator, pos) {
|
||||
var infowindow = getInfoWindow();
|
||||
infowindow.locator = locator;
|
||||
var inLocator = $.map(rectangles, function(r, callsign) {
|
||||
return {callsign: callsign, locator: r.locator, lastseen: r.lastseen, mode: r.mode, band: r.band}
|
||||
}).filter(rectangleFilter).filter(function(d) {
|
||||
return d.locator == locator;
|
||||
}).sort(function(a, b){
|
||||
return b.lastseen - a.lastseen;
|
||||
});
|
||||
var distance = receiverMarker?
|
||||
" at " + Marker.distanceKm(receiverMarker.position, pos) + " km" : "";
|
||||
infowindow.setContent(
|
||||
'<h3>Locator: ' + locator + distance + '</h3>' +
|
||||
'<div>Active Callsigns:</div>' +
|
||||
'<ul>' +
|
||||
inLocator.map(function(i){
|
||||
var timestring = moment(i.lastseen).fromNow();
|
||||
var message = Marker.linkify(i.callsign) + ' (' + timestring + ' using ' + i.mode;
|
||||
if (i.band) message += ' on ' + i.band;
|
||||
message += ')';
|
||||
return '<li>' + message + '</li>'
|
||||
}).join("") +
|
||||
'</ul>'
|
||||
);
|
||||
infowindow.setContent(locmanager.getInfoHTML(locator, pos, receiverMarker));
|
||||
infowindow.setPosition(pos);
|
||||
infowindow.open(map);
|
||||
};
|
||||
|
|
@ -468,47 +383,12 @@ $(function(){
|
|||
infowindow.open(map, marker);
|
||||
}
|
||||
|
||||
var getScale = function(lastseen) {
|
||||
var age = new Date().getTime() - lastseen;
|
||||
var scale = 1;
|
||||
if (age >= retention_time / 2) {
|
||||
scale = (retention_time - age) / (retention_time / 2);
|
||||
}
|
||||
return Math.max(0, Math.min(1, scale));
|
||||
};
|
||||
|
||||
var getRectangleOpacityOptions = function(lastseen) {
|
||||
var scale = getScale(lastseen);
|
||||
return {
|
||||
strokeOpacity: strokeOpacity * scale,
|
||||
fillOpacity: fillOpacity * scale
|
||||
};
|
||||
};
|
||||
|
||||
// fade out / remove positions after time
|
||||
// Fade out / remove positions after time
|
||||
setInterval(function(){
|
||||
var now = new Date().getTime();
|
||||
$.each(rectangles, function(callsign, m) {
|
||||
var age = now - m.lastseen;
|
||||
if (age > retention_time) {
|
||||
delete rectangles[callsign];
|
||||
m.setMap();
|
||||
return;
|
||||
}
|
||||
m.setOptions(getRectangleOpacityOptions(m.lastseen));
|
||||
});
|
||||
markmanager.ageAll();
|
||||
if (locmanager) locmanager.ageAll();
|
||||
if (markmanager) markmanager.ageAll();
|
||||
}, 1000);
|
||||
|
||||
var rectangleFilter = allRectangles = function() { return true; };
|
||||
|
||||
var filterRectangles = function(filter) {
|
||||
rectangleFilter = filter;
|
||||
$.each(rectangles, function(_, r) {
|
||||
r.setMap(rectangleFilter(r) ? map : undefined);
|
||||
});
|
||||
};
|
||||
|
||||
var setupLegendFilters = function($legend) {
|
||||
$content = $legend.find('.content');
|
||||
$content.on('click', 'li', function() {
|
||||
|
|
@ -516,18 +396,13 @@ $(function(){
|
|||
$lis = $content.find('li');
|
||||
if ($lis.hasClass('disabled') && !$el.hasClass('disabled')) {
|
||||
$lis.removeClass('disabled');
|
||||
filterRectangles(allRectangles);
|
||||
locmanager.setFilter(map);
|
||||
} else {
|
||||
$el.removeClass('disabled');
|
||||
$lis.filter(function() {
|
||||
return this != $el[0]
|
||||
}).addClass('disabled');
|
||||
|
||||
var key = colorMode.slice(2);
|
||||
var selector = $el.data('selector');
|
||||
filterRectangles(function(r) {
|
||||
return r[key] === selector;
|
||||
});
|
||||
locmanager.setFilter(map, $el.data('selector'));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue