From 33199038e48e759682b624c080f507462d8b8e71 Mon Sep 17 00:00:00 2001
From: Marat Fayzullin
Date: Fri, 28 Jul 2023 00:24:45 -0400
Subject: [PATCH 01/10] Moving common markers functionality to
MarkerManager.js.
---
htdocs/lib/MarkerManager.js | 271 +++++++++++++++++++++++++++++++++++-
htdocs/map.js | 71 +---------
2 files changed, 270 insertions(+), 72 deletions(-)
diff --git a/htdocs/lib/MarkerManager.js b/htdocs/lib/MarkerManager.js
index 700ff71f..d495eb21 100644
--- a/htdocs/lib/MarkerManager.js
+++ b/htdocs/lib/MarkerManager.js
@@ -72,7 +72,7 @@ MarkerManager.prototype.addType = function(type) {
this.enabled[type] = enabled;
// If there is a list of features...
- var $content = $(".openwebrx-map-legend").find('.features');
+ var $content = $('.openwebrx-map-legend').find('.features');
if($content)
{
// Add visual list item for the type
@@ -85,6 +85,81 @@ MarkerManager.prototype.addType = function(type) {
}
};
+//
+// Generic marker functionality
+//
+
+function Marker() {}
+
+// Wrap given callsign or other ID into a clickable link. When
+// URL not supplied, guess the correct URL by ID type.
+Marker.linkify = function(callsign, url = null) {
+ // Leave passed URLs as they are
+ if (url && (url != ''))
+ { /* leave as is */ }
+ // 9-character strings may be AIS MMSI numbers
+ else if (callsign.match(new RegExp('^[0-9]{9}$')))
+ url = vessel_url;
+ // 3 characters and a number may be a flight number
+ else if (callsign.match(new RegExp('^[A-Z]{3,4}[0-9]{1,4}[A-Z]{0,2}$')))
+ url = flight_url;
+ // 2 characters and a long number may be a flight number
+ else if (callsign.match(new RegExp('^[A-Z]{2}[0-9]{2,4}[A-Z]{0,2}$')))
+ url = flight_url;
+ // Everything else is a HAM callsign
+ else
+ url = callsign_url;
+
+ // Must have valid lookup URL
+ if ((url == null) || (url == ''))
+ return callsign;
+ else
+ return '' + callsign + '';
+}
+
+// Compute distance, in kilometers, between two latlons.
+Marker.distanceKm = function(p1, p2) {
+ // Earth radius in km
+ var R = 6371.0;
+ // Convert degrees to radians
+ var rlat1 = p1.lat() * (Math.PI/180);
+ var rlat2 = p2.lat() * (Math.PI/180);
+ // Compute difference in radians
+ var difflat = rlat2-rlat1;
+ var difflon = (p2.lng()-p1.lng()) * (Math.PI/180);
+ // Compute distance
+ d = 2 * R * Math.asin(Math.sqrt(
+ Math.sin(difflat/2) * Math.sin(difflat/2) +
+ Math.cos(rlat1) * Math.cos(rlat2) * Math.sin(difflon/2) * Math.sin(difflon/2)
+ ));
+ return Math.round(d);
+}
+
+// Truncate string to a given number of characters, adding "..."
+// to the end.
+Marker.truncate = function(str, count) {
+ return str.length > count? str.slice(0, count) + '…' : str;
+}
+
+// Convert degrees to compass direction.
+Marker.degToCompass = function(deg) {
+ dir = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'];
+ return dir[Math.floor((deg/22.5) + 0.5) % 16];
+}
+
+Marker.makeListTitle = function(name) {
+ return '' + name + '
';
+}
+
+Marker.makeListItem = function(name, value) {
+ return ''
+ + '' + name + ' '
+ + '' + value + ''
+ + '
';
+}
+
//
// Feature Markers
//
@@ -128,9 +203,9 @@ FeatureMarker.prototype.onAdd = function() {
div.style.lineHeight = this.symHeight + 'px';
var self = this;
- google.maps.event.addDomListener(div, "click", function(event) {
+ google.maps.event.addDomListener(div, 'click', function(event) {
event.stopPropagation();
- google.maps.event.trigger(self, "click", event);
+ google.maps.event.trigger(self, 'click', event);
});
var panes = this.getPanes();
@@ -147,3 +222,193 @@ FeatureMarker.prototype.remove = function() {
FeatureMarker.prototype.getAnchorPoint = function() {
return new google.maps.Point(0, -this.symHeight/2);
};
+
+FeatureMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
+ var nameString = this.url? Marker.linkify(name, this.url) : name;
+ var commentString = this.comment? '' + this.comment + '
' : '';
+ var detailsString = '';
+ var scheduleString = '';
+ var distance = '';
+
+ if (this.altitude) {
+ detailsString += Marker.makeListItem('Altitude', this.altitude.toFixed(0) + ' m');
+ }
+
+ if (this.device) {
+ detailsString += Marker.makeListItem('Device', this.device.manufacturer?
+ this.device.device + ' by ' + this.device.manufacturer : this.device
+ );
+ }
+
+ if (this.antenna) {
+ detailsString += Marker.makeListItem('Antenna', Marker.truncate(this.antenna, 24));
+ }
+
+ if (this.schedule) {
+ for (var j=0 ; j'
+ + Math.round(this.schedule[j].freq/1000) + 'kHz';
+
+ scheduleString += Marker.makeListItem(name, freq);
+ }
+ }
+
+ if (detailsString.length > 0) {
+ detailsString = '' + Marker.makeListTitle('Details') + detailsString + '
';
+ }
+
+ if (scheduleString.length > 0) {
+ scheduleString = '' + Marker.makeListTitle('Schedule') + scheduleString + '
';
+ }
+
+ if (receiverMarker) {
+ distance = ' at ' + Marker.distanceKm(receiverMarker.position, this.position) + ' km';
+ }
+
+ return '' + nameString + distance + '
'
+ + commentString + detailsString + scheduleString;
+}
+
+//
+// APRS Markers (also AIS and HFDL)
+//
+
+function NewAprsMarker() {}
+
+NewAprsMarker.prototype = new google.maps.OverlayView();
+
+NewAprsMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
+ var timeString = moment(this.lastseen).fromNow();
+ var commentString = '';
+ var weatherString = '';
+ var detailsString = '';
+ var hopsString = '';
+ var distance = '';
+
+ if (this.comment) {
+ commentString += '' + Marker.makeListTitle('Comment') + '
' +
+ this.comment + '
';
+ }
+
+ if (this.weather) {
+ weatherString += '' + Marker.makeListTitle('Weather');
+
+ if (this.weather.temperature) {
+ weatherString += Marker.makeListItem('Temperature', this.weather.temperature.toFixed(1) + ' oC');
+ }
+
+ if (this.weather.humidity) {
+ weatherString += Marker.makeListItem('Humidity', this.weather.humidity + '%');
+ }
+
+ if (this.weather.barometricpressure) {
+ weatherString += Marker.makeListItem('Pressure', this.weather.barometricpressure.toFixed(1) + ' mbar');
+ }
+
+ if (this.weather.wind) {
+ if (this.weather.wind.speed && (this.weather.wind.speed>0)) {
+ weatherString += Marker.makeListItem('Wind',
+ degToCompass(this.weather.wind.direction) + ' ' +
+ this.weather.wind.speed.toFixed(1) + ' km/h '
+ );
+ }
+
+ if (this.weather.wind.gust && (this.weather.wind.gust>0)) {
+ weatherString += Marker.makeListItem('Gusts', this.weather.wind.gust.toFixed(1) + ' km/h');
+ }
+ }
+
+ if (this.weather.rain && (this.weather.rain.day>0)) {
+ weatherString += Marker.makeListItem('Rain',
+ this.weather.rain.hour.toFixed(0) + ' mm/h, ' +
+ this.weather.rain.day.toFixed(0) + ' mm/day'
+// this.weather.rain.sincemidnight + ' mm since midnight'
+ );
+ }
+
+ if (this.weather.snowfall) {
+ weatherString += Marker.makeListItem('Snow', this.weather.snowfall.toFixed(1) + ' cm');
+ }
+
+ weatherString += '
';
+ }
+
+ if (this.altitude) {
+ detailsString += Marker.makeListItem('Altitude', this.altitude.toFixed(0) + ' m');
+ }
+
+ if (this.device) {
+ detailsString += Marker.makeListItem('Device', this.device.manufacturer?
+ this.device.device + ' by ' + this.device.manufacturer : this.device
+ );
+ }
+
+ if (this.height) {
+ detailsString += Marker.makeListItem('Height', this.height.toFixed(0) + ' m');
+ }
+
+ if (this.power) {
+ detailsString += Marker.makeListItem('Power', this.power + ' W');
+ }
+
+ if (this.gain) {
+ detailsString += Marker.makeListItem('Gain', this.gain + ' dB');
+ }
+
+ if (this.directivity) {
+ detailsString += Marker.makeListItem('Direction', this.directivity);
+ }
+
+ if (this.aircraft) {
+ detailsString += Marker.makeListItem('Aircraft', this.aircraft);
+ }
+
+ // Combine course and speed if both present
+ if (this.course && this.speed) {
+ detailsString += Marker.makeListItem('Course',
+ Marker.degToCompass(this.course) + ' ' +
+ this.speed.toFixed(1) + ' km/h'
+ );
+ } else {
+ if (this.course) {
+ detailsString += Marker.makeListItem('Course', degToCompass(this.course));
+ }
+ if (this.speed) {
+ detailsString += Marker.makeListItem('Speed', this.speed.toFixed(1) + ' km/h');
+ }
+ }
+
+ if (detailsString.length > 0) {
+ detailsString = '' + Marker.makeListTitle('Details') + detailsString + '
';
+ }
+
+ if (receiverMarker) {
+ distance = ' at ' + Marker.distanceKm(receiverMarker.position, this.position) + ' km';
+ }
+
+ if (this.hops && this.hops.length > 0) {
+ var hops = this.hops.toString().split(',');
+ hops.forEach(function(part, index, hops) {
+ hops[index] = Marker.linkify(part);
+ });
+
+ hopsString = 'via ' + hops.join(', ') + '
';
+ }
+
+ return '' + Marker.linkify(name) + distance + '
'
+ + '' + timeString + ' using ' + this.mode
+ + ( this.band ? ' on ' + this.band : '' ) + '
'
+ + commentString + weatherString + detailsString + hopsString;
+}
diff --git a/htdocs/map.js b/htdocs/map.js
index 6ecd3263..24decccc 100644
--- a/htdocs/map.js
+++ b/htdocs/map.js
@@ -663,77 +663,10 @@ $(function(){
var showFeatureInfoWindow = function(name, pos) {
var infowindow = getInfoWindow();
- infowindow.callsign = name;
var marker = markers[name];
- var commentString = "";
- var detailsString = "";
- var scheduleString = "";
- var nameString = "";
- var distance = "";
-
- if (marker.url) {
- nameString += linkifyCallsign(name, marker.url);
- } else {
- nameString += name;
- }
-
- if (marker.comment) {
- commentString += '' + marker.comment + '
';
- }
-
- if (marker.altitude) {
- detailsString += makeListItem('Altitude', marker.altitude.toFixed(0) + ' m');
- }
-
- if (marker.device) {
- detailsString += makeListItem('Device', marker.device.manufacturer?
- marker.device.device + " by " + marker.device.manufacturer
- : marker.device
- );
- }
-
- if (marker.antenna) {
- detailsString += makeListItem('Antenna', truncate(marker.antenna, 24));
- }
-
- if (marker.schedule) {
- for (var j=0 ; j'
- + Math.round(marker.schedule[j].freq/1000) + 'kHz';
-
- scheduleString += makeListItem(name, freq);
- }
- }
-
- if (detailsString.length > 0) {
- detailsString = '' + makeListTitle('Details') + detailsString + '
';
- }
-
- if (scheduleString.length > 0) {
- scheduleString = '' + makeListTitle('Schedule') + scheduleString + '
';
- }
-
- if (receiverMarker) {
- distance = " at " + distanceKm(receiverMarker.position, marker.position) + " km";
- }
-
- infowindow.setContent(
- '' + nameString + distance + '
' +
- commentString + detailsString + scheduleString
- );
+ infowindow.callsign = name;
+ infowindow.setContent(marker.getInfoHTML(name, receiverMarker));
infowindow.open(map, marker);
}
From 62da3e296b30b7b4a1da5f722b136a1f8361b6ce Mon Sep 17 00:00:00 2001
From: Marat Fayzullin
Date: Fri, 28 Jul 2023 17:57:03 -0400
Subject: [PATCH 02/10] Refactoring markers.
---
htdocs/lib/MarkerManager.js | 147 +++++++++++++++++---
htdocs/map.js | 260 +++---------------------------------
2 files changed, 152 insertions(+), 255 deletions(-)
diff --git a/htdocs/lib/MarkerManager.js b/htdocs/lib/MarkerManager.js
index d495eb21..f026225a 100644
--- a/htdocs/lib/MarkerManager.js
+++ b/htdocs/lib/MarkerManager.js
@@ -91,8 +91,8 @@ MarkerManager.prototype.addType = function(type) {
function Marker() {}
-// Wrap given callsign or other ID into a clickable link. When
-// URL not supplied, guess the correct URL by ID type.
+// Wrap given callsign or other ID into a clickable link.
+// When URL not supplied, guess the correct URL by ID type.
Marker.linkify = function(callsign, url = null) {
// Leave passed URLs as they are
if (url && (url != ''))
@@ -117,7 +117,7 @@ Marker.linkify = function(callsign, url = null) {
return '' + callsign + '';
-}
+};
// Compute distance, in kilometers, between two latlons.
Marker.distanceKm = function(p1, p2) {
@@ -135,30 +135,31 @@ Marker.distanceKm = function(p1, p2) {
Math.cos(rlat1) * Math.cos(rlat2) * Math.sin(difflon/2) * Math.sin(difflon/2)
));
return Math.round(d);
-}
+};
-// Truncate string to a given number of characters, adding "..."
-// to the end.
+// Truncate string to a given number of characters, adding "..." to the end.
Marker.truncate = function(str, count) {
return str.length > count? str.slice(0, count) + '…' : str;
-}
+};
// Convert degrees to compass direction.
Marker.degToCompass = function(deg) {
dir = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'];
return dir[Math.floor((deg/22.5) + 0.5) % 16];
-}
+};
+// Convert given name to an information section title.
Marker.makeListTitle = function(name) {
return '' + name + '
';
-}
+};
+// Convert given name/value to an information section item.
Marker.makeListItem = function(name, value) {
return ''
+ '' + name + ' '
+ '' + value + ''
+ '
';
-}
+};
//
// Feature Markers
@@ -168,6 +169,17 @@ function FeatureMarker() {}
FeatureMarker.prototype = new google.maps.OverlayView();
+FeatureMarker.prototype.update = function(update) {
+ this.lastseen = update.lastseen;
+ this.mode = update.mode;
+ this.url = update.location.url;
+ this.comment = update.location.comment;
+ this.altitude = update.location.altitude;
+ this.device = update.location.device;
+ this.antenna = update.location.antenna;
+ this.schedule = update.location.schedule;
+};
+
FeatureMarker.prototype.draw = function() {
var div = this.div;
if (!div) return;
@@ -279,17 +291,118 @@ FeatureMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
return '' + nameString + distance + '
'
+ commentString + detailsString + scheduleString;
-}
+};
//
// APRS Markers (also AIS and HFDL)
//
-function NewAprsMarker() {}
+function AprsMarker() {}
-NewAprsMarker.prototype = new google.maps.OverlayView();
+AprsMarker.prototype = new google.maps.OverlayView();
-NewAprsMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
+AprsMarker.prototype.update = function(update) {
+ this.lastseen = update.lastseen;
+ this.mode = update.mode;
+ this.hops = update.hops;
+ this.band = update.band;
+ this.comment = update.location.comment;
+ this.weather = update.location.weather;
+ this.altitude = update.location.altitude;
+ this.height = update.location.height;
+ this.power = update.location.power;
+ this.gain = update.location.gain;
+ this.device = update.location.device;
+ this.aircraft = update.location.aircraft;
+ this.directivity = update.location.directivity;
+};
+
+AprsMarker.prototype.draw = function() {
+ var div = this.div;
+ var overlay = this.overlay;
+ if (!div || !overlay) return;
+
+ if (this.symbol) {
+ var tableId = this.symbol.table === '/' ? 0 : 1;
+ div.style.background = 'url(aprs-symbols/aprs-symbols-24-' + tableId + '@2x.png)';
+ div.style['background-size'] = '384px 144px';
+ div.style['background-position-x'] = -(this.symbol.index % 16) * 24 + 'px';
+ div.style['background-position-y'] = -Math.floor(this.symbol.index / 16) * 24 + 'px';
+ }
+
+ if (!this.course) {
+ div.style.transform = null;
+ } else if (this.course > 180) {
+ div.style.transform = 'scalex(-1) rotate(' + (270 - this.course) + 'deg)'
+ } else {
+ div.style.transform = 'rotate(' + (this.course - 90) + 'deg)';
+ }
+
+ if (this.symbol.table !== '/' && this.symbol.table !== '\\') {
+ overlay.style.display = 'block';
+ overlay.style['background-position-x'] = -(this.symbol.tableindex % 16) * 24 + 'px';
+ overlay.style['background-position-y'] = -Math.floor(this.symbol.tableindex / 16) * 24 + 'px';
+ } else {
+ overlay.style.display = 'none';
+ }
+
+ if (this.opacity) {
+ div.style.opacity = this.opacity;
+ } else {
+ div.style.opacity = null;
+ }
+
+ var point = this.getProjection().fromLatLngToDivPixel(this.position);
+ if (point) {
+ div.style.left = point.x - 12 + 'px';
+ div.style.top = point.y - 12 + 'px';
+ }
+};
+
+AprsMarker.prototype.setOptions = function(options) {
+ google.maps.OverlayView.prototype.setOptions.apply(this, arguments);
+ this.draw();
+};
+
+AprsMarker.prototype.onAdd = function() {
+ var div = this.div = document.createElement('div');
+
+ div.style.position = 'absolute';
+ div.style.cursor = 'pointer';
+ div.style.width = '24px';
+ div.style.height = '24px';
+
+ var overlay = this.overlay = document.createElement('div');
+ overlay.style.width = '24px';
+ overlay.style.height = '24px';
+ overlay.style.background = 'url(aprs-symbols/aprs-symbols-24-2@2x.png)';
+ overlay.style['background-size'] = '384px 144px';
+ overlay.style.display = 'none';
+
+ div.appendChild(overlay);
+
+ 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);
+};
+
+AprsMarker.prototype.remove = function() {
+ if (this.div) {
+ this.div.parentNode.removeChild(this.div);
+ this.div = null;
+ }
+};
+
+AprsMarker.prototype.getAnchorPoint = function() {
+ return new google.maps.Point(0, -12);
+};
+
+AprsMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
var timeString = moment(this.lastseen).fromNow();
var commentString = '';
var weatherString = '';
@@ -320,7 +433,7 @@ NewAprsMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
if (this.weather.wind) {
if (this.weather.wind.speed && (this.weather.wind.speed>0)) {
weatherString += Marker.makeListItem('Wind',
- degToCompass(this.weather.wind.direction) + ' ' +
+ Marker.degToCompass(this.weather.wind.direction) + ' ' +
this.weather.wind.speed.toFixed(1) + ' km/h '
);
}
@@ -383,7 +496,7 @@ NewAprsMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
);
} else {
if (this.course) {
- detailsString += Marker.makeListItem('Course', degToCompass(this.course));
+ detailsString += Marker.makeListItem('Course', Marker.degToCompass(this.course));
}
if (this.speed) {
detailsString += Marker.makeListItem('Speed', this.speed.toFixed(1) + ' km/h');
@@ -411,4 +524,4 @@ NewAprsMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
+ '' + timeString + ' using ' + this.mode
+ ( this.band ? ' on ' + this.band : '' ) + '
'
+ commentString + weatherString + detailsString + hopsString;
-}
+};
diff --git a/htdocs/map.js b/htdocs/map.js
index 24decccc..1cf5af38 100644
--- a/htdocs/map.js
+++ b/htdocs/map.js
@@ -1,3 +1,10 @@
+//
+// Marker.linkify() uses these URLs
+//
+var callsign_url = null;
+var vessel_url = null;
+var flight_url = null;
+
$(function(){
var query = window.location.search.replace(/^\?/, '').split('&').map(function(v){
var s = v.split('=');
@@ -37,9 +44,6 @@ $(function(){
var retention_time = 2 * 60 * 60 * 1000;
var strokeOpacity = 0.8;
var fillOpacity = 0.35;
- var callsign_url = null;
- var vessel_url = null;
- var flight_url = null;
// marker manager
var markmanager = null;
@@ -148,19 +152,9 @@ $(function(){
map: markmanager.isEnabled(update.mode)? map : undefined,
title: update.callsign
}, aprsOptions, getMarkerOpacityOptions(update.lastseen) ));
- marker.lastseen = update.lastseen;
- marker.mode = update.mode;
- marker.hops = update.hops;
- marker.band = update.band;
- marker.comment = update.location.comment;
- marker.weather = update.location.weather;
- marker.altitude = update.location.altitude;
- marker.height = update.location.height;
- marker.power = update.location.power;
- marker.gain = update.location.gain;
- marker.device = update.location.device;
- marker.aircraft = update.location.aircraft;
- marker.directivity = update.location.directivity;
+
+ // Update marker attributes
+ marker.update(update);
if (expectedCallsign && expectedCallsign == update.callsign) {
map.panTo(pos);
@@ -195,7 +189,7 @@ $(function(){
} else {
marker = new FeatureMarker();
marker.addListener('click', function(){
- showFeatureInfoWindow(update.callsign, pos);
+ showMarkerInfoWindow(update.callsign, pos);
});
markers[update.callsign] = marker;
markmanager.addType(update.mode);
@@ -208,24 +202,17 @@ $(function(){
title: update.callsign
}, options));
- // Get attributes
- marker.lastseen = update.lastseen;
- marker.mode = update.mode;
- marker.url = update.location.url;
- marker.comment = update.location.comment;
- marker.altitude = update.location.altitude;
- marker.device = update.location.device;
- marker.antenna = update.location.antenna;
- marker.schedule = update.location.schedule;
+ // Update marker attributes
+ marker.update(update);
if (expectedCallsign && expectedCallsign == update.callsign) {
map.panTo(pos);
- showFeatureInfoWindow(update.callsign, pos);
+ showMarkerInfoWindow(update.callsign, pos);
expectedCallsign = false;
}
if (infowindow && infowindow.callsign && infowindow.callsign == update.callsign) {
- showFeatureInfoWindow(infowindow.callsign, pos);
+ showMarkerInfoWindow(infowindow.callsign, pos);
}
break;
case 'locator':
@@ -326,19 +313,11 @@ $(function(){
nite.init(map);
setInterval(function() { nite.refresh() }, 10000); // every 10s
});
- $.getScript('static/lib/AprsMarker.js').done(function(){
- if(typeof(FeatureMarker) != 'undefined') {
- markmanager = new MarkerManager();
- processUpdates(updateQueue);
- updateQueue = [];
- }
- });
+
$.getScript('static/lib/MarkerManager.js').done(function(){
- if(typeof(AprsMarker) != 'undefined') {
- markmanager = new MarkerManager();
- processUpdates(updateQueue);
- updateQueue = [];
- }
+ markmanager = new MarkerManager();
+ processUpdates(updateQueue);
+ updateQueue = [];
});
var $legend = $(".openwebrx-map-legend");
@@ -436,53 +415,6 @@ $(function(){
return infowindow;
}
- var linkifyCallsign = function(callsign, url = null) {
- // Leave passed URLs as they are
- if (url && (url != ''))
- { /* leave as is */ }
- // 9-character strings may be AIS MMSI numbers
- else if (callsign.match(new RegExp('^[0-9]{9}$')))
- url = vessel_url;
- // 3 characters and a number may be a flight number
- else if (callsign.match(new RegExp('^[A-Z]{3,4}[0-9]{1,4}[A-Z]{0,2}$')))
- url = flight_url;
- // 2 characters and a long number may be a flight number
- else if (callsign.match(new RegExp('^[A-Z]{2}[0-9]{2,4}[A-Z]{0,2}$')))
- url = flight_url;
- // Everything else is a HAM callsign
- else
- url = callsign_url;
-
- // Must have valid lookup URL
- if ((url == null) || (url == ''))
- return callsign;
- else
- return '' + callsign + '';
- }
-
- var distanceKm = function(p1, p2) {
- // Earth radius in km
- var R = 6371.0;
- // Convert degrees to radians
- var rlat1 = p1.lat() * (Math.PI/180);
- var rlat2 = p2.lat() * (Math.PI/180);
- // Compute difference in radians
- var difflat = rlat2-rlat1;
- var difflon = (p2.lng()-p1.lng()) * (Math.PI/180);
- // Compute distance
- d = 2 * R * Math.asin(Math.sqrt(
- Math.sin(difflat/2) * Math.sin(difflat/2) +
- Math.cos(rlat1) * Math.cos(rlat2) * Math.sin(difflon/2) * Math.sin(difflon/2)
- ));
- return Math.round(d);
- }
-
- var truncate = function(str, count) {
- return str.length > count? str.slice(0, count) + '…' : str;
- }
-
var infowindow;
var showLocatorInfoWindow = function(locator, pos) {
var infowindow = getInfoWindow();
@@ -495,14 +427,14 @@ $(function(){
return b.lastseen - a.lastseen;
});
var distance = receiverMarker?
- " at " + distanceKm(receiverMarker.position, pos) + " km" : "";
+ " at " + Marker.distanceKm(receiverMarker.position, pos) + " km" : "";
infowindow.setContent(
'Locator: ' + locator + distance + '
' +
'Active Callsigns:
' +
'' +
inLocator.map(function(i){
var timestring = moment(i.lastseen).fromNow();
- var message = linkifyCallsign(i.callsign) + ' (' + timestring + ' using ' + i.mode;
+ var message = Marker.linkify(i.callsign) + ' (' + timestring + ' using ' + i.mode;
if (i.band) message += ' on ' + i.band;
message += ')';
return '- ' + message + '
'
@@ -513,155 +445,7 @@ $(function(){
infowindow.open(map);
};
- var degToCompass = function(deg) {
- dir = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"];
- return dir[Math.floor((deg/22.5) + 0.5) % 16];
- }
-
- var makeListTitle = function(name) {
- return '' + name + '
';
- }
-
- var makeListItem = function(name, value) {
- return ''
- + '' + name + ' '
- + '' + value + ''
- + '
';
- }
-
- var showMarkerInfoWindow = function(callsign, pos) {
- var infowindow = getInfoWindow();
- infowindow.callsign = callsign;
- var marker = markers[callsign];
- var timeString = moment(marker.lastseen).fromNow();
- var commentString = "";
- var weatherString = "";
- var detailsString = "";
- var hopsString = "";
- var distance = "";
-
- if (marker.comment) {
- commentString += '' + makeListTitle('Comment') + '
' +
- marker.comment + '
';
- }
-
- if (marker.weather) {
- weatherString += '' + makeListTitle('Weather');
-
- if (marker.weather.temperature) {
- weatherString += makeListItem('Temperature', marker.weather.temperature.toFixed(1) + ' oC');
- }
-
- if (marker.weather.humidity) {
- weatherString += makeListItem('Humidity', marker.weather.humidity + '%');
- }
-
- if (marker.weather.barometricpressure) {
- weatherString += makeListItem('Pressure', marker.weather.barometricpressure.toFixed(1) + ' mbar');
- }
-
- if (marker.weather.wind) {
- if (marker.weather.wind.speed && (marker.weather.wind.speed>0)) {
- weatherString += makeListItem('Wind',
- degToCompass(marker.weather.wind.direction) + ' ' +
- marker.weather.wind.speed.toFixed(1) + ' km/h '
- );
- }
-
- if (marker.weather.wind.gust && (marker.weather.wind.gust>0)) {
- weatherString += makeListItem('Gusts', marker.weather.wind.gust.toFixed(1) + ' km/h');
- }
- }
-
- if (marker.weather.rain && (marker.weather.rain.day>0)) {
- weatherString += makeListItem('Rain',
- marker.weather.rain.hour.toFixed(0) + ' mm/h, ' +
- marker.weather.rain.day.toFixed(0) + ' mm/day'
-// marker.weather.rain.sincemidnight + ' mm since midnight'
- );
- }
-
- if (marker.weather.snowfall) {
- weatherString += makeListItem('Snow', marker.weather.snowfall.toFixed(1) + ' cm');
- }
-
- weatherString += '
';
- }
-
- if (marker.altitude) {
- detailsString += makeListItem('Altitude', marker.altitude.toFixed(0) + ' m');
- }
-
- if (marker.device) {
- detailsString += makeListItem('Device', marker.device.manufacturer?
- marker.device.device + " by " + marker.device.manufacturer
- : marker.device
- );
- }
-
- if (marker.height) {
- detailsString += makeListItem('Height', marker.height.toFixed(0) + ' m');
- }
-
- if (marker.power) {
- detailsString += makeListItem('Power', marker.power + " W");
- }
-
- if (marker.gain) {
- detailsString += makeListItem('Gain', marker.gain + " dB");
- }
-
- if (marker.directivity) {
- detailsString += makeListItem('Direction', marker.directivity);
- }
-
- if (marker.aircraft) {
- detailsString += makeListItem('Aircraft', marker.aircraft);
- }
-
- // Combine course and speed if both present
- if (marker.course && marker.speed) {
- detailsString += makeListItem('Course',
- degToCompass(marker.course) + " " +
- marker.speed.toFixed(1) + " km/h"
- );
- } else {
- if (marker.course) {
- detailsString += makeListItem('Course', degToCompass(marker.course));
- }
- if (marker.speed) {
- detailsString += makeListItem('Speed', marker.speed.toFixed(1) + " km/h");
- }
- }
-
- if (detailsString.length > 0) {
- detailsString = '' + makeListTitle('Details') + detailsString + '
';
- }
-
- if (receiverMarker) {
- distance = " at " + distanceKm(receiverMarker.position, marker.position) + " km";
- }
-
- if (marker.hops && marker.hops.length > 0) {
- var hops = marker.hops.toString().split(',');
- hops.forEach(function(part, index, hops) {
- hops[index] = linkifyCallsign(part);
- });
-
- hopsString = 'via ' + hops.join(', ') + '
';
- }
-
- infowindow.setContent(
- '' + linkifyCallsign(callsign) + distance + '
' +
- '' + timeString + ' using ' + marker.mode +
- ( marker.band ? ' on ' + marker.band : '' ) + '
' +
- commentString + weatherString + detailsString + hopsString
- );
-
- infowindow.open(map, marker);
- }
-
- var showFeatureInfoWindow = function(name, pos) {
+ var showMarkerInfoWindow = function(name, pos) {
var infowindow = getInfoWindow();
var marker = markers[name];
From ba8f66497a68fbbc569ce49619622d452db1543b Mon Sep 17 00:00:00 2001
From: Marat Fayzullin
Date: Fri, 28 Jul 2023 23:13:24 -0400
Subject: [PATCH 03/10] Proper inheritance now.
---
htdocs/lib/MarkerManager.js | 83 ++++++++++++++++++++++---------------
htdocs/map.js | 12 +++---
2 files changed, 54 insertions(+), 41 deletions(-)
diff --git a/htdocs/lib/MarkerManager.js b/htdocs/lib/MarkerManager.js
index f026225a..54f2f983 100644
--- a/htdocs/lib/MarkerManager.js
+++ b/htdocs/lib/MarkerManager.js
@@ -167,7 +167,7 @@ Marker.makeListItem = function(name, value) {
function FeatureMarker() {}
-FeatureMarker.prototype = new google.maps.OverlayView();
+FeatureMarker.prototype = new FeatureMarker();
FeatureMarker.prototype.update = function(update) {
this.lastseen = update.lastseen;
@@ -194,12 +194,7 @@ FeatureMarker.prototype.draw = function() {
}
};
-FeatureMarker.prototype.setOptions = function(options) {
- google.maps.OverlayView.prototype.setOptions.apply(this, arguments);
- this.draw();
-};
-
-FeatureMarker.prototype.onAdd = function() {
+FeatureMarker.prototype.create = function() {
var div = this.div = document.createElement('div');
// Marker size
@@ -214,14 +209,7 @@ FeatureMarker.prototype.onAdd = function() {
div.style.fontSize = this.symHeight + 'px';
div.style.lineHeight = this.symHeight + 'px';
- 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);
+ return div;
};
FeatureMarker.prototype.remove = function() {
@@ -231,8 +219,8 @@ FeatureMarker.prototype.remove = function() {
}
};
-FeatureMarker.prototype.getAnchorPoint = function() {
- return new google.maps.Point(0, -this.symHeight/2);
+FeatureMarker.prototype.getAnchorOffset = function() {
+ return [0, -this.symHeight/2];
};
FeatureMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
@@ -299,7 +287,7 @@ FeatureMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
function AprsMarker() {}
-AprsMarker.prototype = new google.maps.OverlayView();
+AprsMarker.prototype = new AprsMarker();
AprsMarker.prototype.update = function(update) {
this.lastseen = update.lastseen;
@@ -359,12 +347,7 @@ AprsMarker.prototype.draw = function() {
}
};
-AprsMarker.prototype.setOptions = function(options) {
- google.maps.OverlayView.prototype.setOptions.apply(this, arguments);
- this.draw();
-};
-
-AprsMarker.prototype.onAdd = function() {
+AprsMarker.prototype.create = function() {
var div = this.div = document.createElement('div');
div.style.position = 'absolute';
@@ -381,14 +364,7 @@ AprsMarker.prototype.onAdd = function() {
div.appendChild(overlay);
- 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);
+ return div;
};
AprsMarker.prototype.remove = function() {
@@ -398,8 +374,8 @@ AprsMarker.prototype.remove = function() {
}
};
-AprsMarker.prototype.getAnchorPoint = function() {
- return new google.maps.Point(0, -12);
+AprsMarker.prototype.getAnchorOffset = function() {
+ return [0, -12];
};
AprsMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
@@ -525,3 +501,42 @@ AprsMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
+ ( this.band ? ' on ' + this.band : '' ) + ''
+ commentString + weatherString + detailsString + hopsString;
};
+
+//
+// GoogleMaps-Specific Markers (derived from generic markers)
+//
+
+function GMarker() {}
+GMarker.prototype = new google.maps.OverlayView();
+
+GMarker.prototype.setOptions = function(options) {
+ google.maps.OverlayView.prototype.setOptions.apply(this, arguments);
+ 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]);
+};
+
+// 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();
diff --git a/htdocs/map.js b/htdocs/map.js
index 1cf5af38..e8de67c1 100644
--- a/htdocs/map.js
+++ b/htdocs/map.js
@@ -132,7 +132,7 @@ $(function(){
var markerClass = google.maps.Marker;
var aprsOptions = {}
if (update.location.symbol) {
- markerClass = AprsMarker;
+ markerClass = GAprsMarker;
aprsOptions.symbol = update.location.symbol;
aprsOptions.course = update.location.course;
aprsOptions.speed = update.location.speed;
@@ -140,12 +140,11 @@ $(function(){
if (markers[update.callsign]) {
marker = markers[update.callsign];
} else {
- marker = new markerClass();
+ markmanager.addType(update.mode);
+ marker = markers[update.callsign] = new markerClass();
marker.addListener('click', function(){
showMarkerInfoWindow(update.callsign, pos);
});
- markers[update.callsign] = marker;
- markmanager.addType(update.mode);
}
marker.setOptions($.extend({
position: pos,
@@ -187,12 +186,11 @@ $(function(){
if (markers[update.callsign]) {
marker = markers[update.callsign];
} else {
- marker = new FeatureMarker();
+ markmanager.addType(update.mode);
+ marker = markers[update.callsign] = new GFeatureMarker();
marker.addListener('click', function(){
showMarkerInfoWindow(update.callsign, pos);
});
- markers[update.callsign] = marker;
- markmanager.addType(update.mode);
}
// Apply marker options
From 90a99f7a32778ed28a151697d51a1528e82be196 Mon Sep 17 00:00:00 2001
From: Marat Fayzullin
Date: Sat, 29 Jul 2023 14:01:50 -0400
Subject: [PATCH 04/10] Moved markers storage into MarkerManager.
---
htdocs/lib/MarkerManager.js | 57 ++++++++++++++++++++++----
htdocs/map.js | 80 +++++++++++++++----------------------
2 files changed, 82 insertions(+), 55 deletions(-)
diff --git a/htdocs/lib/MarkerManager.js b/htdocs/lib/MarkerManager.js
index 54f2f983..64756a9b 100644
--- a/htdocs/lib/MarkerManager.js
+++ b/htdocs/lib/MarkerManager.js
@@ -3,17 +3,20 @@
//
function MarkerManager() {
- // Currently known features
+ // Current markers
+ this.markers = {};
+
+ // Currently known marker types
this.types = {};
- // Colors used for features
+ // Colors used for marker types
this.colors = {
'KiwiSDR' : '#800000',
'WebSDR' : '#000080',
'OpenWebRX' : '#004000'
};
- // Symbols used for features
+ // Symbols used for marker types
this.symbols = {
'KiwiSDR' : '◬',
'WebSDR' : '◬',
@@ -24,7 +27,7 @@ function MarkerManager() {
'HFDL' : '✈'
};
- // Feature type shown/hidden status
+ // Marker type shown/hidden status
this.enabled = {
'KiwiSDR' : false,
'WebSDR' : false,
@@ -48,13 +51,13 @@ MarkerManager.prototype.isEnabled = function(type) {
return type in this.enabled? this.enabled[type] : true;
};
-MarkerManager.prototype.toggle = function(map, markers, type, onoff) {
+MarkerManager.prototype.toggle = function(map, type, onoff) {
// Keep track of each feature table being show or hidden
this.enabled[type] = onoff;
// Show or hide features on the map
- $.each(markers, function(_, r) {
- if (r.mode === type) r.setMap(onoff ? map : undefined);
+ $.each(this.markers, function(_, x) {
+ if (x.mode === type) x.setMap(onoff ? map : undefined);
});
};
@@ -85,6 +88,28 @@ MarkerManager.prototype.addType = function(type) {
}
};
+MarkerManager.prototype.find = function(id) {
+ return id in this.markers? this.markers[id] : null;
+};
+
+MarkerManager.prototype.add = function(id, marker) {
+ this.markers[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];
+ });
+};
+
+MarkerManager.prototype.clear = function() {
+ // Remove all markers from the map
+ $.each(this.markers, function(_, x) { x.setMap(); });
+ // Delete all markers
+ this.markers = {};
+};
+
//
// Generic marker functionality
//
@@ -161,6 +186,14 @@ Marker.makeListItem = function(name, value) {
+ '';
};
+Marker.getOpacityScale = function(age) {
+ var scale = 1;
+ if (age >= retention_time / 2) {
+ scale = (retention_time - age) / (retention_time / 2);
+ }
+ return Math.max(0, Math.min(1, scale));
+};
+
//
// Feature Markers
//
@@ -533,6 +566,16 @@ GMarker.prototype.getAnchorPoint = function() {
return new google.maps.Point(offset[0], offset[1]);
};
+GMarker.prototype.age = function(age) {
+ if(age <= retention_time) {
+ this.setOptions({ opacity: Marker.getOpacityScale(age) });
+ return true;
+ } else {
+ this.setMap();
+ return false;
+ }
+};
+
// GoogleMaps-Specific FeatureMarker
function GFeatureMarker() { $.extend(this, new FeatureMarker()); }
GFeatureMarker.prototype = new GMarker();
diff --git a/htdocs/map.js b/htdocs/map.js
index e8de67c1..4db1500a 100644
--- a/htdocs/map.js
+++ b/htdocs/map.js
@@ -1,10 +1,17 @@
-//
// 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;
+
+// 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';
+}
+
$(function(){
var query = window.location.search.replace(/^\?/, '').split('&').map(function(v){
var s = v.split('=');
@@ -35,13 +42,10 @@ $(function(){
var ws_url = href + "ws/";
var map;
- var markers = {};
var rectangles = {};
var receiverMarker;
var updateQueue = [];
- // reasonable default; will be overriden by server
- var retention_time = 2 * 60 * 60 * 1000;
var strokeOpacity = 0.8;
var fillOpacity = 0.35;
@@ -128,7 +132,7 @@ $(function(){
switch (update.location.type) {
case 'latlon':
var pos = new google.maps.LatLng(update.location.lat, update.location.lon);
- var marker;
+ var marker = markmanager.find(update.callsign);
var markerClass = google.maps.Marker;
var aprsOptions = {}
if (update.location.symbol) {
@@ -137,22 +141,26 @@ $(function(){
aprsOptions.course = update.location.course;
aprsOptions.speed = update.location.speed;
}
- if (markers[update.callsign]) {
- marker = markers[update.callsign];
- } else {
+
+ // If new item, create a new marker for it
+ if (!marker) {
+ marker = new markerClass();
markmanager.addType(update.mode);
- marker = markers[update.callsign] = new markerClass();
+ markmanager.add(update.callsign, marker);
marker.addListener('click', function(){
showMarkerInfoWindow(update.callsign, pos);
});
}
+
+ // Apply marker options
marker.setOptions($.extend({
position: pos,
map: markmanager.isEnabled(update.mode)? map : undefined,
title: update.callsign
- }, aprsOptions, getMarkerOpacityOptions(update.lastseen) ));
+ }, aprsOptions));
- // Update marker attributes
+ // Update marker attributes and age
+ marker.age(new Date().getTime() - update.lastseen);
marker.update(update);
if (expectedCallsign && expectedCallsign == update.callsign) {
@@ -167,8 +175,8 @@ $(function(){
break;
case 'feature':
var pos = new google.maps.LatLng(update.location.lat, update.location.lon);
+ var marker = markmanager.find(update.callsign);
var options = {}
- var marker;
// If no symbol or color supplied, use defaults by type
if (update.location.symbol) {
@@ -182,12 +190,11 @@ $(function(){
options.color = markmanager.getColor(update.mode);
}
- // If new item, create a new feature marker for it
- if (markers[update.callsign]) {
- marker = markers[update.callsign];
- } else {
+ // If new item, create a new marker for it
+ if (!marker) {
+ marker = new GFeatureMarker();
markmanager.addType(update.mode);
- marker = markers[update.callsign] = new GFeatureMarker();
+ markmanager.add(update.callsign, marker);
marker.addListener('click', function(){
showMarkerInfoWindow(update.callsign, pos);
});
@@ -200,7 +207,8 @@ $(function(){
title: update.callsign
}, options));
- // Update marker attributes
+ // Update marker attributes and age
+ marker.age(new Date().getTime() - update.lastseen);
marker.update(update);
if (expectedCallsign && expectedCallsign == update.callsign) {
@@ -265,10 +273,9 @@ $(function(){
var clearMap = function(){
var reset = function(callsign, item) { item.setMap(); };
- $.each(markers, reset);
- $.each(rectangles, reset);
receiverMarker.setMap();
- markers = {};
+ markmanager.clear();
+ $.each(rectangles, reset);
rectangles = {};
};
@@ -445,7 +452,7 @@ $(function(){
var showMarkerInfoWindow = function(name, pos) {
var infowindow = getInfoWindow();
- var marker = markers[name];
+ var marker = markmanager.find(name);
infowindow.callsign = name;
infowindow.setContent(marker.getInfoHTML(name, receiverMarker));
@@ -478,13 +485,6 @@ $(function(){
};
};
- var getMarkerOpacityOptions = function(lastseen) {
- var scale = getScale(lastseen);
- return {
- opacity: scale
- };
- };
-
// fade out / remove positions after time
setInterval(function(){
var now = new Date().getTime();
@@ -497,15 +497,7 @@ $(function(){
}
m.setOptions(getRectangleOpacityOptions(m.lastseen));
});
- $.each(markers, function(callsign, m) {
- var age = now - m.lastseen;
- if (age > retention_time) {
- delete markers[callsign];
- m.setMap();
- return;
- }
- m.setOptions(getMarkerOpacityOptions(m.lastseen));
- });
+ markmanager.ageAll();
}, 1000);
var rectangleFilter = allRectangles = function() { return true; };
@@ -548,16 +540,8 @@ $(function(){
} else {
$el.addClass('disabled');
}
- markmanager.toggle(map, markers, $el.data('selector'), onoff);
+ markmanager.toggle(map, $el.data('selector'), onoff);
});
}
});
-
-function toggleElement(el_name) {
- var el = document.getElementById(el_name);
- if (el) {
- el.style.display = el.style.display === 'none'?
- 'block' : 'none';
- }
-}
From 8aae159bad7c80b8eb2ca91c814966c72f706684 Mon Sep 17 00:00:00 2001
From: Marat Fayzullin
Date: Sun, 30 Jul 2023 12:18:50 -0400
Subject: [PATCH 05/10] Separated locators from the map implementation.
---
htdocs/lib/LocatorManager.js | 232 +++++++++++++++++++++++++++++++++++
htdocs/lib/MarkerManager.js | 5 +-
htdocs/map.js | 227 ++++++++--------------------------
3 files changed, 286 insertions(+), 178 deletions(-)
create mode 100644 htdocs/lib/LocatorManager.js
diff --git a/htdocs/lib/LocatorManager.js b/htdocs/lib/LocatorManager.js
new file mode 100644
index 00000000..11d5eedb
--- /dev/null
+++ b/htdocs/lib/LocatorManager.js
@@ -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 '- '
+ + key + '
';
+ });
+
+ $(".openwebrx-map-legend .content").html('');
+}
+
+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' + message + ')';
+ }).join("");
+
+ return 'Locator: ' + locator + distance +
+ '
Active Callsigns:
';
+};
+
+//
+// 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;
+ }
+};
diff --git a/htdocs/lib/MarkerManager.js b/htdocs/lib/MarkerManager.js
index 64756a9b..9a538e25 100644
--- a/htdocs/lib/MarkerManager.js
+++ b/htdocs/lib/MarkerManager.js
@@ -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];
});
};
diff --git a/htdocs/map.js b/htdocs/map.js
index 4db1500a..65310a43 100644
--- a/htdocs/map.js
+++ b/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 '- ' + key + '
';
- });
- $(".openwebrx-map-legend .content").html('');
- }
-
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(
- 'Locator: ' + locator + distance + '
' +
- 'Active Callsigns:
' +
- '' +
- 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 '- ' + message + '
'
- }).join("") +
- '
'
- );
+ 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'));
}
});
From e7a88cc68abea23924566efec418179e20444928 Mon Sep 17 00:00:00 2001
From: Marat Fayzullin
Date: Sun, 30 Jul 2023 12:34:26 -0400
Subject: [PATCH 06/10] Performance issue with GMap rectangles solved.
---
htdocs/lib/LocatorManager.js | 24 ++++++++++++++----------
htdocs/map.js | 4 ++--
2 files changed, 16 insertions(+), 12 deletions(-)
diff --git a/htdocs/lib/LocatorManager.js b/htdocs/lib/LocatorManager.js
index 11d5eedb..28bdaf01 100644
--- a/htdocs/lib/LocatorManager.js
+++ b/htdocs/lib/LocatorManager.js
@@ -45,7 +45,7 @@ LocatorManager.prototype.ageAll = function() {
LocatorManager.prototype.clear = function() {
// Remove all rectangles from the map
- $.each(this.markers, function(_, x) { x.setMap(); });
+ $.each(this.rectangles, function(_, x) { x.setMap(); });
// Delete all rectangles
this.rectangles = {};
};
@@ -196,12 +196,18 @@ Locator.prototype.update = function(update) {
// GoogleMaps-Specific Locators (derived from generic locators)
//
-function GLocator() { $.extend(this, new Locator()); }
+function GLocator() {
+ this.rect = new google.maps.Rectangle();
+}
-GLocator.prototype = new google.maps.Rectangle();
+GLocator.prototype = new Locator();
GLocator.prototype.setOptions = function(options) {
- google.maps.Rectangle.prototype.setOptions.apply(this, arguments);
+ this.rect.setOptions(options);
+};
+
+GLocator.prototype.setMap = function(map) {
+ this.rect.setMap(map);
};
GLocator.prototype.setCenter = function(lat, lon) {
@@ -218,12 +224,10 @@ GLocator.prototype.setCenter = function(lat, lon) {
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
-// });
+ this.setOptions({
+ strokeOpacity : LocatorManager.strokeOpacity * scale,
+ fillOpacity : LocatorManager.fillOpacity * scale
+ });
return true;
} else {
this.setMap();
diff --git a/htdocs/map.js b/htdocs/map.js
index 65310a43..095951fa 100644
--- a/htdocs/map.js
+++ b/htdocs/map.js
@@ -165,8 +165,8 @@ $(function(){
if (!rectangle) {
rectangle = new GLocator();
locmanager.add(update.callsign, rectangle);
- rectangle.addListener('click', function() {
- showLocatorInfoWindow(this.locator, this.center);
+ rectangle.rect.addListener('click', function() {
+ showLocatorInfoWindow(rectangle.locator, rectangle.center);
});
}
From 10401f0bdb936b1b8bdabbfd2258d3a1fa822ebd Mon Sep 17 00:00:00 2001
From: Marat Fayzullin
Date: Sun, 30 Jul 2023 13:13:44 -0400
Subject: [PATCH 07/10] Defined API between abstract and
implementation-specific Locators.
---
htdocs/lib/LocatorManager.js | 54 ++++++++++++++++++++++--------------
htdocs/map.js | 14 +++-------
2 files changed, 37 insertions(+), 31 deletions(-)
diff --git a/htdocs/lib/LocatorManager.js b/htdocs/lib/LocatorManager.js
index 28bdaf01..3e2cf799 100644
--- a/htdocs/lib/LocatorManager.js
+++ b/htdocs/lib/LocatorManager.js
@@ -69,8 +69,7 @@ LocatorManager.prototype.setFilter = function(map, filterBy = null) {
LocatorManager.prototype.reColor = function() {
var self = this;
$.each(this.rectangles, function(_, x) {
- var color = self.getColor(x);
- x.setOptions({ strokeColor: color, fillColor: color });
+ x.setColor(self.getColor(x));
});
};
@@ -170,7 +169,9 @@ LocatorManager.prototype.getInfoHTML = function(locator, pos, receiverMarker = n
};
//
-// Generic locator functionality
+// Generic Map Locator
+// Derived classes have to implement:
+// setMap(), setCenter(), setColor(), setOpacity()
//
function Locator() {}
@@ -190,22 +191,36 @@ Locator.prototype.update = function(update) {
// Implementation-dependent function call
this.setCenter(lat, lon);
+
+ // Age locator
+ this.age(new Date().getTime() - update.lastseen);
+};
+
+Locator.prototype.age = function(age) {
+ if (age <= retention_time) {
+ this.setOpacity(Marker.getOpacityScale(age));
+ return true;
+ } else {
+ this.setMap();
+ return false;
+ }
};
//
-// GoogleMaps-Specific Locators (derived from generic locators)
+// 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.setOptions = function(options) {
- this.rect.setOptions(options);
-};
-
GLocator.prototype.setMap = function(map) {
this.rect.setMap(map);
};
@@ -213,7 +228,7 @@ GLocator.prototype.setMap = function(map) {
GLocator.prototype.setCenter = function(lat, lon) {
this.center = new google.maps.LatLng({lat: lat, lng: lon});
- this.setOptions({ bounds : {
+ this.rect.setOptions({ bounds : {
north : lat - 0.5,
south : lat + 0.5,
west : lon - 1.0,
@@ -221,16 +236,13 @@ GLocator.prototype.setCenter = function(lat, lon) {
}});
}
-GLocator.prototype.age = function(age) {
- if (age <= retention_time) {
- var scale = Marker.getOpacityScale(age);
- this.setOptions({
- strokeOpacity : LocatorManager.strokeOpacity * scale,
- fillOpacity : LocatorManager.fillOpacity * scale
- });
- return true;
- } else {
- this.setMap();
- return false;
- }
+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
+ });
};
diff --git a/htdocs/map.js b/htdocs/map.js
index 095951fa..531ab16d 100644
--- a/htdocs/map.js
+++ b/htdocs/map.js
@@ -170,18 +170,12 @@ $(function(){
});
}
- // Update locator attributes, center, and age
- rectangle.age(new Date().getTime() - update.lastseen);
+ // Update locator attributes, center, age
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
- });
+ // 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);
From 263864700617e81d87c9ebb84477ca90bca761c1 Mon Sep 17 00:00:00 2001
From: Marat Fayzullin
Date: Sun, 30 Jul 2023 14:10:42 -0400
Subject: [PATCH 08/10] Added proper derivation for markers.
---
htdocs/lib/MarkerManager.js | 88 +++++++++++++++++++++++++------------
htdocs/map.js | 51 ++++++++++-----------
2 files changed, 82 insertions(+), 57 deletions(-)
diff --git a/htdocs/lib/MarkerManager.js b/htdocs/lib/MarkerManager.js
index 9a538e25..0f41aa4f 100644
--- a/htdocs/lib/MarkerManager.js
+++ b/htdocs/lib/MarkerManager.js
@@ -112,7 +112,9 @@ MarkerManager.prototype.clear = function() {
};
//
-// Generic marker functionality
+// Generic Map Marker
+// Derived classes have to implement:
+// setMap(), setMarkerOpacity()
//
function Marker() {}
@@ -187,6 +189,7 @@ Marker.makeListItem = function(name, value) {
+ '';
};
+// Get opacity value in the 0..1 range based on the given age.
Marker.getOpacityScale = function(age) {
var scale = 1;
if (age >= retention_time / 2) {
@@ -195,13 +198,37 @@ Marker.getOpacityScale = function(age) {
return Math.max(0, Math.min(1, scale));
};
+// Set marker's opacity based on the supplied age. Returns TRUE
+// if the marker should still be visible, FALSE if it has to be
+// removed.
+Marker.prototype.age = function(age) {
+ if(age <= retention_time) {
+ this.setMarkerOpacity(Marker.getOpacityScale(age));
+ return true;
+ } else {
+ this.setMap();
+ return false;
+ }
+};
+
+// Remove visual marker element from its parent, if that element exists.
+Marker.prototype.remove = function() {
+ if (this.div) {
+ this.div.parentNode.removeChild(this.div);
+ this.div = null;
+ }
+};
+
//
-// Feature Markers
+// Feature Marker
+// Represents static map features, such as stations and receivers.
+// Derived classes have to implement:
+// setMarkerOpacity()
//
function FeatureMarker() {}
-FeatureMarker.prototype = new FeatureMarker();
+FeatureMarker.prototype = new Marker();
FeatureMarker.prototype.update = function(update) {
this.lastseen = update.lastseen;
@@ -212,6 +239,12 @@ FeatureMarker.prototype.update = function(update) {
this.device = update.location.device;
this.antenna = update.location.antenna;
this.schedule = update.location.schedule;
+
+ // Implementation-dependent function call
+ this.setMarkerPosition(update.callsign, update.location.lat, update.location.lon);
+
+ // Age locator
+ this.age(new Date().getTime() - update.lastseen);
};
FeatureMarker.prototype.draw = function() {
@@ -246,13 +279,6 @@ FeatureMarker.prototype.create = function() {
return div;
};
-FeatureMarker.prototype.remove = function() {
- if (this.div) {
- this.div.parentNode.removeChild(this.div);
- this.div = null;
- }
-};
-
FeatureMarker.prototype.getAnchorOffset = function() {
return [0, -this.symHeight/2];
};
@@ -316,12 +342,16 @@ FeatureMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
};
//
-// APRS Markers (also AIS and HFDL)
+// APRS Marker
+// Represents APRS transmitters, as well as AIS (vessels)
+// and HFDL (planes).
+// Derived classes have to implement:
+// setMarkerOpacity()
//
function AprsMarker() {}
-AprsMarker.prototype = new AprsMarker();
+AprsMarker.prototype = new Marker();
AprsMarker.prototype.update = function(update) {
this.lastseen = update.lastseen;
@@ -337,6 +367,12 @@ AprsMarker.prototype.update = function(update) {
this.device = update.location.device;
this.aircraft = update.location.aircraft;
this.directivity = update.location.directivity;
+
+ // Implementation-dependent function call
+ this.setMarkerPosition(update.callsign, update.location.lat, update.location.lon);
+
+ // Age locator
+ this.age(new Date().getTime() - update.lastseen);
};
AprsMarker.prototype.draw = function() {
@@ -401,13 +437,6 @@ AprsMarker.prototype.create = function() {
return div;
};
-AprsMarker.prototype.remove = function() {
- if (this.div) {
- this.div.parentNode.removeChild(this.div);
- this.div = null;
- }
-};
-
AprsMarker.prototype.getAnchorOffset = function() {
return [0, -12];
};
@@ -543,8 +572,8 @@ AprsMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
function GMarker() {}
GMarker.prototype = new google.maps.OverlayView();
-GMarker.prototype.setOptions = function(options) {
- google.maps.OverlayView.prototype.setOptions.apply(this, arguments);
+GMarker.prototype.setMarkerOptions = function(options) {
+ this.setOptions(options);
this.draw();
};
@@ -567,14 +596,15 @@ GMarker.prototype.getAnchorPoint = function() {
return new google.maps.Point(offset[0], offset[1]);
};
-GMarker.prototype.age = function(age) {
- if(age <= retention_time) {
- this.setOptions({ opacity: Marker.getOpacityScale(age) });
- return true;
- } else {
- this.setMap();
- return false;
- }
+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
diff --git a/htdocs/map.js b/htdocs/map.js
index 531ab16d..73ec14fc 100644
--- a/htdocs/map.js
+++ b/htdocs/map.js
@@ -66,10 +66,10 @@ $(function(){
updates.forEach(function(update) {
switch (update.location.type) {
case 'latlon':
- var pos = new google.maps.LatLng(update.location.lat, update.location.lon);
var marker = markmanager.find(update.callsign);
var markerClass = google.maps.Marker;
var aprsOptions = {}
+
if (update.location.symbol) {
markerClass = GAprsMarker;
aprsOptions.symbol = update.location.symbol;
@@ -82,35 +82,32 @@ $(function(){
marker = new markerClass();
markmanager.addType(update.mode);
markmanager.add(update.callsign, marker);
- marker.addListener('click', function(){
- showMarkerInfoWindow(update.callsign, pos);
+ marker.addListener('click', function() {
+ showMarkerInfoWindow(update.callsign, marker.pos);
});
}
- // Apply marker options
- marker.setOptions($.extend({
- position: pos,
- map: markmanager.isEnabled(update.mode)? map : undefined,
- title: update.callsign
- }, aprsOptions));
-
// Update marker attributes and age
- marker.age(new Date().getTime() - update.lastseen);
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(pos);
- showMarkerInfoWindow(update.callsign, pos);
+ map.panTo(marker.pos);
+ showMarkerInfoWindow(update.callsign, marker.pos);
expectedCallsign = false;
}
if (infowindow && infowindow.callsign && infowindow.callsign == update.callsign) {
- showMarkerInfoWindow(infowindow.callsign, pos);
+ showMarkerInfoWindow(infowindow.callsign, marker.pos);
}
break;
case 'feature':
- var pos = new google.maps.LatLng(update.location.lat, update.location.lon);
var marker = markmanager.find(update.callsign);
var options = {}
@@ -131,30 +128,28 @@ $(function(){
marker = new GFeatureMarker();
markmanager.addType(update.mode);
markmanager.add(update.callsign, marker);
- marker.addListener('click', function(){
- showMarkerInfoWindow(update.callsign, pos);
+ marker.addListener('click', function() {
+ showMarkerInfoWindow(update.callsign, marker.pos);
});
}
- // Apply marker options
- marker.setOptions($.extend({
- position: pos,
- map: markmanager.isEnabled(update.mode)? map : undefined,
- title: update.callsign
- }, options));
-
// Update marker attributes and age
- marker.age(new Date().getTime() - update.lastseen);
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(pos);
- showMarkerInfoWindow(update.callsign, pos);
+ map.panTo(marker.pos);
+ showMarkerInfoWindow(update.callsign, marker.pos);
expectedCallsign = false;
}
if (infowindow && infowindow.callsign && infowindow.callsign == update.callsign) {
- showMarkerInfoWindow(infowindow.callsign, pos);
+ showMarkerInfoWindow(infowindow.callsign, marker.pos);
}
break;
From b846fb2d8846712fdbee8c809fe2150ce3960d0d Mon Sep 17 00:00:00 2001
From: Marat Fayzullin
Date: Mon, 31 Jul 2023 21:53:07 -0400
Subject: [PATCH 09/10] Finished refactoring maps.
---
htdocs/lib/Map.js | 197 ++++++
.../lib/{LocatorManager.js => MapLocators.js} | 41 --
.../lib/{MarkerManager.js => MapMarkers.js} | 50 --
htdocs/map.js | 657 +++++++-----------
owrx/controllers/assets.py | 3 +
5 files changed, 462 insertions(+), 486 deletions(-)
create mode 100644 htdocs/lib/Map.js
rename htdocs/lib/{LocatorManager.js => MapLocators.js} (85%)
rename htdocs/lib/{MarkerManager.js => MapMarkers.js} (93%)
diff --git a/htdocs/lib/Map.js b/htdocs/lib/Map.js
new file mode 100644
index 00000000..22b96efd
--- /dev/null
+++ b/htdocs/lib/Map.js
@@ -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);
+ });
+};
diff --git a/htdocs/lib/LocatorManager.js b/htdocs/lib/MapLocators.js
similarity index 85%
rename from htdocs/lib/LocatorManager.js
rename to htdocs/lib/MapLocators.js
index 3e2cf799..9e8f7523 100644
--- a/htdocs/lib/LocatorManager.js
+++ b/htdocs/lib/MapLocators.js
@@ -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
- });
-};
diff --git a/htdocs/lib/MarkerManager.js b/htdocs/lib/MapMarkers.js
similarity index 93%
rename from htdocs/lib/MarkerManager.js
rename to htdocs/lib/MapMarkers.js
index 0f41aa4f..e51c279a 100644
--- a/htdocs/lib/MarkerManager.js
+++ b/htdocs/lib/MapMarkers.js
@@ -564,53 +564,3 @@ AprsMarker.prototype.getInfoHTML = function(name, receiverMarker = null) {
+ ( this.band ? ' on ' + this.band : '' ) + ''
+ 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();
diff --git a/htdocs/map.js b/htdocs/map.js
index 73ec14fc..08e8ef31 100644
--- a/htdocs/map.js
+++ b/htdocs/map.js
@@ -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(
+ '' + marker.config['receiver_name'] + '
' +
+ 'Receiver Location
'
+ );
+ 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(
- '' + marker.config['receiver_name'] + '
' +
- 'Receiver location
'
- );
- 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);
- });
- }
-
-});
+ });
+};
diff --git a/owrx/controllers/assets.py b/owrx/controllers/assets.py
index d5d180e3..a11b0540 100644
--- a/owrx/controllers/assets.py
+++ b/owrx/controllers/assets.py
@@ -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",
],
From f9d45e014d8c309031ac499807087b68bb19b28e Mon Sep 17 00:00:00 2001
From: Marat Fayzullin
Date: Mon, 31 Jul 2023 21:58:07 -0400
Subject: [PATCH 10/10] Fixing UTC clock event.
---
htdocs/map.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/htdocs/map.html b/htdocs/map.html
index 01daa0ea..242608eb 100644
--- a/htdocs/map.html
+++ b/htdocs/map.html
@@ -27,7 +27,7 @@
- 00:00 UTC
+ 00:00 UTC