Finished refactoring maps.

This commit is contained in:
Marat Fayzullin 2023-07-31 21:53:07 -04:00
parent f6afd5fe8c
commit 7dc8f910d5
5 changed files with 462 additions and 486 deletions

197
htdocs/lib/Map.js Normal file
View File

@ -0,0 +1,197 @@
//
// Map Manager handles web socket connection and traffic processing
//
function MapManager() {
var self = this;
// Determine web socket URL
var protocol = window.location.protocol.match(/https/) ? 'wss' : 'ws';
var href = window.location.href.replace(/\/[^\/]*$/,'');
href = protocol + '://' + href.split('://')[1];
this.ws_url = href + (href.endsWith('/')? '':'/') + 'ws/';
// Reset everything for now
this.reconnect_timeout = false;
this.config = {};
// Markers management (features, APRS, AIS, HFDL, etc)
this.mman = new MarkerManager();
// Locators management (FT8, FT4, WSPR, etc)
this.lman = new LocatorManager();
// Clock display
this.clock = new Clock($("#openwebrx-clock-utc"));
// Toggle color modes on click
$(function() {
$('#openwebrx-map-colormode').on('change', function() {
self.lman.setColorMode(map, $(this).val());
});
});
// Toggle legend box on/off when clicking the clock
$(function() {
$('#openwebrx-clock-utc').on('click', function() {
var el = document.getElementById('openwebrx-map-selectors');
if (el) {
el.style.display = el.style.display === 'none'?
'block' : 'none';
}
});
});
// Fade out / remove positions after time
setInterval(function() {
self.lman.ageAll();
self.mman.ageAll();
}, 1000);
// Connect web socket
this.connect();
}
//
// Process a message received over web socket
//
MapManager.prototype.process = function(e) {
if (typeof e.data != 'string') {
console.error("unsupported binary data on websocket; ignoring");
return
}
if (e.data.substr(0, 16) == "CLIENT DE SERVER") {
return
}
try {
var json = JSON.parse(e.data);
switch (json.type) {
case "update":
this.processUpdates(json.value);
break;
case 'receiver_details':
$('.webrx-top-container').header().setDetails(json.value);
break;
case "config":
Object.assign(this.config, json.value);
if ('receiver_gps' in this.config) {
// Passing API key even if this particular map
// engine does not need it (Google Maps do)
this.initializeMap(
this.config.receiver_gps,
this.config.google_maps_api_key
);
}
if ('receiver_name' in this.config) {
this.setReceiverName(this.config.receiver_name);
}
if ('map_position_retention_time' in this.config) {
retention_time = this.config.map_position_retention_time * 1000;
}
if ('callsign_url' in this.config) {
callsign_url = this.config.callsign_url;
}
if ('vessel_url' in this.config) {
vessel_url = this.config.vessel_url;
}
if ('flight_url' in this.config) {
flight_url = this.config.flight_url;
}
break;
default:
console.warn('received message of unknown type: ' + json.type);
}
} catch (e) {
// Don't lose exception
console.error(e);
}
};
//
// Connect web socket
//
MapManager.prototype.connect = function() {
var ws = new WebSocket(this.ws_url);
var self = this;
// When socket opens...
ws.onopen = function() {
ws.send("SERVER DE CLIENT client=map.js type=map");
self.reconnect_timeout = false
};
// When socket closes...
ws.onclose = function() {
// Clear map
self.removeReceiver();
self.mman.clear();
self.lman.clear();
if (self.reconnect_timeout) {
// Max value: roundabout 8 and a half minutes
self.reconnect_timeout = Math.min(self.reconnect_timeout * 2, 512000);
} else {
// Initial value: 1s
self.reconnect_timeout = 1000;
}
// Try reconnecting after timeout
setTimeout(self.connect, self.reconnect_timeout);
};
// When socket receives a message...
ws.onmessage = function(e) {
self.process(e);
}
// When socket gets an error...
//ws.onerror = function() {
// console.info("websocket error");
//};
// http://stackoverflow.com/questions/4812686/closing-websocket-correctly-html5-javascript
window.onbeforeunload = function() {
ws.onclose = function () {};
ws.close();
};
};
//
// Set up legend filter toggles inside given HTML element.
//
MapManager.prototype.setupLegendFilters = function($legend) {
var self = this;
$content = $legend.find('.content');
$content.on('click', 'li', function() {
var $el = $(this);
$lis = $content.find('li');
if ($lis.hasClass('disabled') && !$el.hasClass('disabled')) {
$lis.removeClass('disabled');
self.lman.setFilter(map);
} else {
$el.removeClass('disabled');
$lis.filter(function() {
return this != $el[0]
}).addClass('disabled');
self.lman.setFilter(map, $el.data('selector'));
}
});
$content1 = $legend.find('.features');
$content1.on('click', 'li', function() {
var $el = $(this);
var onoff = $el.hasClass('disabled');
if (onoff) {
$el.removeClass('disabled');
} else {
$el.addClass('disabled');
}
self.mman.toggle(map, $el.data('selector'), onoff);
});
};

View File

@ -205,44 +205,3 @@ Locator.prototype.age = function(age) {
return false;
}
};
//
// GoogleMaps-specific Map Locator (derived from generic locator)
//
function GLocator() {
this.rect = new google.maps.Rectangle();
this.rect.setOptions({
strokeWeight : 2,
strokeColor : "#FFFFFF",
fillColor : "#FFFFFF"
});
}
GLocator.prototype = new Locator();
GLocator.prototype.setMap = function(map) {
this.rect.setMap(map);
};
GLocator.prototype.setCenter = function(lat, lon) {
this.center = new google.maps.LatLng({lat: lat, lng: lon});
this.rect.setOptions({ bounds : {
north : lat - 0.5,
south : lat + 0.5,
west : lon - 1.0,
east : lon + 1.0
}});
}
GLocator.prototype.setColor = function(color) {
this.rect.setOptions({ strokeColor: color, fillColor: color });
};
GLocator.prototype.setOpacity = function(opacity) {
this.rect.setOptions({
strokeOpacity : LocatorManager.strokeOpacity * opacity,
fillOpacity : LocatorManager.fillOpacity * opacity
});
};

View File

@ -564,53 +564,3 @@ AprsMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
+ ( this.band ? ' on ' + this.band : '' ) + '</div>'
+ commentString + weatherString + detailsString + hopsString;
};
//
// GoogleMaps-Specific Markers (derived from generic markers)
//
function GMarker() {}
GMarker.prototype = new google.maps.OverlayView();
GMarker.prototype.setMarkerOptions = function(options) {
this.setOptions(options);
this.draw();
};
GMarker.prototype.onAdd = function() {
// Create HTML elements representing the mark
var div = this.create();
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);
};
GMarker.prototype.getAnchorPoint = function() {
var offset = this.getAnchorOffset();
return new google.maps.Point(offset[0], offset[1]);
};
GMarker.prototype.setMarkerOpacity = function(opacity) {
this.setOptions({ opacity: opacity });
};
GMarker.prototype.setMarkerPosition = function(title, lat, lon) {
this.setOptions({
title : title,
position : new google.maps.LatLng(lat, lon)
});
};
// GoogleMaps-Specific FeatureMarker
function GFeatureMarker() { $.extend(this, new FeatureMarker()); }
GFeatureMarker.prototype = new GMarker();
// GoogleMaps-Specific AprsMarker
function GAprsMarker() { $.extend(this, new AprsMarker()); }
GAprsMarker.prototype = new GMarker();

View File

@ -6,406 +6,273 @@ var flight_url = null;
// reasonable default; will be overriden by server
var retention_time = 2 * 60 * 60 * 1000;
// Function to toggle legend box on/off
function toggleElement(el_name) {
var el = document.getElementById(el_name);
if (el) el.style.display = el.style.display === 'none'? 'block' : 'none';
// Our Google Map
var map = null;
// Receiver location marker
var receiverMarker = null;
// Information bubble window
var infoWindow = null;
// Updates are queued here
var updateQueue = [];
// Web socket connection management, message processing
var mapManager = new MapManager();
var query = window.location.search.replace(/^\?/, '').split('&').map(function(v){
var s = v.split('=');
var r = {};
r[s[0]] = s.slice(1).join('=');
return r;
}).reduce(function(a, b){
return a.assign(b);
});
var expectedCallsign = query.callsign? decodeURIComponent(query.callsign) : null;
var expectedLocator = query.locator? query.locator : null;
// Get information bubble window
function getInfoWindow() {
if (!infoWindow) {
infoWindow = new google.maps.InfoWindow();
google.maps.event.addListener(infoWindow, 'closeclick', function() {
delete infoWindow.locator;
delete infoWindow.callsign;
});
}
delete infoWindow.locator;
delete infoWindow.callsign;
return infoWindow;
};
// Show information bubble for a locator
function showLocatorInfoWindow(locator, pos) {
var iw = getInfoWindow();
iw.locator = locator;
iw.setContent(mapManager.lman.getInfoHTML(locator, pos, receiverMarker));
iw.setPosition(pos);
iw.open(map);
};
// Show information bubble for a marker
function showMarkerInfoWindow(name, pos) {
var marker = mapManager.mman.find(name);
var iw = getInfoWindow();
iw.callsign = name;
iw.setContent(marker.getInfoHTML(name, receiverMarker));
iw.open(map, marker);
};
// Show information bubble for the receiver location
function showReceiverInfoWindow(marker) {
var iw = getInfoWindow()
iw.setContent(
'<h3>' + marker.config['receiver_name'] + '</h3>' +
'<div>Receiver Location</div>'
);
iw.open(map, marker);
};
//
// GOOGLE-SPECIFIC MAP MANAGER METHODS
//
MapManager.prototype.setReceiverName = function(name) {
if (this.receiverMarker) this.receiverMarker.setOptions({ title: name });
}
$(function(){
var query = window.location.search.replace(/^\?/, '').split('&').map(function(v){
var s = v.split('=');
var r = {};
r[s[0]] = s.slice(1).join('=');
return r;
}).reduce(function(a, b){
return a.assign(b);
});
MapManager.prototype.removeReceiver = function() {
if (this.receiverMarker) this.receiverMarker.setMap();
}
var expectedCallsign;
if (query.callsign) expectedCallsign = decodeURIComponent(query.callsign);
var expectedLocator;
if (query.locator) expectedLocator = query.locator;
MapManager.prototype.initializeMap = function(receiver_gps, api_key) {
var receiverPos = { lat: receiver_gps.lat, lng: receiver_gps.lon };
var protocol = window.location.protocol.match(/https/) ? 'wss' : 'ws';
var href = window.location.href;
var index = href.lastIndexOf('/');
if (index > 0) {
href = href.substr(0, index + 1);
}
href = href.split("://")[1];
href = protocol + "://" + href;
if (!href.endsWith('/')) {
href += '/';
}
var ws_url = href + "ws/";
var map;
var receiverMarker;
var updateQueue = [];
// marker and locator managers
var markmanager = null;
var locmanager = null;
// clock
var clock = new Clock($("#openwebrx-clock-utc"));
$(function() {
$('#openwebrx-map-colormode').on('change', function() {
locmanager.setColorMode(map, $(this).val());
if (map) {
this.receiverMarker.setOptions({
map : map,
position : receiverPos,
config : this.config
});
});
} else {
var self = this;
var processUpdates = function(updates) {
if (!markmanager || !locmanager) {
updateQueue = updateQueue.concat(updates);
return;
}
updates.forEach(function(update) {
switch (update.location.type) {
case 'latlon':
var marker = markmanager.find(update.callsign);
var markerClass = google.maps.Marker;
var aprsOptions = {}
if (update.location.symbol) {
markerClass = GAprsMarker;
aprsOptions.symbol = update.location.symbol;
aprsOptions.course = update.location.course;
aprsOptions.speed = update.location.speed;
}
// If new item, create a new marker for it
if (!marker) {
marker = new markerClass();
markmanager.addType(update.mode);
markmanager.add(update.callsign, marker);
marker.addListener('click', function() {
showMarkerInfoWindow(update.callsign, marker.pos);
});
}
// Update marker attributes and age
marker.update(update);
// Assign marker to map
marker.setMap(markmanager.isEnabled(update.mode)? map : undefined);
// Apply marker options
marker.setMarkerOptions(aprsOptions);
if (expectedCallsign && expectedCallsign == update.callsign) {
map.panTo(marker.pos);
showMarkerInfoWindow(update.callsign, marker.pos);
expectedCallsign = false;
}
if (infowindow && infowindow.callsign && infowindow.callsign == update.callsign) {
showMarkerInfoWindow(infowindow.callsign, marker.pos);
}
break;
case 'feature':
var marker = markmanager.find(update.callsign);
var options = {}
// If no symbol or color supplied, use defaults by type
if (update.location.symbol) {
options.symbol = update.location.symbol;
} else {
options.symbol = markmanager.getSymbol(update.mode);
}
if (update.location.color) {
options.color = update.location.color;
} else {
options.color = markmanager.getColor(update.mode);
}
// If new item, create a new marker for it
if (!marker) {
marker = new GFeatureMarker();
markmanager.addType(update.mode);
markmanager.add(update.callsign, marker);
marker.addListener('click', function() {
showMarkerInfoWindow(update.callsign, marker.pos);
});
}
// Update marker attributes and age
marker.update(update);
// Assign marker to map
marker.setMap(markmanager.isEnabled(update.mode)? map : undefined);
// Apply marker options
marker.setMarkerOptions(options);
if (expectedCallsign && expectedCallsign == update.callsign) {
map.panTo(marker.pos);
showMarkerInfoWindow(update.callsign, marker.pos);
expectedCallsign = false;
}
if (infowindow && infowindow.callsign && infowindow.callsign == update.callsign) {
showMarkerInfoWindow(infowindow.callsign, marker.pos);
}
break;
case 'locator':
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.rect.addListener('click', function() {
showLocatorInfoWindow(rectangle.locator, rectangle.center);
});
}
// Update locator attributes, center, age
rectangle.update(update);
// Assign locator to map and set its color
rectangle.setMap(locmanager.filter(rectangle)? map : undefined);
rectangle.setColor(locmanager.getColor(rectangle));
if (expectedLocator && expectedLocator == update.location.locator) {
map.panTo(rectangle.center);
showLocatorInfoWindow(expectedLocator, rectangle.center);
expectedLocator = false;
}
if (infowindow && infowindow.locator && infowindow.locator == update.location.locator) {
showLocatorInfoWindow(infowindow.locator, rectangle.center);
}
break;
}
});
};
var clearMap = function(){
var reset = function(callsign, item) { item.setMap(); };
receiverMarker.setMap();
markmanager.clear();
locmanager.clear();
};
var reconnect_timeout = false;
var config = {}
var connect = function(){
var ws = new WebSocket(ws_url);
ws.onopen = function(){
ws.send("SERVER DE CLIENT client=map.js type=map");
reconnect_timeout = false
};
ws.onmessage = function(e){
if (typeof e.data != 'string') {
console.error("unsupported binary data on websocket; ignoring");
return
}
if (e.data.substr(0, 16) == "CLIENT DE SERVER") {
return
}
try {
var json = JSON.parse(e.data);
switch (json.type) {
case "config":
Object.assign(config, json.value);
if ('receiver_gps' in config) {
var receiverPos = {
lat: config.receiver_gps.lat,
lng: config.receiver_gps.lon
};
if (!map) $.getScript("https://maps.googleapis.com/maps/api/js?key=" + config.google_maps_api_key).done(function(){
map = new google.maps.Map($('.openwebrx-map')[0], {
center: receiverPos,
zoom: 5,
});
$.getScript("static/lib/nite-overlay.js").done(function(){
nite.init(map);
setInterval(function() { nite.refresh() }, 10000); // every 10s
});
$.getScript('static/lib/MarkerManager.js').done(function(){
markmanager = new MarkerManager();
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");
setupLegendFilters($legend);
map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push($legend[0]);
if (!receiverMarker) {
receiverMarker = new google.maps.Marker();
receiverMarker.addListener('click', function() {
showReceiverInfoWindow(receiverMarker);
});
}
receiverMarker.setOptions({
map: map,
position: receiverPos,
title: config['receiver_name'],
config: config
});
}); else {
receiverMarker.setOptions({
map: map,
position: receiverPos,
config: config
});
}
}
if ('receiver_name' in config && receiverMarker) {
receiverMarker.setOptions({
title: config['receiver_name']
});
}
if ('map_position_retention_time' in config) {
retention_time = config.map_position_retention_time * 1000;
}
if ('callsign_url' in config) {
callsign_url = config['callsign_url'];
}
if ('vessel_url' in config) {
vessel_url = config['vessel_url'];
}
if ('flight_url' in config) {
flight_url = config['flight_url'];
}
break;
case "update":
processUpdates(json.value);
break;
case 'receiver_details':
$('.webrx-top-container').header().setDetails(json['value']);
break;
default:
console.warn('received message of unknown type: ' + json['type']);
}
} catch (e) {
// don't lose exception
console.error(e);
}
};
ws.onclose = function(){
clearMap();
if (reconnect_timeout) {
// max value: roundabout 8 and a half minutes
reconnect_timeout = Math.min(reconnect_timeout * 2, 512000);
} else {
// initial value: 1s
reconnect_timeout = 1000;
}
setTimeout(connect, reconnect_timeout);
};
window.onbeforeunload = function() { //http://stackoverflow.com/questions/4812686/closing-websocket-correctly-html5-javascript
ws.onclose = function () {};
ws.close();
};
/*
ws.onerror = function(){
console.info("websocket error");
};
*/
};
connect();
var getInfoWindow = function() {
if (!infowindow) {
infowindow = new google.maps.InfoWindow();
google.maps.event.addListener(infowindow, 'closeclick', function() {
delete infowindow.locator;
delete infowindow.callsign;
// After Google Maps API loads...
$.getScript("https://maps.googleapis.com/maps/api/js?key=" + api_key).done(function() {
// Create a map instance
map = new google.maps.Map($('.openwebrx-map')[0], {
center : receiverPos,
zoom : 5,
});
// Load and initialize day-and-night overlay
$.getScript("static/lib/nite-overlay.js").done(function() {
nite.init(map);
setInterval(function() { nite.refresh() }, 10000); // every 10s
});
// Load and initialize OWRX-specific map item managers
$.getScript('static/lib/GoogleMaps.js').done(function() {
// Process any accumulated updates
self.processUpdates(updateQueue);
updateQueue = [];
});
// Create map legend selectors
var $legend = $(".openwebrx-map-legend");
self.setupLegendFilters($legend);
map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push($legend[0]);
// Create receiver marker
if (!receiverMarker) {
receiverMarker = new google.maps.Marker();
receiverMarker.addListener('click', function() {
showReceiverInfoWindow(receiverMarker);
});
}
// Set receiver marker position, name, etc.
receiverMarker.setOptions({
map : map,
position : receiverPos,
title : self.config['receiver_name'],
config : self.config
});
});
}
};
MapManager.prototype.processUpdates = function(updates) {
var self = this;
if (typeof(GMarker) == 'undefined') {
updateQueue = updateQueue.concat(updates);
return;
}
updates.forEach(function(update) {
switch (update.location.type) {
case 'latlon':
var marker = self.mman.find(update.callsign);
var markerClass = google.maps.Marker;
var aprsOptions = {}
if (update.location.symbol) {
markerClass = GAprsMarker;
aprsOptions.symbol = update.location.symbol;
aprsOptions.course = update.location.course;
aprsOptions.speed = update.location.speed;
}
// If new item, create a new marker for it
if (!marker) {
marker = new markerClass();
self.mman.addType(update.mode);
self.mman.add(update.callsign, marker);
marker.addListener('click', function() {
showMarkerInfoWindow(update.callsign, marker.pos);
});
}
// Update marker attributes and age
marker.update(update);
// Assign marker to map
marker.setMap(self.mman.isEnabled(update.mode)? map : undefined);
// Apply marker options
marker.setMarkerOptions(aprsOptions);
if (expectedCallsign && expectedCallsign == update.callsign) {
map.panTo(marker.pos);
showMarkerInfoWindow(update.callsign, marker.pos);
expectedCallsign = false;
}
if (infoWindow && infoWindow.callsign && infoWindow.callsign == update.callsign) {
showMarkerInfoWindow(infoWindow.callsign, marker.pos);
}
break;
case 'feature':
var marker = self.mman.find(update.callsign);
var options = {}
// If no symbol or color supplied, use defaults by type
if (update.location.symbol) {
options.symbol = update.location.symbol;
} else {
options.symbol = self.mman.getSymbol(update.mode);
}
if (update.location.color) {
options.color = update.location.color;
} else {
options.color = self.mman.getColor(update.mode);
}
// If new item, create a new marker for it
if (!marker) {
marker = new GFeatureMarker();
self.mman.addType(update.mode);
self.mman.add(update.callsign, marker);
marker.addListener('click', function() {
showMarkerInfoWindow(update.callsign, marker.pos);
});
}
// Update marker attributes and age
marker.update(update);
// Assign marker to map
marker.setMap(self.mman.isEnabled(update.mode)? map : undefined);
// Apply marker options
marker.setMarkerOptions(options);
if (expectedCallsign && expectedCallsign == update.callsign) {
map.panTo(marker.pos);
showMarkerInfoWindow(update.callsign, marker.pos);
expectedCallsign = false;
}
if (infoWindow && infoWindow.callsign && infoWindow.callsign == update.callsign) {
showMarkerInfoWindow(infoWindow.callsign, marker.pos);
}
break;
case 'locator':
var rectangle = self.lman.find(update.callsign);
// If new item, create a new locator for it
if (!rectangle) {
rectangle = new GLocator();
self.lman.add(update.callsign, rectangle);
rectangle.rect.addListener('click', function() {
showLocatorInfoWindow(rectangle.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));
if (expectedLocator && expectedLocator == update.location.locator) {
map.panTo(rectangle.center);
showLocatorInfoWindow(expectedLocator, rectangle.center);
expectedLocator = false;
}
if (infoWindow && infoWindow.locator && infoWindow.locator == update.location.locator) {
showLocatorInfoWindow(infoWindow.locator, rectangle.center);
}
break;
}
delete infowindow.locator;
delete infowindow.callsign;
return infowindow;
}
var infowindow;
var showLocatorInfoWindow = function(locator, pos) {
var infowindow = getInfoWindow();
infowindow.locator = locator;
infowindow.setContent(locmanager.getInfoHTML(locator, pos, receiverMarker));
infowindow.setPosition(pos);
infowindow.open(map);
};
var showMarkerInfoWindow = function(name, pos) {
var infowindow = getInfoWindow();
var marker = markmanager.find(name);
infowindow.callsign = name;
infowindow.setContent(marker.getInfoHTML(name, receiverMarker));
infowindow.open(map, marker);
}
var showReceiverInfoWindow = function(marker) {
var infowindow = getInfoWindow()
infowindow.setContent(
'<h3>' + marker.config['receiver_name'] + '</h3>' +
'<div>Receiver location</div>'
);
infowindow.open(map, marker);
}
// Fade out / remove positions after time
setInterval(function(){
if (locmanager) locmanager.ageAll();
if (markmanager) markmanager.ageAll();
}, 1000);
var setupLegendFilters = function($legend) {
$content = $legend.find('.content');
$content.on('click', 'li', function() {
var $el = $(this);
$lis = $content.find('li');
if ($lis.hasClass('disabled') && !$el.hasClass('disabled')) {
$lis.removeClass('disabled');
locmanager.setFilter(map);
} else {
$el.removeClass('disabled');
$lis.filter(function() {
return this != $el[0]
}).addClass('disabled');
locmanager.setFilter(map, $el.data('selector'));
}
});
$content1 = $legend.find('.features');
$content1.on('click', 'li', function() {
var $el = $(this);
var onoff = $el.hasClass('disabled');
if (onoff) {
$el.removeClass('disabled');
} else {
$el.addClass('disabled');
}
markmanager.toggle(map, $el.data('selector'), onoff);
});
}
});
});
};

View File

@ -145,6 +145,9 @@ class CompiledAssetsController(GzipMixin, ModificationAwareController):
"lib/jquery-3.2.1.min.js",
"lib/chroma.min.js",
"lib/Header.js",
"lib/MapLocators.js",
"lib/MapMarkers.js",
"lib/Map.js",
"lib/Clock.js",
"map.js",
],