Colors
diff --git a/htdocs/map.js b/htdocs/map.js
index decdf829..f91bae07 100644
--- a/htdocs/map.js
+++ b/htdocs/map.js
@@ -149,7 +149,7 @@ MapManager.prototype.initializeMap = function(receiver_gps, api_key) {
MapManager.prototype.processUpdates = function(updates) {
var self = this;
- if (typeof(GMarker) == 'undefined') {
+ if (typeof(GMarker) === 'undefined') {
updateQueue = updateQueue.concat(updates);
return;
}
@@ -174,7 +174,7 @@ MapManager.prototype.processUpdates = function(updates) {
self.mman.addType(update.mode);
self.mman.add(update.callsign, marker);
marker.addListener('click', function() {
- showMarkerInfoWindow(update.callsign, marker.pos);
+ showMarkerInfoWindow(update.callsign, marker.position);
});
}
@@ -188,13 +188,13 @@ MapManager.prototype.processUpdates = function(updates) {
marker.setMarkerOptions(aprsOptions);
if (expectedCallsign && expectedCallsign == update.callsign) {
- map.panTo(marker.pos);
- showMarkerInfoWindow(update.callsign, marker.pos);
+ map.panTo(marker.position);
+ showMarkerInfoWindow(update.callsign, marker.position);
expectedCallsign = false;
}
if (infoWindow && infoWindow.callsign && infoWindow.callsign == update.callsign) {
- showMarkerInfoWindow(infoWindow.callsign, marker.pos);
+ showMarkerInfoWindow(infoWindow.callsign, marker.position);
}
break;
@@ -220,7 +220,7 @@ MapManager.prototype.processUpdates = function(updates) {
self.mman.addType(update.mode);
self.mman.add(update.callsign, marker);
marker.addListener('click', function() {
- showMarkerInfoWindow(update.callsign, marker.pos);
+ showMarkerInfoWindow(update.callsign, marker.position);
});
}
@@ -234,13 +234,13 @@ MapManager.prototype.processUpdates = function(updates) {
marker.setMarkerOptions(options);
if (expectedCallsign && expectedCallsign == update.callsign) {
- map.panTo(marker.pos);
- showMarkerInfoWindow(update.callsign, marker.pos);
+ map.panTo(marker.position);
+ showMarkerInfoWindow(update.callsign, marker.position);
expectedCallsign = false;
}
if (infoWindow && infoWindow.callsign && infoWindow.callsign == update.callsign) {
- showMarkerInfoWindow(infoWindow.callsign, marker.pos);
+ showMarkerInfoWindow(infoWindow.callsign, marker.position);
}
break;
diff --git a/htdocs/mapLeaflet.js b/htdocs/mapLeaflet.js
new file mode 100644
index 00000000..fe363e58
--- /dev/null
+++ b/htdocs/mapLeaflet.js
@@ -0,0 +1,312 @@
+// Marker.linkify() uses these URLs
+var callsign_url = null;
+var vessel_url = null;
+var flight_url = null;
+
+// reasonable default; will be overriden by server
+var retention_time = 2 * 60 * 60 * 1000;
+
+// Our Leaflet Map and layerControl
+var map = null;
+var layerControl;
+
+// Receiver location marker
+var receiverMarker = null;
+
+// Updates are queued here
+var updateQueue = [];
+
+// Web socket connection management, message processing
+var mapManager = new MapManager();
+
+// icons cache
+var icons = {};
+
+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;
+
+// https://stackoverflow.com/a/46981806/420585
+function fetchStyleSheet(url, media = 'screen') {
+ let $dfd = $.Deferred(),
+ finish = () => $dfd.resolve(),
+ $link = $(document.createElement('link')).attr({
+ media,
+ type: 'text/css',
+ rel: 'stylesheet'
+ })
+ .on('load', 'error', finish)
+ .appendTo('head'),
+ $img = $(document.createElement('img'))
+ .on('error', finish); // Support browsers that don't fire events on link elements
+ $link[0].href = $img[0].src = url;
+ return $dfd.promise();
+}
+
+
+
+// Show information bubble for a locator
+function showLocatorInfoWindow(locator, pos) {
+ var p = new posObj(pos);
+
+ L.popup(pos, {
+ content: mapManager.lman.getInfoHTML(locator, p, receiverMarker)
+ }).openOn(map);
+};
+
+// Show information bubble for a marker
+function showMarkerInfoWindow(name, pos) {
+ var marker = mapManager.mman.find(name);
+ L.popup(pos, { content: marker.getInfoHTML(name, receiverMarker) }).openOn(map);
+};
+
+//
+// Leaflet-SPECIFIC MAP MANAGER METHODS
+//
+
+MapManager.prototype.setReceiverName = function(name) {
+ if (receiverMarker) receiverMarker.setTitle(name);
+}
+
+MapManager.prototype.removeReceiver = function() {
+ if (receiverMarker) receiverMarker.setMap();
+}
+
+MapManager.prototype.initializeMap = function(receiver_gps, api_key) {
+ var receiverPos = [ receiver_gps.lat, receiver_gps.lon ];
+
+ if (map) {
+ receiverMarker.setLatLng(receiverPos);
+ receiverMarker.setMarkerOptions(this.config);
+ receiverMarker.setMap(map);
+ } else {
+ var self = this;
+
+ // load Leaflet CSS first
+ fetchStyleSheet('https://unpkg.com/leaflet@1.9.4/dist/leaflet.css').done(function () {
+ // now load Leaflet JS
+ $.getScript('https://unpkg.com/leaflet@1.9.4/dist/leaflet.js').done(function () {
+ // create map
+ map = L.map('openwebrx-map').setView(receiverPos, 5);
+ baseLayer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ maxZoom: 19,
+ noWrap: true,
+ attribution: '© OpenStreetMap'
+ }).addTo(map);
+ // add night overlay
+ $.getScript('https://unpkg.com/@joergdietrich/leaflet.terminator@1.0.0/L.Terminator.js').done(function () {
+ var pane = map.createPane('nite');
+ pane.style.zIndex = 201;
+ pane.style.pointerEvents = 'none !important';
+ pane.style.cursor = 'grab !important';
+ var t = L.terminator({ fillOpacity: 0.2, interactive: false, pane });
+ t.addTo(map);
+ setInterval(function () { t.setTime(); }, 10000); // refresh every 10 secs
+ });
+
+ // create layerControl and add more maps
+ if (!layerControl) {
+ var OpenTopoMap = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
+ maxZoom: 17,
+ noWrap: true,
+ attribution: 'Map data: ©
OpenStreetMap contributors,
SRTM | Map style: ©
OpenTopoMap (
CC-BY-SA)'
+ });
+ var Stadia_AlidadeSmooth = L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_smooth/{z}/{x}/{y}{r}.png', {
+ maxZoom: 20,
+ noWrap: true,
+ attribution: '©
Stadia Maps, ©
OpenMapTiles ©
OpenStreetMap contributors',
+ });
+ var Esri_WorldTopoMap = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', {
+ noWrap: true,
+ attribution: 'Tiles © Esri — Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community'
+ });
+ var Stadia_AlidadeSmoothDark = L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png', {
+ maxZoom: 20,
+ noWrap: true,
+ attribution: '©
Stadia Maps, ©
OpenMapTiles ©
OpenStreetMap contributors'
+ });
+
+ // used to open or collaps the layerControl by default
+ function isMobile () {
+ try { document.createEvent("TouchEvent"); return true; }
+ catch (e) { return false; }
+ }
+
+ layerControl = L.control.layers({
+ 'OSM': baseLayer,
+ 'StadiaAlidade': Stadia_AlidadeSmooth,
+ 'StadiaAlidadeDark': Stadia_AlidadeSmoothDark,
+ 'EsriWorldTopo': Esri_WorldTopoMap,
+ 'OpenTopoMap': OpenTopoMap,
+ }, null, {
+ collapsed: isMobile(), hideSingleBase: true, position: 'bottomleft'
+ }
+ ).addTo(map);
+
+ // move legend div to our layerControl
+ $('
').insertAfter(layerControl._overlaysList);
+ layerControl.legend = $('.openwebrx-map-legend').insertAfter($('#openwebrx-map-legend-separator'));
+ } // layerControl
+
+ // Load and initialize OWRX-specific map item managers
+ $.getScript('static/lib/Leaflet.js').done(function() {
+ // Process any accumulated updates
+ self.processUpdates(updateQueue);
+ updateQueue = [];
+
+ if (!receiverMarker) {
+ receiverMarker = new LMarker();
+ receiverMarker.setMarkerPosition(self.config['receiver_name'], receiverPos[0], receiverPos[1]);
+ receiverMarker.addListener('click', function () {
+ L.popup(receiverMarker.getPos(), {
+ content: '
' + self.config['receiver_name'] + '
' +
+ '
Receiver location
'
+ }).openOn(map);
+ });
+ receiverMarker.setMarkerOptions(this.config);
+ receiverMarker.setMap(map);
+ }
+ });
+
+ // Create map legend selectors
+ self.setupLegendFilters(layerControl.legend);
+
+ }); // leaflet.js
+ }); // leaflet.css
+ }
+};
+
+MapManager.prototype.processUpdates = function(updates) {
+ var self = this;
+
+ if (typeof(LMarker) === '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 = LMarker;
+ var aprsOptions = {}
+
+ if (update.location.symbol) {
+ markerClass = LAprsMarker;
+ 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.getPos());
+ });
+ marker.div = marker.create();
+ marker.setIcon(L.divIcon({ html: marker.div, className: 'dummy' }));
+ }
+
+ // 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.setView(marker.getPos());
+ showMarkerInfoWindow(update.callsign, marker.getPos());
+ expectedCallsign = false;
+ }
+ 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 LFeatureMarker();
+ if (!icons[update.mode]) {
+ icons[update.mode] = L.icon({
+ iconUrl: 'static/gfx/icon-' + update.mode + '.png',
+ iconSize: [24, 24],
+ });
+ }
+ marker.setIcon(icons[update.mode]);
+ self.mman.addType(update.mode);
+ self.mman.add(update.callsign, marker);
+ marker.addListener('click', function() {
+ showMarkerInfoWindow(update.callsign, marker.getPos());
+ });
+ }
+
+ // 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.setView(marker.getPos());
+ showMarkerInfoWindow(update.callsign, marker.getPos());
+ expectedCallsign = false;
+ }
+ break;
+
+ case 'locator':
+ var rectangle = self.lman.find(update.callsign);
+
+ // If new item, create a new locator for it
+ if (!rectangle) {
+ rectangle = new LLocator();
+ self.lman.add(update.callsign, rectangle);
+ rectangle.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.setView(rectangle.center);
+ showLocatorInfoWindow(expectedLocator, rectangle.center);
+ expectedLocator = false;
+ }
+ break;
+ }
+ });
+};
diff --git a/owrx/controllers/assets.py b/owrx/controllers/assets.py
index 52755acd..7cb50214 100644
--- a/owrx/controllers/assets.py
+++ b/owrx/controllers/assets.py
@@ -151,6 +151,16 @@ class CompiledAssetsController(GzipMixin, ModificationAwareController):
"lib/Clock.js",
"map.js",
],
+ "mapLeaflet.js": [
+ "lib/jquery-3.2.1.min.js",
+ "lib/chroma.min.js",
+ "lib/Header.js",
+ "lib/MapLocators.js",
+ "lib/MapMarkers.js",
+ "lib/MapManager.js",
+ "lib/Clock.js",
+ "mapLeaflet.js",
+ ],
"settings.js": [
"lib/jquery-3.2.1.min.js",
"lib/bootstrap.bundle.min.js",