2023-09-18 03:47:11 +02:00
|
|
|
// Marker.linkify() uses these URLs
|
|
|
|
|
var callsign_url = null;
|
|
|
|
|
var vessel_url = null;
|
|
|
|
|
var flight_url = null;
|
|
|
|
|
var modes_url = null;
|
|
|
|
|
|
|
|
|
|
// reasonable default; will be overriden by server
|
|
|
|
|
var retention_time = 2 * 60 * 60 * 1000;
|
|
|
|
|
|
|
|
|
|
// 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;
|
2023-09-21 00:57:09 +02:00
|
|
|
var expectedIcao = query.icao? query.icao: null;
|
2023-09-18 03:47:11 +02:00
|
|
|
|
|
|
|
|
// Get information bubble window
|
|
|
|
|
function getInfoWindow() {
|
|
|
|
|
if (!infoWindow) {
|
|
|
|
|
infoWindow = new google.maps.InfoWindow();
|
|
|
|
|
google.maps.event.addListener(infoWindow, 'closeclick', function() {
|
|
|
|
|
delete infoWindow.locator;
|
|
|
|
|
delete infoWindow.callsign;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
delete infoWindow.locator;
|
|
|
|
|
delete infoWindow.callsign;
|
|
|
|
|
return infoWindow;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Show information bubble for a locator
|
|
|
|
|
function showLocatorInfoWindow(locator, pos) {
|
|
|
|
|
var iw = getInfoWindow();
|
|
|
|
|
|
|
|
|
|
iw.locator = locator;
|
|
|
|
|
iw.setContent(mapManager.lman.getInfoHTML(locator, pos, receiverMarker));
|
|
|
|
|
iw.setPosition(pos);
|
|
|
|
|
iw.open(map);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Show information bubble for a marker
|
|
|
|
|
function showMarkerInfoWindow(name, pos) {
|
|
|
|
|
var marker = mapManager.mman.find(name);
|
|
|
|
|
var iw = getInfoWindow();
|
|
|
|
|
|
|
|
|
|
iw.callsign = name;
|
|
|
|
|
iw.setContent(marker.getInfoHTML(name, receiverMarker));
|
|
|
|
|
iw.open(map, marker);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Show information bubble for the receiver location
|
|
|
|
|
function showReceiverInfoWindow(marker) {
|
|
|
|
|
var iw = getInfoWindow()
|
|
|
|
|
iw.setContent(
|
|
|
|
|
'<h3>' + marker.config['receiver_name'] + '</h3>' +
|
|
|
|
|
'<div>Receiver Location</div>'
|
|
|
|
|
);
|
|
|
|
|
iw.open(map, marker);
|
|
|
|
|
};
|
|
|
|
|
|
2023-09-21 00:57:09 +02:00
|
|
|
var sourceToKey = function(source) {
|
|
|
|
|
// special treatment for special entities
|
|
|
|
|
// not just for display but also in key treatment in order not to overlap with other locations sent by the same callsign
|
|
|
|
|
if ('item' in source) return source['item'];
|
|
|
|
|
if ('object' in source) return source['object'];
|
|
|
|
|
if ('icao' in source) return source['icao'];
|
|
|
|
|
if ('flight' in source) return source['flight'];
|
|
|
|
|
var key = source.callsign;
|
|
|
|
|
if ('ssid' in source) key += '-' + source.ssid;
|
|
|
|
|
return key;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// we can reuse the same logic for displaying and indexing
|
|
|
|
|
var sourceToString = sourceToKey;
|
|
|
|
|
|
2023-09-18 03:47:11 +02:00
|
|
|
//
|
|
|
|
|
// GOOGLE-SPECIFIC MAP MANAGER METHODS
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
MapManager.prototype.setReceiverName = function(name) {
|
|
|
|
|
if (receiverMarker) receiverMarker.setOptions({ title: name });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MapManager.prototype.removeReceiver = function() {
|
|
|
|
|
if (receiverMarker) receiverMarker.setMap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MapManager.prototype.initializeMap = function(receiver_gps, api_key, weather_key) {
|
|
|
|
|
var receiverPos = { lat: receiver_gps.lat, lng: receiver_gps.lon };
|
|
|
|
|
|
|
|
|
|
if (map) {
|
|
|
|
|
receiverMarker.setOptions({
|
|
|
|
|
map : map,
|
|
|
|
|
position : receiverPos,
|
|
|
|
|
config : this.config
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
|
|
// 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) {
|
2023-09-21 00:57:09 +02:00
|
|
|
var key = sourceToKey(update.source);
|
2023-09-18 03:47:11 +02:00
|
|
|
|
|
|
|
|
switch (update.location.type) {
|
|
|
|
|
case 'latlon':
|
2023-09-21 01:07:21 +02:00
|
|
|
var marker = self.mman.find(key);
|
2023-09-18 03:47:11 +02:00
|
|
|
var markerClass = GSimpleMarker;
|
|
|
|
|
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) {
|
2023-09-21 00:57:09 +02:00
|
|
|
// AF: here shall be created ICAO markers for planes.
|
|
|
|
|
// either by adapting the PlaneMarker.js or by reusing the AprsMarkers as in OWRX+
|
|
|
|
|
// I'll leave this to someone more competent or will try to implement it myself
|
|
|
|
|
// when I have the time to spend to understand how.
|
|
|
|
|
// As of now, the planes are shown on the map, but with default icon.
|
2023-09-18 03:47:11 +02:00
|
|
|
marker = new markerClass();
|
2023-09-21 01:07:21 +02:00
|
|
|
self.mman.add(key, marker);
|
2023-09-18 03:47:11 +02:00
|
|
|
marker.addListener('click', function() {
|
2023-09-21 01:07:21 +02:00
|
|
|
showMarkerInfoWindow(key, marker.position);
|
2023-09-18 03:47:11 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Keep track of new marker types as they may change
|
|
|
|
|
self.mman.addType(update.mode);
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
2023-09-21 00:57:09 +02:00
|
|
|
if (expectedIcao && expectedIcao === update.source.icao) {
|
|
|
|
|
map.panTo(marker.position);
|
2023-09-21 01:07:21 +02:00
|
|
|
showMarkerInfoWindow(key, marker.position);
|
2023-09-21 00:57:09 +02:00
|
|
|
expectedIcao = false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 01:07:21 +02:00
|
|
|
if (expectedCallsign && expectedCallsign == key) {
|
2023-09-18 03:47:11 +02:00
|
|
|
map.panTo(marker.position);
|
2023-09-21 01:07:21 +02:00
|
|
|
showMarkerInfoWindow(key, marker.position);
|
2023-09-18 03:47:11 +02:00
|
|
|
expectedCallsign = false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 01:07:21 +02:00
|
|
|
if (infoWindow && infoWindow.callsign && infoWindow.callsign == key) {
|
2023-09-18 03:47:11 +02:00
|
|
|
showMarkerInfoWindow(infoWindow.callsign, marker.position);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'feature':
|
2023-09-21 01:07:21 +02:00
|
|
|
var marker = self.mman.find(key);
|
2023-09-18 03:47:11 +02:00
|
|
|
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);
|
2023-09-21 01:07:21 +02:00
|
|
|
self.mman.add(key, marker);
|
2023-09-18 03:47:11 +02:00
|
|
|
marker.addListener('click', function() {
|
2023-09-21 01:07:21 +02:00
|
|
|
showMarkerInfoWindow(key, marker.position);
|
2023-09-18 03:47:11 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
2023-09-21 01:07:21 +02:00
|
|
|
if (expectedCallsign && expectedCallsign == key) {
|
2023-09-18 03:47:11 +02:00
|
|
|
map.panTo(marker.position);
|
2023-09-21 01:07:21 +02:00
|
|
|
showMarkerInfoWindow(key, marker.position);
|
2023-09-18 03:47:11 +02:00
|
|
|
expectedCallsign = false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-21 01:07:21 +02:00
|
|
|
if (infoWindow && infoWindow.callsign && infoWindow.callsign == key) {
|
2023-09-18 03:47:11 +02:00
|
|
|
showMarkerInfoWindow(infoWindow.callsign, marker.position);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'locator':
|
2023-09-21 01:07:21 +02:00
|
|
|
var rectangle = self.lman.find(key);
|
2023-09-18 03:47:11 +02:00
|
|
|
|
|
|
|
|
// If new item, create a new locator for it
|
|
|
|
|
if (!rectangle) {
|
|
|
|
|
rectangle = new GLocator();
|
2023-09-21 01:07:21 +02:00
|
|
|
self.lman.add(key, rectangle);
|
2023-09-18 03:47:11 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|