node-red-contrib-jwht-map/worldmap/worldmap.js

3034 lines
139 KiB
JavaScript
Raw Normal View History

2019-11-25 21:48:43 +08:00
/* eslint-disable no-undef */
var startpos = [51.05, -1.38]; // Start location - somewhere in UK :-)
var startzoom = 10;
var ws;
var map;
var allData = {};
var markers = {};
var polygons = {};
var layers = {};
var overlays = {};
var basemaps = {};
var marks = [];
var buttons = {};
var marksIndex = 0;
var menuOpen = false;
var clusterAt = 0;
2021-10-04 16:41:46 +08:00
var maxage = 900; // default max age of icons on map in seconds - cleared after 10 mins
var baselayername = "OSM grey"; // Default base layer OSM but uniform grey
2023-08-11 22:21:34 +08:00
var pagefoot = " © DCJ 2023"
var inIframe = false;
var showUserMenu = true;
var showLayerMenu = true;
var showMouseCoords = false;
2020-12-07 07:53:47 +08:00
var allowFileDrop = false;
var heat;
2020-08-03 20:16:53 +08:00
var minimap;
var sidebyside;
var layercontrol;
var drawControl;
2020-03-11 20:18:18 +08:00
var drawingColour = "#910000";
var sendDrawing;
var colorControl;
var sendRoute;
var oldBounds = {ne:{lat:0, lng:0}, sw:{lat:0, lng:0}};
var edgeLayer = new L.layerGroup();
var edgeEnabled = true;
var iconSz = {
"Team/Crew": 24,
"Squad": 24,
"Section": 24,
"Platoon/detachment": 26,
"Company/battery/troop": 28,
"Battalion/squadron": 30,
"Regiment/group": 32,
"Brigade": 34,
"Division": 36,
"Corps/MEF": 36,
"Army": 40,
"Army Group/front": 40,
"Region/Theater": 44,
"Command": 44
};
// Create the socket
var connect = function() {
2021-10-22 05:57:51 +08:00
// var transports = ["websocket", "xhr-streaming", "xhr-polling"],
ws = new SockJS(location.pathname.split("index")[0] + 'socket');
ws.onopen = function() {
console.log("CONNECTED");
if (!inIframe) {
document.getElementById("footer").innerHTML = "<font color='#494'>"+pagefoot+"</font>";
}
ws.send(JSON.stringify({action:"connected",parameters:Object.fromEntries((new URL(location)).searchParams),clientTimezone:Intl.DateTimeFormat().resolvedOptions().timeZone || false}));
onoffline();
};
ws.onclose = function() {
console.log("DISCONNECTED");
if (!inIframe) {
document.getElementById("footer").innerHTML = "<font color='#900'>"+pagefoot+"</font>";
}
setTimeout(function() { connect(); }, 2500);
};
ws.onmessage = function(e) {
2021-10-25 17:02:31 +08:00
try {
var data = JSON.parse(e.data);
if (data.hasOwnProperty("type") && data.hasOwnProperty("data") && data.type === "Buffer") { data = data.data.toString(); }
handleData(data);
}
2021-05-26 20:34:34 +08:00
catch (e) { if (data) { console.log("BAD DATA",data); } }
2020-10-29 18:07:19 +08:00
// console.log("DATA",typeof data,data);
2020-12-07 07:53:47 +08:00
};
}
console.log("CONNECT TO",location.pathname + 'socket');
var handleData = function(data) {
if (Array.isArray(data)) {
//console.log("ARRAY");
// map.closePopup();
// var bnds= L.latLngBounds([0,0]);
for (var prop in data) {
if (data[prop].command) { doCommand(data[prop].command); delete data[prop].command; }
if (data[prop].hasOwnProperty("name")) {
setMarker(data[prop]);
// bnds.extend(markers[data[prop].name].getLatLng());
}
2021-09-29 00:58:10 +08:00
else if (data[prop].hasOwnProperty("filename") && data[prop].filename === "doc.kml") {
data = {command:{map:{overlay:"KML", kml:data[prop].payload}}};
doCommand(data.command); return;
}
2020-12-07 07:53:47 +08:00
else { console.log("SKIP A",data[prop]); }
}
// map.fitBounds(bnds.pad(0.25));
}
else {
if (typeof data === "string" && data.indexOf("<?xml") == 0) {
if (data.indexOf("<nvg") != -1) {
data = {command:{map:{overlay:"NVG", nvg:data}}};
}
else if (data.indexOf("<kml") != -1) {
data = {command:{map:{overlay:"KML", kml:data}}};
}
else if (data.indexOf("<gpx") != -1) {
data = {command:{map:{overlay:"GPX", gpx:data}}};
}
}
2020-12-07 07:53:47 +08:00
if (data.command) { doCommand(data.command); delete data.command; }
2023-08-11 22:21:34 +08:00
// handle raw geojson type msg
if (data.hasOwnProperty("type") && data.type.indexOf("Feature") === 0) {
if (data.hasOwnProperty('properties') && data.properties.hasOwnProperty('title')) {
doGeojson(data.properties.title,data)
}
else { doGeojson("geojson",data); }
}
2023-08-11 22:21:34 +08:00
// handle TAK json (from tak-ingest node or fastxml node)
else if (data.hasOwnProperty("event") && data.event.hasOwnProperty("point")) {
doTAKjson(data.event);
}
// handle TAK json (from multicast Protobuf)
else if (data.hasOwnProperty("cotEvent") && data.cotEvent.hasOwnProperty("lat") && data.cotEvent.hasOwnProperty("lon")) {
doTAKMCjson(data.cotEvent);
}
// handle default worldmap json msg
else if (data.hasOwnProperty("name")) { setMarker(data); }
2020-12-07 07:53:47 +08:00
else {
if (JSON.stringify(data) !== '{}') {
console.log("SKIP",data);
}
2020-12-07 07:53:47 +08:00
// if (typeof data === "string") { doDialog(data); }
// else { console.log("SKIP",data); }
}
}
}
window.onunload = function() { if (ws) ws.close(); }
var customTopoLayer = L.geoJson(null, {clickable:false, style: {color:"blue", weight:2, fillColor:"#cf6", fillOpacity:0.04}});
layers["_countries"] = omnivore.topojson('images/world-50m-flat.json',null,customTopoLayer);
overlays["countries"] = layers["_countries"];
var onoffline = function() { if (!navigator.onLine) {
map.addLayer(overlays["countries"]);
} }
document.addEventListener ("keydown", function (ev) {
// Set Ctl-Alt-3 to switch to 3d view
if (ev.ctrlKey && ev.altKey && ev.code === "Digit3") {
ws.close();
window.location.href = "index3d.html";
}
// Set Esc key to close all open popups
if (ev.keyCode === 27) {
map.eachLayer(function (layer) {
layer.closePopup();
});
}
});
if ( window.self !== window.top ) { inIframe = true; }
if (inIframe === true) {
if ( window.localStorage.hasOwnProperty("lastpos") ) {
var sp = JSON.parse(window.localStorage.getItem("lastpos"));
startpos = [ sp.lat, sp.lng ];
}
if ( window.localStorage.hasOwnProperty("lastzoom") ) {
startzoom = window.localStorage.getItem("lastzoom");
}
}
// if ( window.localStorage.hasOwnProperty("clusterat") ) {
// clusterAt = window.localStorage.getItem("clusterat");
// document.getElementById("setclus").value = clusterAt;
// }
// if ( window.localStorage.hasOwnProperty("maxage") ) {
// maxage = window.localStorage.getItem("maxage");
// document.getElementById("maxage").value = maxage;
// }
// Create the Initial Map object.
map = new L.map('map',{zoomSnap: 0.1}).setView(startpos, startzoom);
map.whenReady(function() {
connect();
});
2020-12-07 07:53:47 +08:00
var droplatlng;
var target = document.getElementById("map")
target.ondragover = function (ev) {
ev.preventDefault()
ev.dataTransfer.dropEffect = "move"
}
target.ondrop = function (ev) {
if (allowFileDrop === true) {
ev.preventDefault();
droplatlng = map.mouseEventToLatLng(ev);
handleFiles(ev.dataTransfer.files);
}
}
var handleFiles = function(files) {
([...files]).forEach(readFile);
}
var readFile = function(file) {
// Check if the file is text or kml
if (file.type &&
file.type.indexOf('text') === -1 &&
file.type.indexOf('kml') === -1 &&
2021-09-29 00:58:10 +08:00
file.type.indexOf('kmz') === -1 &&
file.type.indexOf('json') === -1 &&
file.type.indexOf('image/jpeg') === -1 &&
file.type.indexOf('image/png') === -1 &&
file.type.indexOf('image/tiff') === -1) {
2021-09-29 00:58:10 +08:00
console.log('File is not text, kml, kmz, jpeg, png, or json', file.type, file);
2020-12-07 07:53:47 +08:00
return;
}
const reader = new FileReader();
reader.addEventListener('load', (event) => {
var content = event.target.result;
var data;
if (content.indexOf("base64") !== -1) {
if (content.indexOf("image") === -1) {
data = atob(content.split("base64,")[1]);
if (data.indexOf('<?xml') !== -1) {
if (data.indexOf("<gpx") !== -1) {
doCommand({map:{overlay:file.name, gpx:data}});
}
else if (data.indexOf("<kml") !== -1) {
doCommand({map:{overlay:file.name, kml:data}});
}
else if (data.indexOf("<nvg") !== -1) {
doCommand({map:{overlay:file.name, nvg:data}});
}
2020-12-07 07:53:47 +08:00
}
2021-09-29 00:58:10 +08:00
else if (data.indexOf('PK') === 0) {
console.log("ZIP FILE");
}
2020-12-07 07:53:47 +08:00
else {
try {
data = JSON.parse(data);
handleData(data);
}
catch(e) {
console.log("NOT JSON DATA",data);
}
2020-12-07 07:53:47 +08:00
}
}
else if (content.indexOf("image/tiff") !== -1) {
data = atob(content.split("base64,")[1]);
console.log("Geotiff",typeof data)
/// we now have a geotiff image to render...
}
ws.send(JSON.stringify({action:"file", name:file.name, type:file.type, content:content, lat:droplatlng.lat, lon:droplatlng.lng}));
2020-12-07 07:53:47 +08:00
}
else {
console.log("NOT SURE WHAT THIS IS?",content)
}
});
reader.readAsDataURL(file);
}
// Create some buttons
var menuButton = L.easyButton({states:[{icon:'fa-bars fa-lg', onClick:function() { toggleMenu(); }, title:'Toggle menu'}], position:"topright"});
var fullscreenButton = L.control.fullscreen();
var rulerButton = L.control.ruler({position:"topleft"});
var followMode = { accuracy:true };
var followState = false;
var trackMeButton;
var errRing;
2019-11-25 21:48:43 +08:00
function onLocationFound(e) {
if (followState === true) { map.panTo(e.latlng); }
if (followMode.icon) {
var self = {name:followMode.name || "self", lat:e.latlng.lat, lon:e.latlng.lng, hdg:e.heading, speed:(e.speed*3.6 ?? undefined), layer:followMode.layer, icon:followMode.icon, iconColor:followMode.iconColor ?? "#910000" };
setMarker(self);
}
if (followMode.accuracy) {
errRing = L.circle(e.latlng, e.accuracy, {color:followMode.color ?? "cyan", weight:3, opacity:0.6, fill:false, clickable:false});
errRing.addTo(map);
// if (e.hasOwnProperty("heading")) {
// var lengthAsDegrees = e.speed * 60 / 110540;
// var ya = e.latlng.lat + Math.sin((90-e.heading)/180*Math.PI)*lengthAsDegrees*Math.cos(e.latlng.lng/180*Math.PI);
// var xa = e.latlng.lng + Math.cos((90-e.heading)/180*Math.PI)*lengthAsDegrees;
// var lla = new L.LatLng(ya,xa);
// L.polygon([ e.latlng, lla ], {color:"cyan", weight:3, opacity:0.5, clickable:false}).addTo(map);
// }
2019-11-25 21:48:43 +08:00
}
ws.send(JSON.stringify({action:"point", lat:e.latlng.lat.toFixed(5), lon:e.latlng.lng.toFixed(5), point:"self", hdg:e.heading, speed:(e.speed*3.6 ?? undefined)}));
2019-11-25 21:48:43 +08:00
}
function onLocationError(e) { console.log(e.message); }
// Move some bits around if in an iframe
if (inIframe) {
console.log("IN an iframe");
if (showUserMenu) { menuButton.addTo(map); }
document.getElementById("topbar").style.display="none";
document.getElementById("map").style.top="0px";
document.getElementById("results").style.right="50px";
document.getElementById("results").style.top="10px";
document.getElementById("results").style.zIndex="1";
document.getElementById("results").style.height="31px";
document.getElementById("results").style.paddingTop="6px";
document.getElementById("bars").style.display="none";
document.getElementById("menu").style.right="8px";
document.getElementById("menu").style.borderRadius="6px";
}
else {
console.log("NOT in an iframe");
if (!showUserMenu) { document.getElementById("bars").style.display="none"; }
// Add the fullscreen button
fullscreenButton.addTo(map);
trackMeButton = L.easyButton({
states: [{
stateName: 'track-on',
icon: 'fa-window-close-o fa-lg',
title: 'Disable tracking',
onClick: function(btn, map) {
btn.state('track-off');
followState = false;
if (errRing) { errRing.removeFrom(map); }
delMarker(followMode.name || "self")
map.stopLocate();
}
}, {
stateName: 'track-off',
icon: 'fa-crosshairs fa-lg',
title: 'Enable tracking',
onClick: function(btn, map) {
btn.state('track-on');
followState = true;
map.locate({setView:false, watch:followState, enableHighAccuracy:true});
}
}]
});
trackMeButton.state('track-off');
trackMeButton.addTo(map);
2019-11-25 21:48:43 +08:00
// Add the locate my position button
// L.easyButton( 'fa-crosshairs fa-lg', function() {
// map.locate({setView:true, maxZoom:16});
// }, "Locate me").addTo(map);
2022-09-23 20:41:24 +08:00
// Create the clear heatmap button
2021-06-24 23:14:58 +08:00
var clrHeat = L.easyButton( 'fa-eraser', function() {
console.log("Reset heatmap");
heat.setLatLngs([]);
2021-06-24 23:14:58 +08:00
}, "Clears the current heatmap", {position:"bottomright"});
}
2019-06-16 22:57:30 +08:00
var helpMenu = '<table>'
helpMenu += '<tr><td><input type="text" name="search" id="search" size="20" style="width:150px;"/>&nbsp;<span onclick=\'doSearch();\'><i class="fa fa-search fa-lg"></i></span></td></tr>';
helpMenu += '<tr><td style="cursor:default"><i class="fa fa-spinner fa-lg fa-fw"></i> Set Max Age <input type="text" name="maxage" id="maxage" value="600" size="5" onchange=\'setMaxAge();\'/>s</td></tr>';
helpMenu += '<tr><td style="cursor:default"><i class="fa fa-search-plus fa-lg fa-fw"></i> Cluster at zoom &lt;<input type="text" name="setclus" id="setclus" size="2" onchange=\'setCluster(this.value);\'/></td></tr>';
helpMenu += '<tr><td style="cursor:default"><input type="checkbox" id="panit" onclick=\'doPanit(this.checked);\'/> Auto Pan Map</td></tr>';
helpMenu += '<tr><td style="cursor:default"><input type="checkbox" id="lockit" onclick=\'doLock(this.checked);\'/> Lock Map</td></tr>';
helpMenu += '<tr><td style="cursor:default"><input type="checkbox" id="heatall" onclick=\'doHeatAll(this.checked);\'/> Heatmap all layers</td></tr>';
if (!inIframe) { helpMenu += '<tr><td style="cursor:default"><span id="showHelp" onclick=\'doDialog(helpText);\'><i class="fa fa-info fa-lg fa-fw"></i>Help</span></td></tr></table>'; }
else { helpMenu += '</table>' }
document.getElementById('menu').innerHTML = helpMenu;
// Add graticule
var showGrid = false;
2022-09-23 20:41:24 +08:00
var showRuler = false;
var Lgrid = L.latlngGraticule({
2019-11-25 21:48:43 +08:00
font: "Verdana",
fontColor: "#666",
zoomInterval: [
2019-11-25 21:48:43 +08:00
{start:1, end:2, interval:40},
{start:3, end:3, interval:20},
{start:4, end:4, interval:10},
{start:5, end:7, interval:5},
{start:8, end:20, interval:1}
]
});
// Add small sidc icons around edge of map for things just outside of view
// This function based heavily on Game Aware code from Måns Beckman
// Copyright (c) 2013 Måns Beckman, All rights reserved.
var edgeAware = function() {
if (!edgeEnabled) { return; }
map.removeLayer(edgeLayer)
edgeLayer = new L.layerGroup();
var mapBounds = map.getBounds();
var mapBoundsCenter = mapBounds.getCenter();
pSW = map.options.crs.latLngToPoint(mapBounds.getSouthWest(), map.getZoom());
pNE = map.options.crs.latLngToPoint(mapBounds.getNorthEast(), map.getZoom());
pCenter = map.options.crs.latLngToPoint(mapBoundsCenter, map.getZoom());
var viewBounds = L.latLngBounds(map.options.crs.pointToLatLng(L.point(pSW.x - (pCenter.x - pSW.x ), pSW.y - (pCenter.y - pSW.y )), map.getZoom()) , map.options.crs.pointToLatLng(L.point(pNE.x + (pNE.x - pCenter.x) , pNE.y + (pNE.y - pCenter.y) ), map.getZoom()) );
for (var id in markers) {
2023-05-17 15:45:05 +08:00
if (allData[id] && allData[id].hasOwnProperty("SIDC")) {
markerLatLng = markers[id].getLatLng();
if ( viewBounds.contains(markerLatLng) && !mapBounds.contains(markerLatLng) ) {
var k = (markerLatLng.lat - mapBoundsCenter.lat) / (markerLatLng.lng - mapBoundsCenter.lng);
if (markerLatLng.lng > mapBoundsCenter.lng) { x = mapBounds.getEast() - mapBoundsCenter.lng; }
else { x = (mapBounds.getWest() - mapBoundsCenter.lng); }
if (markerLatLng.lat < mapBoundsCenter.lat) { y = mapBounds.getSouth() - mapBoundsCenter.lat; }
else { y = mapBounds.getNorth() - mapBoundsCenter.lat; }
var lat = (mapBoundsCenter.lat + (k * x));
var lng = (mapBoundsCenter.lng + (y / k));
var iconAnchor = {x:5, y:5}
if (lng > mapBounds.getEast()) {
lng = mapBounds.getEast();
iconAnchor.x = 20;
}
if (lng < mapBounds.getWest()) {
lng = mapBounds.getWest();
iconAnchor.x = -5;
};
if (lat < mapBounds.getSouth()) {
lat = mapBounds.getSouth();
iconAnchor.y = 15;
}
if (lat > mapBounds.getNorth()) {
lat = mapBounds.getNorth();
iconAnchor.y = -5;
};
var eico = new ms.Symbol(allData[id].SIDC.substr(0,5)+"-------",{size:9});
var myicon = L.icon({
iconUrl: eico.toDataURL(),
iconAnchor: new L.Point(iconAnchor.x, iconAnchor.y),
className: "natoicon-s",
});
edgeLayer.addLayer(L.marker([lat,lng],{icon:myicon}))
}
}
}
edgeLayer.addTo(map)
}
// end of edge function
var panit = false;
function doPanit(v) {
if (v !== undefined) { panit = v; }
console.log("Panit set :",panit);
}
var heatAll = false;
function doHeatAll(v) {
if (v !== undefined) { heatall = v; }
console.log("Heatall set :",heatAll);
}
var lockit = false;
var mb = new L.LatLngBounds([[-120,-360],[120,360]]);
function doLock(v) {
if (v !== undefined) { lockit = v; }
if (lockit === false) {
mb = new L.LatLngBounds([[-120,-360],[120,360]]);
map.dragging.enable();
}
else {
mb = map.getBounds();
map.dragging.disable();
window.localStorage.setItem("lastpos",JSON.stringify(map.getCenter()));
window.localStorage.setItem("lastzoom", map.getZoom());
window.localStorage.setItem("lastlayer", baselayername);
//window.localStorage.setItem("clusterat", clusterAt);
window.localStorage.setItem("maxage", maxage);
console.log("Saved :",JSON.stringify(map.getCenter()),map.getZoom(),baselayername);
}
map.setMaxBounds(mb);
//console.log("Map bounds lock :",lockit);
}
// Remove old markers
function doTidyUp(l) {
if (l === "heatmap") {
heat.setLatLngs([]);
}
else {
var d = parseInt(Date.now()/1000);
for (var m in markers) {
if ((l && (l == markers[m].lay)) || typeof markers[m].ts != "undefined") {
if ((l && (l == markers[m].lay)) || (markers[m].hasOwnProperty("ts") && (Number(markers[m].ts) < d) && (markers[m].lay !== "_drawing"))) {
//console.log("STALE :",m);
if (typeof polygons[m+"_"] != "undefined") {
layers[polygons[m+"_"].lay].removeLayer(polygons[m+"_"]);
delete polygons[m+"_"];
}
if (typeof polygons[m] != "undefined") {
layers[markers[m].lay].removeLayer(polygons[m]);
delete polygons[m];
}
layers[markers[m].lay].removeLayer(markers[m]);
delete markers[m];
2020-03-24 19:53:54 +08:00
}
}
}
if (l) {
if (layers[l]) { map.removeLayer(layers[l]); layercontrol.removeLayer(layers[l]); delete layers[l]; }
if (overlays[l]) { map.removeLayer(overlays[l]); layercontrol.removeLayer(overlays[l]); delete overlays[l]; }
}
}
}
// Call tidyup every {maxage} seconds - default 10 mins
var stale = null;
function setMaxAge() {
maxage = document.getElementById('maxage').value;
if (stale) { clearInterval(stale); }
//if (maxage > 0) {
stale = setInterval( function() { doTidyUp() }, 20000); // check every 20 secs
}
setMaxAge();
// move the daylight / nighttime boundary (if enabled) every minute
function moveTerminator() { // if terminator line plotted move it every minute
if (layers["_daynight"] && layers["_daynight"].getLayers().length > 0) {
layers["_daynight"].clearLayers();
layers["_daynight"].addLayer(L.terminator());
}
}
setInterval( function() { moveTerminator() }, 60000 );
// move the rainfall overlay (if enabled) every 10 minutes
function moveRainfall() {
if (navigator.onLine && map.hasLayer(overlays["rainfall"])) {
overlays["rainfall"]["_url"] = 'https://tilecache.rainviewer.com/v2/radar/' + parseInt(Date.now()/600000)*600 + '/256/{z}/{x}/{y}/2/1_1.png';
overlays["rainfall"].redraw();
}
}
setInterval( function() { moveRainfall() }, 600000 );
function setCluster(v) {
clusterAt = v || 0;
console.log("clusterAt set:",clusterAt);
showMapCurrentZoom();
}
// Search for markers with names of ... or icons of ...
function doSearch() {
var value = document.getElementById('search').value;
marks = [];
marksIndex = 0;
for (var key in markers) {
if ( (~(key.toLowerCase()).indexOf(value.toLowerCase())) && (mb.contains(markers[key].getLatLng()))) {
marks.push(markers[key]);
}
if (markers[key].icon === value) {
marks.push(markers[key]);
}
}
moveToMarks();
if (marks.length === 0) {
// If no markers found let's try a geolookup...
var protocol = location.protocol;
if (protocol == "file:") { protocol = "https:"; }
var searchUrl = protocol + "//nominatim.openstreetmap.org/search?format=json&limit=1&q=";
fetch(searchUrl + value) // Call the fetch function passing the url of the API as a parameter
2019-11-25 21:48:43 +08:00
.then(function(resp) { return resp.json(); })
.then(function(data) {
if (data.length > 0) {
var bb = data[0].boundingbox;
map.fitBounds([ [bb[0],bb[2]], [bb[1],bb[3]] ]);
map.panTo([data[0].lat, data[0].lon]);
}
else {
document.getElementById('searchResult').innerHTML = "&nbsp;<font color='#ff0'>Not Found</font>";
}
})
.catch(function(err) {
if (err.toString() === "TypeError: Failed to fetch") {
document.getElementById('searchResult').innerHTML = "&nbsp;<font color='#ff0'>Not Found</font>";
}
});
}
else {
if (lockit) {
document.getElementById('searchResult').innerHTML = "&nbsp;<font color='#ff0'>Found "+marks.length+" results within bounds.</font>";
} else {
document.getElementById('searchResult').innerHTML = "&nbsp;<font color='#ff0'>Found "+marks.length+" results.</font>";
}
}
}
// Jump to a markers position - centralise it on map
function moveToMarks() {
if (marks.length > marksIndex) {
var m = marks[marksIndex];
map.setView(m.getLatLng(), map.getZoom());
m.openPopup();
marksIndex++;
setTimeout(moveToMarks, 2500);
}
}
// Clear Search With Marker names
function clearSearch() {
var value = document.getElementById('search').value;
marks = [];
marksIndex = 0;
for (var key in markers) {
if ( (~(key.toLowerCase()).indexOf(value.toLowerCase())) && (mb.contains(markers[key].getLatLng()))) {
marks.push(markers[key]);
}
}
removeMarks();
if (lockit) {
document.getElementById('searchResult').innerHTML = "";
}
else {
document.getElementById('searchResult').innerHTML = "";
}
}
function removeMarks() {
if (marks.length > marksIndex) {
var m = marks[marksIndex];
map.setView(m.getLatLng(), map.getZoom());
m.closePopup();
marksIndex++;
}
}
function toggleMenu() {
menuOpen = !menuOpen;
if (menuOpen) {
document.getElementById("menu").style.display = 'block';
} else {
document.getElementById("menu").style.display = 'none';
dialogue.close();
}
}
function openMenu() {
if (!menuOpen) {
menuOpen = true;
document.getElementById("menu").style.display = 'block';
}
}
function closeMenu() {
if (menuOpen) {
menuOpen = false;
document.getElementById("menu").style.display = 'none';
}
dialogue.close();
}
document.getElementById("menu").style.display = 'none';
map.on('overlayadd', function(e) {
if (typeof overlays[e.name].bringToFront === "function") {
overlays[e.name].bringToFront();
}
if (e.name == "satellite") {
overlays["satellite"].bringToBack();
}
if (e.name == "countries") {
overlays["countries"].bringToBack();
}
if (e.name == "heatmap") { // show heatmap button when it's layer is added.
clrHeat.addTo(map);
}
if (e.name == "day/night") {
layers["_daynight"].addLayer(L.terminator());
}
if (e.name == "drawing") {
overlays["drawing"].bringToFront();
2020-03-11 20:18:18 +08:00
map.addControl(colorControl);
2022-01-10 23:48:26 +08:00
map.addControl(drawControl);
}
ws.send(JSON.stringify({action:"addlayer", name:e.name}));
});
map.on('overlayremove', function(e) {
if (e.name == "heatmap") { // hide heatmap button when it's layer is removed.
clrHeat.removeFrom(map);
}
if (e.name == "day/night") {
layers["_daynight"].clearLayers();
}
if (e.name == "drawing") {
map.removeControl(drawControl);
2022-01-10 23:48:26 +08:00
map.removeControl(colorControl);
}
ws.send(JSON.stringify({action:"dellayer", name:e.name}));
});
map.on('baselayerchange', function(e) {
//console.log("base layer now :",e.name);
baselayername = e.name;
ws.send(JSON.stringify({action:"layer", name:e.name}));
});
function showMapCurrentZoom() {
//console.log("ZOOM:",map.getZoom());
for (var l in layers) {
if (layers[l].hasOwnProperty("_zoom")) {
if (map.getZoom() >= clusterAt) {
layers[l].disableClustering();
}
else {
layers[l].enableClustering();
}
}
}
setTimeout( function() {
for (var key in markers) {
if (polygons[key]) {
if (typeof layers[markers[key].lay].getVisibleParent === 'function') {
var vis = layers[markers[key].lay].getVisibleParent(markers[key]);
if ((vis) && (vis.hasOwnProperty("lay"))) {
polygons[key].setStyle({opacity:1});
}
else {
polygons[key].setStyle({opacity:0});
}
}
2019-11-25 21:48:43 +08:00
try {
if (polygons[key].hasOwnProperty("_layers")) {
2019-11-25 21:48:43 +08:00
polygons[key].eachLayer(function(layer) { layer.redraw(); });
}
else {
polygons[key].redraw();
}
} catch(e) {
console.log(key,polygons[key],e)
}
}
}
},750);
}
map.on('zoomend', function() {
showMapCurrentZoom();
window.localStorage.setItem("lastzoom", map.getZoom());
var b = map.getBounds();
oldBounds = {sw:{lat:b._southWest.lat,lng:b._southWest.lng},ne:{lat:b._northEast.lat,lng:b._northEast.lng}};
ws.send(JSON.stringify({action:"bounds", south:b._southWest.lat, west:b._southWest.lng, north:b._northEast.lat, east:b._northEast.lng, zoom:map.getZoom() }));
edgeAware();
});
map.on('moveend', function() {
window.localStorage.setItem("lastpos",JSON.stringify(map.getCenter()));
var b = map.getBounds();
if (b._southWest.lat !== oldBounds.sw.lat && b._southWest.lng !== oldBounds.sw.lng && b._northEast.lat !== oldBounds.ne.lat && b._northEast.lng !== oldBounds.ne.lng) {
ws.send(JSON.stringify({action:"bounds", south:b._southWest.lat, west:b._southWest.lng, north:b._northEast.lat, east:b._northEast.lng, zoom:map.getZoom() }));
oldBounds = {sw:{lat:b._southWest.lat,lng:b._southWest.lng},ne:{lat:b._northEast.lat,lng:b._northEast.lng}};
}
edgeAware();
});
map.on('locationfound', onLocationFound);
map.on('locationerror', onLocationError);
// single right click to add a marker
2021-11-25 04:20:55 +08:00
var addmenu = "<b>Add marker</b><br><input type='text' id='rinput' autofocus onkeydown='if (event.keyCode == 13) addThing();' placeholder='name (,icon/SIDC, layer, colour, heading)'/>";
if (navigator.onLine) { addmenu += '<br/><a href="https://spatialillusions.com/unitgenerator/" target="_new">MilSymbol SIDC generator</a>'; }
var rightmenuMap = L.popup({keepInView:true, minWidth:260}).setContent(addmenu);
const rgba2hex = (rgba) => `#${rgba.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/).slice(1).map((n, i) => (i === 3 ? Math.round(parseFloat(n) * 255) : parseFloat(n)).toString(16).padStart(2, '0').replace('NaN', '')).join('')}`;
const colorKeywordToRGB = (colorKeyword) => {
let el = document.createElement('div');
el.style.color = colorKeyword;
document.body.appendChild(el);
let rgbValue = window.getComputedStyle(el).color;
document.body.removeChild(el);
console.log(rgbValue);
return rgba2hex(rgbValue);
}
2021-09-16 23:10:58 +08:00
var rclk = {};
var hiderightclick = false;
var addThing = function() {
var thing = document.getElementById('rinput').value;
map.closePopup();
//popped = false;
var bits = thing.split(",");
var icon = (bits[1] || "circle").trim();
var lay = (bits[2] || "_drawing").trim();
var colo = (bits[3] ?? "#910000").trim();
colo = colorKeywordToRGB(colo);
var hdg = parseFloat(bits[4] || 0);
var drag = true;
2021-11-25 03:47:01 +08:00
var regi = /^[S,G,E,I,O][A-Z]{3}.*/i; // if it looks like a SIDC code
2021-12-17 05:45:21 +08:00
var d = {action:"point", name:bits[0].trim(), layer:lay, draggable:drag, lat:rclk.lat, lon:rclk.lng, hdg:hdg, ttl:0 };
if (regi.test(icon)) {
d.SIDC = (icon.toUpperCase()+"------------").substr(0,12);
}
else {
d.icon = icon;
d.iconColor = colo;
}
if (icon === "dot") { d.icon = 'fa-circle fa-fw'; }
ws.send(JSON.stringify(d));
delete d.action;
setMarker(d);
map.addLayer(layers[lay]);
}
var feedback = function(n,v,a,c) {
2021-02-20 23:42:55 +08:00
if (v === "$form") { v = form; }
if (markers[n]) {
//var fp = markers[n]._latlng;
// ws.send(JSON.stringify({action:a||"feedback", name:n, value:v, layer:markers[n].lay, lat:fp.lat, lon:fp.lng}));
var fb = allData[n];
fb.action = a || "feedback";
if (v !== undefined) { fb.value = v; }
ws.send(JSON.stringify(fb));
}
else {
if (n === undefined) { n = "map"; }
2021-09-16 23:10:58 +08:00
ws.send(JSON.stringify({action:a||"feedback", name:n, value:v, lat:rclk.lat, lon:rclk.lng}));
}
if (c === true) { map.closePopup(); }
}
2021-02-20 23:42:55 +08:00
var form = {};
var addToForm = function(n,v) {
form[n] = v;
}
2023-07-15 20:08:06 +08:00
// map.on('click', function(e) {
// ws.send(JSON.stringify({action:"click", lat:e.latlng.lat.toFixed(5), lon:e.latlng.lng.toFixed(5)}));
// });
// allow double right click to zoom out (if enabled)
// single right click opens a message window that adds a marker
var rclicked = false;
var rtout = null;
2021-02-20 23:42:55 +08:00
map.on('contextmenu', function(e) {
if (rclicked) {
rclicked = false;
clearTimeout(rtout);
if (map.doubleClickZoom.enabled()) {
map.zoomOut();
}
}
else {
rclicked = true;
rtout = setTimeout( function() {
rclicked = false;
if ((hiderightclick !== true) && (addmenu.length > 0)) {
rclk = e.latlng;
2021-02-20 23:42:55 +08:00
form = {};
rightmenuMap.setLatLng(e.latlng);
map.openPopup(rightmenuMap);
setTimeout( function() {
try { document.getElementById('rinput').focus(); }
catch(e) {}
}, 200);
}
}, 300);
}
});
// Layer control based on select box rather than radio buttons.
//var layercontrol = L.control.selectLayers(basemaps, overlays).addTo(map);
layercontrol = L.control.layers(basemaps, overlays);
// Add all the base layer maps if we are online.
var addBaseMaps = function(maplist,first) {
// console.log("MAPS",first,maplist)
var layerlookup = { OSMG:"OSM grey", OSMC:"OSM", OSMH:"OSM Humanitarian", EsriC:"Esri", EsriS:"Esri Satellite",
EsriR:"Esri Relief", EsriT:"Esri Topography", EsriO:"Esri Ocean", EsriDG:"Esri Dark Grey", NatGeo: "National Geographic",
UKOS:"UK OS OpenData", OpTop:"Open Topo Map",
HB:"Hike Bike OSM", ST:"Stamen Topography", SW:"Stamen Watercolor", AN:"AutoNavi (Chinese)"
}
if (navigator.onLine) {
// Use this for OSM online maps
var osmUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
var osmAttrib='Map data © OpenStreetMap contributors';
if (maplist.indexOf("MB3d")!==-1) { // handle the case of 3d by redirecting to that page instead.
window.location.href("index3d.html");
}
if (maplist.indexOf("OSMG")!==-1) {
basemaps[layerlookup["OSMG"]] = new L.TileLayer.Grayscale(osmUrl, {
attribution:osmAttrib,
maxNativeZoom:19,
maxZoom:20,
subdomains: ['a','b','c']
});
}
if (maplist.indexOf("OSMC")!==-1) {
basemaps[layerlookup["OSMC"]] = new L.TileLayer(osmUrl, {
attribution:osmAttrib,
maxNativeZoom:19,
maxZoom:20,
subdomains: ['a','b','c']
});
}
if (maplist.indexOf("OSMH")!==-1) {
basemaps[layerlookup["OSMH"]] = new L.TileLayer("https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png", {
attribution:"Map data © OpenStreetMap Contributors. Courtesy of Humanitarian OpenStreetMap Team",
maxNativeZoom:19,
maxZoom:20,
subdomains: ['a','b']
});
}
// Extra Leaflet map layers from https://leaflet-extras.github.io/leaflet-providers/preview/
if (maplist.indexOf("EsriC")!==-1) {
basemaps[layerlookup["EsriC"]] = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
attribution:'Tiles &copy; Esri',
maxNativeZoom:19,
maxZoom:20
});
}
if (maplist.indexOf("EsriS")!==-1) {
basemaps[layerlookup["EsriS"]] = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
//var Esri_WorldImagery = L.tileLayer('http://clarity.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {{
attribution:'Tiles &copy; Esri',
maxNativeZoom:17, maxZoom:20
});
}
if (maplist.indexOf("EsriT")!==-1) {
basemaps[layerlookup["EsriT"]] = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', {
attribution:'Tiles &copy; Esri &mdash; Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community'
});
}
if (maplist.indexOf("EsriR")!==-1) {
basemaps[layerlookup["EsriR"]] = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}', {
attribution:'Tiles &copy; Esri',
maxNativeZoom:13,
maxZoom:16
});
}
if (maplist.indexOf("EsriO")!==-1) {
basemaps[layerlookup["EsriO"]] = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Ocean_Basemap/MapServer/tile/{z}/{y}/{x}', {
attribution:'Tiles &copy; Esri &mdash; Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri',
maxNativeZoom:10,
maxZoom:13
});
}
if (maplist.indexOf("EsriDG")!==-1) {
basemaps[layerlookup["EsriDG"]] = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Esri, DeLorme, NAVTEQ',
maxNativeZoom:16,
maxZoom:18
});
}
if (maplist.indexOf("NatGeo")!==-1) {
basemaps[layerlookup["NatGeo"]] = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri',
maxNativeZoom:12
});
}
if (maplist.indexOf("UKOS")!==-1) {
basemaps[layerlookup["UKOS"]] = L.tileLayer('https://geo.nls.uk/maps/opendata/{z}/{x}/{y}.png', {
attribution: '<a href="https://geo.nls.uk/maps/">National Library of Scotland Historic Maps</a>',
bounds: [[49.6, -12], [61.7, 3]],
minZoom:1, maxNativeZoom:17, maxZoom:20,
subdomains: '0123'
});
}
if (maplist.indexOf("OpTop")!==-1) {
basemaps[layerlookup["OpTop"]] = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
subdomains: 'abc',
maxZoom: 19,
attribution: '&copy; <a href="https://www.opentopomap.org/copyright">OpenTopoMap</a> contributors'
});
}
if (maplist.indexOf("HB")!==-1) {
basemaps[layerlookup["HB"]] = L.tileLayer('https://tiles.wmflabs.org/hikebike/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
}
if (maplist.indexOf("AN")!==-1) {
basemaps["AutoNavi"] = L.tileLayer('https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', {
attribution: 'Tiles &copy; 高德地图',
maxNativeZoom:14,
maxZoom: 19,
});
}
// Nice terrain based maps by Stamen Design
if (maplist.indexOf("ST")!==-1) {
var terrainUrl = "https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.jpg";
basemaps[layerlookup["ST"]] = L.tileLayer(terrainUrl, {
subdomains: ['a','b','c','d'],
minZoom: 0,
maxZoom: 20,
type: 'jpg',
attribution: 'Map tiles by <a href="https://stamen.com">Stamen Design</a>, under <a href="https://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="https://openstreetmap.org">OpenStreetMap</a>, under <a href="https://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>'
});
}
// Nice watercolour based maps by Stamen Design
if (maplist.indexOf("SW")!==-1) {
var watercolorUrl = "https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg";
basemaps[layerlookup["SW"]] = L.tileLayer(watercolorUrl, {
subdomains: ['a','b','c','d'],
minZoom: 0,
maxZoom: 20,
type: 'jpg',
attribution: 'Map tiles by <a href="https://stamen.com">Stamen Design</a>, under <a href="https://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="https://openstreetmap.org">OpenStreetMap</a>, under <a href="https://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>'
});
}
}
if (first) {
if (layerlookup[first]) { baselayername = layerlookup[first]; }
else { basenayername = first; }
}
else {
basenayername = Object.keys(basemaps)[0];
}
basemaps[baselayername].addTo(map);
if (showLayerMenu) {
map.removeControl(layercontrol);
layercontrol = L.control.layers(basemaps, overlays).addTo(map);
}
2020-03-11 20:18:18 +08:00
}
// Now add the overlays
var addOverlays = function(overlist) {
//console.log("OVERLAYS",overlist)
// var overlookup = { DR:"Drawing", CO:"Countries", DN:"Day/Night", BU:"Buildings", SN:"Ship Navigaion", HM:"Heatmap", AC:"Air corridors", TL:"Place labels" };
// "DR,CO,DN,BU,SN,HM"
// Add the drawing layer...
if (overlist.indexOf("DR")!==-1) {
//var colorPickButton = L.easyButton({states:[{icon:'fa-tint fa-lg', onClick:function() { console.log("PICK"); }, title:'Pick Colour'}]});
2022-01-10 23:48:26 +08:00
var redButton = L.easyButton('fa-square wm-red', function(btn) { changeDrawColour("#FF4040"); })
var blueButton = L.easyButton('fa-square wm-blue', function(btn) { changeDrawColour("#4040F0"); })
var greenButton = L.easyButton('fa-square wm-green', function(btn) { changeDrawColour("#40D040"); })
var yellowButton = L.easyButton('fa-square wm-yellow', function(btn) { changeDrawColour("#FFFF40"); })
2022-01-13 01:05:44 +08:00
var cyanButton = L.easyButton('fa-square wm-cyan', function(btn) { changeDrawColour("#40F0F0"); })
var magentaButton = L.easyButton('fa-square wm-magenta', function(btn) { changeDrawColour("#F040F0"); })
2022-01-10 23:48:26 +08:00
var blackButton = L.easyButton('fa-square wm-black', function(btn) { changeDrawColour("#000000"); })
var whiteButton = L.easyButton('fa-square wm-white', function(btn) { changeDrawColour("#EEEEEE"); })
2022-01-13 01:05:44 +08:00
colorControl = L.easyBar([redButton,blueButton,greenButton,yellowButton,cyanButton,magentaButton,blackButton,whiteButton]);
layers["_drawing"] = new L.FeatureGroup();
overlays["drawing"] = layers["_drawing"];
map.options.drawControlTooltips = false;
var drawCount = 0;
drawControl = new L.Control.Draw({
draw: {
polyline: { shapeOptions: { clickable:true } },
marker: false,
//circle: false,
circle: { shapeOptions: { clickable:true } },
circlemarker: false,
rectangle: { shapeOptions: { clickable:true } },
polygon: { shapeOptions: { clickable:true } }
}
//edit: none
// {
// featureGroup: layers["_drawing"],
// remove: true,
// edit: true
// }
});
var changeDrawColour = function(col) {
drawingColour = col;
// console.log("COLOR",col)
drawControl.setDrawingOptions({
polyline: { shapeOptions: { color:drawingColour } },
circle: { shapeOptions: { color:drawingColour } },
rectangle: { shapeOptions: { color:drawingColour } },
polygon: { shapeOptions: { color:drawingColour } }
});
}
var shape;
map.on('draw:created', function (e) {
var name = e.layerType + drawCount;
drawCount = drawCount + 1;
e.layer.on('contextmenu', function(e) {
L.DomEvent.stopPropagation(e);
var rmen = L.popup({offset:[0,-12]}).setLatLng(e.latlng);
rmen.setContent("<input type='text' autofocus value='"+e.target.name+"' id='dinput' placeholder='name (,icon, layer)'/><br/><button onclick='editPoly(\""+e.target.name+"\",true);'>Edit points</button><button onclick='delMarker(\""+e.target.name+"\",true);'>Delete</button><button onclick='sendDrawing();'>OK</button>");
map.openPopup(rmen);
});
var la, lo, cent;
if (e.layer.hasOwnProperty("_latlng")) {
la = e.layer._latlng.lat;
lo = e.layer._latlng.lng;
cent = e.layer._latlng;
}
else {
cent = e.layer.getBounds().getCenter();
}
var m = {action:"draw", name:name, type:e.layerType, layer:"_drawing", options:e.layer.options, radius:e.layer._mRadius, lat:la, lon:lo};
if (e.layer.hasOwnProperty("_latlngs")) {
if (e.layer.options.fill === false) { m.line = e.layer._latlngs; }
else { m.area = e.layer._latlngs[0]; }
}
shape = {m:m, layer:e.layer};
polygons[name] = shape.layer;
polygons[name].lay = "_drawing";
polygons[name].name = name;
layers["_drawing"].addLayer(shape.layer);
2020-11-19 19:48:54 +08:00
var rightmenuMarker = L.popup({offset:[0,-12]}).setContent("<input type='text' autofocus value='"+name+"' id='dinput' placeholder='name (,icon, layer)'/><br/><button onclick='editPoly(\""+name+"\",true);'>Edit points</button><button onclick='delMarker(\""+name+"\",true);'>Delete</button><button onclick='sendDrawing(\""+name+"\");'>OK</button>");
if (e.layer.options.fill === false && navigator.onLine) {
rightmenuMarker = L.popup({offset:[0,-12]}).setContent("<input type='text' autofocus value='"+name+"' id='dinput' placeholder='name (,icon, layer)'/><br/><button onclick='editPoly(\""+name+"\",true);'>Edit points</button><button onclick='delMarker(\""+name+"\",true);'>Delete</button><button onclick='sendRoute(\""+name+"\");'>Route</button><button onclick='sendDrawing(\""+name+"\");'>OK</button>");
}
rightmenuMarker.setLatLng(cent);
setTimeout(function() {map.openPopup(rightmenuMarker)},25);
});
2020-11-19 19:48:54 +08:00
sendDrawing = function(n) {
var thing = document.getElementById('dinput').value;
map.closePopup();
shape.m.name = thing;
delMarker(n,true);
polygons[thing] = shape.layer;
polygons[thing].lay = "_drawing";
polygons[thing].name = thing;
layers["_drawing"].addLayer(shape.layer);
ws.send(JSON.stringify(shape.m));
}
var defaultOptions = function () {
var options = {};
options.precision = 5;
options.factor = Math.pow(10, options.precision);
options.dimension = 2;
return options;
};
var decode = function (encoded, options) {
options = defaultOptions(options);
var flatPoints = decodeDeltas(encoded);
var points = [];
for (var i = 0, len = flatPoints.length; i + (options.dimension - 1) < len;) {
var point = [];
for (var dim = 0; dim < options.dimension; ++dim) {
point.push(flatPoints[i++]);
}
points.push(point);
}
return points;
}
var decodeDeltas = function (encoded, options) {
options = defaultOptions(options);
var lastNumbers = [];
var numbers = decodeFloats(encoded, options);
for (var i = 0, len = numbers.length; i < len;) {
for (var d = 0; d < options.dimension; ++d, ++i) {
numbers[i] = Math.round((lastNumbers[d] = numbers[i] + (lastNumbers[d] || 0)) * options.factor) / options.factor;
}
}
return numbers;
}
var decodeFloats = function (encoded, options) {
options = defaultOptions(options);
var numbers = decodeSignedIntegers(encoded);
for (var i = 0, len = numbers.length; i < len; ++i) {
numbers[i] /= options.factor;
}
return numbers;
2020-11-19 19:48:54 +08:00
}
var decodeSignedIntegers = function (encoded) {
var numbers = decodeUnsignedIntegers(encoded);
for (var i = 0, len = numbers.length; i < len; ++i) {
var num = numbers[i];
numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1);
}
return numbers;
}
var decodeUnsignedIntegers = function (encoded) {
var numbers = [];
var current = 0;
var shift = 0;
for (var i = 0, len = encoded.length; i < len; ++i) {
var b = encoded.charCodeAt(i) - 63;
current |= (b & 0x1f) << shift;
if (b < 0x20) {
numbers.push(current);
current = 0;
shift = 0;
} else {
shift += 5;
}
}
return numbers;
}
sendRoute = function(n) {
var p = (polygons[n]._latlngs.map(function(x) {
return x.lng+","+x.lat;
})).join(';');
fetch('https://router.project-osrm.org/route/v1/driving/'+p)
.then(response => response.json())
.then(data => {
if (data.code !== "Ok") { sendDrawing(n); }
var r = decode(data.routes[0].geometry).map( x => L.latLng(x[0],x[1]) );
polygons[n]._latlngs = r;
shape.m.line = r;
// shape.m.type = {label:"routing", distance:data.routes[0].distance, duration:data.routes[0].duration}
shape.m.type = "route";
shape.m.distance = data.routes[0].distance;
shape.m.duration = data.routes[0].duration;
sendDrawing(n);
});
2020-11-19 19:48:54 +08:00
}
}
// Add the countries (world-110m) for offline use
if (overlist.indexOf("CO") !== -1 || !navigator.onLine) {
var customTopoLayer = L.geoJson(null, {clickable:false, style: {color:"blue", weight:2, fillColor:"#cf6", fillOpacity:0.04}});
layers["_countries"] = omnivore.topojson('images/world-50m-flat.json',null,customTopoLayer);
overlays["countries"] = layers["_countries"];
2020-11-19 19:48:54 +08:00
}
// Add the day/night overlay
if (overlist.indexOf("DN")!==-1) {
layers["_daynight"] = new L.LayerGroup();
overlays["day/night"] = layers["_daynight"];
2020-11-19 19:48:54 +08:00
}
// Add live rain data
if (overlist.indexOf("RA")!==-1) {
if (navigator.onLine) {
overlays["rainfall"] = new L.TileLayer('https://tilecache.rainviewer.com/v2/radar/' + parseInt(Date.now()/600000)*600 + '/256/{z}/{x}/{y}/2/1_1.png', {
tileSize: 256,
opacity: 0.4,
transparent: true,
attribution: '<a href="https://rainviewer.com" target="_blank">rainviewer.com</a>'
});
2020-11-19 19:48:54 +08:00
}
}
// Add the buildings layer
if (overlist.indexOf("BU")!==-1) {
overlays["buildings"] = new OSMBuildings(map).load();
// map.removeLayer(overlays["buildings"]); // Hide it at start
}
// Add Railways
if (overlist.indexOf("RW")!==-1) {
// eg https://a.tiles.openrailwaymap.org/standard/11/1015/686.png
overlays["railways"] = L.tileLayer('https://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: 'Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> | Map style: &copy; <a href="https://www.OpenRailwayMap.org">OpenRailwayMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
});
}
// Add Air Corridors
if (overlist.indexOf("AC")!==-1) {
overlays["air corridors"] = L.tileLayer('https://{s}.tile.maps.openaip.net/geowebcache/service/tms/1.0.0/openaip_basemap@EPSG%3A900913@png/{z}/{x}/{y}.{ext}', {
attribution: '<a href="https://www.openaip.net/">openAIP Data</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-NC-SA</a>)',
ext: 'png',
minZoom: 4,
maxZoom: 15,
maxNativeZoom: 14,
tms: true,
detectRetina: true,
subdomains: '12'
});
}
// Add the OpenSea markers layer
if (overlist.indexOf("SN")!==-1) {
2021-11-03 23:13:51 +08:00
overlays["ship navigation"] = L.tileLayer('https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: 'Map data: &copy; <a href="https://www.openseamap.org">OpenSeaMap</a> contributors'
});
}
// Add the Stamen Toner Labels layer
if (overlist.indexOf("TL")!==-1) {
overlays["place labels"] = L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/toner-labels/{z}/{x}/{y}{r}.{ext}', {
attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
subdomains: 'abcd',
minZoom: 0,
maxZoom: 20,
ext: 'png'
});
}
// Add the heatmap layer (and add delete LatLng function)
if (overlist.indexOf("HM") !== -1) {
heat = L.heatLayer([], {radius:60, gradient:{0.2:'blue', 0.4:'lime', 0.6:'red', 0.8:'yellow', 1:'white'}});
heat.delLatLng = function(ll) {
heat._latlngs = heat._latlngs.filter(v => { return v != ll; } );
heat._redraw();
}
layers["_heat"] = new L.LayerGroup().addLayer(heat);
overlays["heatmap"] = layers["_heat"];
}
if (showLayerMenu) {
map.removeControl(layercontrol);
layercontrol = L.control.layers(basemaps, overlays).addTo(map);
}
}
// Add the layers control widget
if (!inIframe) { layercontrol.addTo(map); }
else { showLayerMenu = false;}
var coords = L.control.mouseCoordinate({position:"bottomleft"});
2019-11-13 07:56:50 +08:00
// Add an optional legend
var legend = L.control({ position: "bottomleft" });
// Add the dialog box for messages
var dialogue = L.control.dialog({initOpen:false, size:[600,400], anchor:[50,150]}).addTo(map);
dialogue.freeze();
var doDialog = function(d) {
//console.log("DIALOGUE",d);
dialogue.setContent(d);
dialogue.open();
}
2019-06-16 22:57:30 +08:00
var helpText = '<h3>Node-RED - Map all the things</h3><br/>';
helpText += '<p><i class="fa fa-search fa-lg fa-fw"></i> <b>Search</b> - You may enter a name, or partial name, or icon name of an object to search for.';
helpText += 'The map will then jump to centre on each of the results in turn. If nothing is found locally it will try to';
helpText += 'search for a place name if connected to a network.</p>';
helpText += '<p><i class="fa fa-spinner fa-lg fa-fw"></i> <b>Set Max Age</b> - You can set the time after which points';
helpText += 'that haven\'t been updated get removed.</p>';
helpText += '<p><i class="fa fa-search-plus fa-lg fa-fw"></i> <b>Cluster at zoom</b> - lower numbers mean less clustering. 0 means disable totally.</p>';
helpText += '<p><i class="fa fa-arrows fa-lg fa-fw"></i> <b>Auto Pan</b> - When selected, the map will';
helpText += 'automatically move to centre on each data point as they arrive.</p>';
helpText += '<p><i class="fa fa-lock fa-lg fa-fw"></i> <b>Lock Map</b> - When selected will save the';
helpText += 'currently displayed area and basemap.';
helpText += 'Reloading the map in the current browser will return to the same view.';
helpText += 'This can be used to set your initial start position.';
helpText += 'While active it also restricts the "auto pan" and "search" to within that area.</p>';
helpText += '<p><i class="fa fa-globe fa-lg fa-fw"></i> <b>Heatmap all layers</b> - When selected';
helpText += 'all layers whether hidden or not will contribute to the heatmap.';
helpText += 'The default is that only visible layers add to the heatmap.</p>';
// Delete a marker (and notify websocket)
var delMarker = function(dname,note) {
if (note) { map.closePopup(); }
if (typeof polygons[dname] != "undefined") {
layers[polygons[dname].lay].removeLayer(polygons[dname]);
delete polygons[dname];
}
if (typeof polygons[dname+"_"] != "undefined") {
layers[polygons[dname+"_"].lay].removeLayer(polygons[dname+"_"]);
delete polygons[dname+"_"];
}
if (typeof markers[dname] != "undefined") {
if (heat && markers[dname].hasOwnProperty("_latlng")) {
try { heat.delLatLng(markers[dname]._latlng); }
catch(e) { }
}
layers[markers[dname].lay].removeLayer(markers[dname]);
map.removeLayer(markers[dname]);
delete markers[dname];
}
delete allData[dname];
if (note) { ws.send(JSON.stringify({action:"delete", name:dname, deleted:true})); }
}
var editPoly = function(pname) {
map.closePopup();
editFeatureGroup = L.featureGroup();
editToolbar = new L.EditToolbar({ featureGroup:editFeatureGroup });
editHandler = editToolbar.getModeHandlers()[0].handler;
editHandler._map = map;
polygons[pname].on("dblclick", function(e) {
editHandler.disable();
editFeatureGroup.removeLayer(polygons[pname]);
polygons[pname].off("dblclick");
L.DomEvent.stopPropagation(e);
var la, lo;
if (e.target.hasOwnProperty("_latlng")) {
la = e.target._latlng.lat;
lo = e.target._latlng.lng;
}
var m = {action:"draw", name:pname, layer:polygons[pname].lay, options:e.target.options, radius:e.target._mRadius, lat:la, lon:lo};
if (e.target.hasOwnProperty("_latlngs")) {
if (e.target.options.fill === false) { m.line = e.target._latlngs; }
else { m.area = e.target._latlngs[0]; }
}
ws.send(JSON.stringify(m));
})
editFeatureGroup.addLayer(polygons[pname]);
editHandler.enable();
}
2019-11-25 21:48:43 +08:00
var rangerings = function(latlng, options) {
options = L.extend({
ranges: [250,500,750,1000],
pan: 0,
fov: 60,
color: '#aaaa00'
2019-11-25 21:48:43 +08:00
}, options);
var rings = L.featureGroup();
if (typeof options.ranges === "number") { options.ranges = [ options.ranges ]; }
for (var i = 0; i < options.ranges.length; i++) {
L.semiCircle(latlng, {
radius: options.ranges[i],
fill: false,
color: options.color,
weight: options.weight ?? 1
2019-11-25 21:48:43 +08:00
}).setDirection(options.pan, options.fov).addTo(rings);
}
return rings;
}
// the MAIN add something to map function
function setMarker(data) {
var rightmenu = function(m) {
m.on('click', function(e) {
var fb = allData[data.name];
fb.action = "click";
ws.send(JSON.stringify(fb));
});
// customise right click context menu
var rightcontext = "";
//if (polygons[data.name] == undefined) {
rightcontext = "<button id='delbutton' onclick='delMarker(\""+data.name+"\",true);'>Delete</button>";
//}
if (data.editable) {
rightcontext = "<button onclick='editPoly(\""+data.name+"\",true);'>Edit</button><button onclick='delMarker(\""+data.name+"\",true);'>Delete</button>";
}
if ((data.contextmenu !== undefined) && (typeof data.contextmenu === "string")) {
rightcontext = data.contextmenu.replace(/\$name/g,'"'+data.name+'"');
delete data.contextmenu;
}
if (rightcontext.length > 0) {
var rightmenuMarker = L.popup({offset:[0,-12]}).setContent("<b>"+data.name+"</b><br/>"+rightcontext);
if (hiderightclick !== true) {
m.on('contextmenu', function(e) {
L.DomEvent.stopPropagation(e);
rightmenuMarker.setLatLng(e.latlng);
map.openPopup(rightmenuMarker);
});
}
}
else {
if (hiderightclick !== true) {
m.on('contextmenu', function(e) {
L.DomEvent.stopPropagation(e);
});
}
}
return m;
}
2021-07-02 20:15:03 +08:00
// console.log("DATA", typeof data, data);
2021-11-29 03:38:25 +08:00
if (data.deleted == true) { // remove markers we are told to
delMarker(data.name);
return;
}
var ll;
var lli = null;
var opt = {};
opt.color = data.color ?? data.lineColor ?? "#910000";
opt.fillColor = data.fillColor ?? "#910000";
opt.stroke = (data.hasOwnProperty("stroke")) ? data.stroke : true;
2021-12-21 21:29:36 +08:00
opt.weight = data.weight;
opt.opacity = data.opacity;
2021-10-24 23:04:44 +08:00
opt.fillOpacity = data.fillOpacity;
opt.clickable = (data.hasOwnProperty("clickable")) ? data.clickable : false;
opt.fill = (data.hasOwnProperty("fill")) ? data.fill : true;
if (data.hasOwnProperty("dashArray")) { opt.dashArray = data.dashArray; }
2021-10-24 23:04:44 +08:00
if (opt.fillOpacity === undefined) { opt.fillOpacity = 0.2; }
2021-12-21 21:29:36 +08:00
if (opt.opacity === undefined) { opt.opacity = 1; }
if (opt.weight === undefined) { opt.weight = 2; }
// Replace building
if (data.hasOwnProperty("building")) {
if ((data.building === "") && layers.hasOwnProperty("buildings")) {
map.removeLayer(layers["buildings"]);
layercontrol._update();
layers["buildings"] = overlays["buildings"].set("");
return;
}
//layers["buildings"] = new OSMBuildings(map).set(data.building);
layers["buildings"] = overlays["buildings"].set(data.building);
map.addLayer(layers["buildings"]);
return;
}
2021-04-20 22:08:38 +08:00
var lll = "unknown";
if (markers.hasOwnProperty(data.name) && markers[data.name].hasOwnProperty("lay")) {
lll = markers[data.name].lay;
}
var lay = data.layer ?? lll;
if (!data.hasOwnProperty("action") || data.action.indexOf("layer") === -1) {
if (typeof layers[lay] == "undefined") { // add layer if if doesn't exist
if (clusterAt > 0) {
layers[lay] = new L.MarkerClusterGroup({
maxClusterRadius:50,
spiderfyDistanceMultiplier:1.8,
disableClusteringAtZoom:clusterAt
//zoomToBoundsOnClick:false
});
}
else {
layers[lay] = new L.LayerGroup();
}
overlays[lay] = layers[lay];
if (showLayerMenu !== false) {
layercontrol.addOverlay(layers[lay],lay);
}
map.addLayer(overlays[lay]);
//console.log("ADDED LAYER",lay,layers);
}
if (!allData.hasOwnProperty(data.name)) { allData[data.name] = {}; }
delete data.action;
2019-06-16 22:57:30 +08:00
Object.keys(data).forEach(function(key) {
if (data[key] == null) { delete allData[data.name][key]; }
else { allData[data.name][key] = data[key]; }
});
data = Object.assign({},allData[data.name]);
}
delete data.action;
if (typeof markers[data.name] != "undefined") {
if (markers[data.name].lay !== lay) {
delMarker(data.name);
}
else {
try {layers[lay].removeLayer(markers[data.name]); }
catch(e) { console.log("OOPS"); }
}
}
2019-11-25 21:48:43 +08:00
if (typeof polygons[data.name] != "undefined") { layers[lay].removeLayer(polygons[data.name]); }
if (data.hasOwnProperty("line") && Array.isArray(data.line)) {
delete opt.fill;
if (!data.hasOwnProperty("weight")) { opt.weight = 3; } //Standard settings different for lines
if (!data.hasOwnProperty("opacity")) { opt.opacity = 0.8; }
var polyln = L.polyline(data.line, opt);
polygons[data.name] = rightmenu(polyln);
if (data.hasOwnProperty("fly") && data.fly === true) {
map.flyToBounds(polygons[data.name].getBounds(),{padding:[50,50]})
} else if (data.hasOwnProperty("fit") && data.fit === true) {
2021-12-21 23:44:56 +08:00
map.fitBounds(polygons[data.name].getBounds(),{padding:[50,50]})
}
}
else if (data.hasOwnProperty("area") && Array.isArray(data.area)) {
var polyarea;
if (data.area.length === 2) { polyarea = L.rectangle(data.area, opt); }
else { polyarea = L.polygon(data.area, opt); }
polygons[data.name] = rightmenu(polyarea);
if (data.hasOwnProperty("fly") && data.fly === true) {
map.flyToBounds(polygons[data.name].getBounds(),{padding:[50,50]})
} else if (data.hasOwnProperty("fit") && data.fit === true) {
2021-12-21 21:29:36 +08:00
map.fitBounds(polygons[data.name].getBounds(),{padding:[50,50]})
}
}
if (data.hasOwnProperty("greatcircle") && Array.isArray(data.greatcircle) && data.greatcircle.length === 2) {
delete opt.fill;
2022-02-25 05:28:54 +08:00
opt.vertices = opt.vertices || 20;
if (!data.hasOwnProperty("weight")) { opt.weight = 3; } //Standard settings different for lines
if (!data.hasOwnProperty("opacity")) { opt.opacity = 0.8; }
var greatc = L.Polyline.Arc(data.greatcircle[0], data.greatcircle[1], opt);
var aml = new L.Wrapped.Polyline(greatc._latlngs, opt);
polygons[data.name] = rightmenu(aml);
if (data.hasOwnProperty("fly") && data.fly === true) {
map.flyToBounds(polygons[data.name].getBounds(),{padding:[50,50]})
} else if (data.hasOwnProperty("fit") && data.fit === true) {
map.fitBounds(polygons[data.name].getBounds(),{padding:[50,50]})
}
}
else if (data.hasOwnProperty("sdlat") && data.hasOwnProperty("sdlon")) {
if (!data.hasOwnProperty("iconColor")) { opt.color = "blue"; } //different standard Color Settings
if (!data.hasOwnProperty("fillColor")) { opt.fillColor = "blue"; }
var ellipse = L.ellipse(new L.LatLng((data.lat*1), (data.lon*1)), [200000*data.sdlon*Math.cos(data.lat*Math.PI/180), 200000*data.sdlat], 0, opt);
polygons[data.name] = rightmenu(ellipse);
}
else if (data.hasOwnProperty("radius")) {
if (data.hasOwnProperty("lat") && data.hasOwnProperty("lon")) {
var polycirc;
if (Array.isArray(data.radius)) {
2022-01-08 20:04:33 +08:00
//polycirc = L.ellipse(new L.LatLng((data.lat*1), (data.lon*1)), [data.radius[0]*Math.cos(data.lat*Math.PI/180), data.radius[1]], data.tilt || 0, opt);
polycirc = L.ellipse(new L.LatLng((data.lat*1), (data.lon*1)), [data.radius[0], data.radius[1]], data.tilt || 0, opt);
}
else {
polycirc = L.circle(new L.LatLng((data.lat*1), (data.lon*1)), data.radius*1, opt);
}
polygons[data.name] = rightmenu(polycirc);
if (!data.hasOwnProperty("icon")) {
delete (data.lat);
delete (data.lon);
}
}
}
2019-11-25 21:48:43 +08:00
else if (data.hasOwnProperty("arc")) {
if (data.hasOwnProperty("lat") && data.hasOwnProperty("lon")) {
polygons[data.name] = rangerings(new L.LatLng((data.lat*1), (data.lon*1)), data.arc);
}
}
2020-03-24 19:53:54 +08:00
else if (data.hasOwnProperty("geojson")) {
2020-05-06 06:37:27 +08:00
doGeojson(data.name,data.geojson,(data.layer || "unknown"),opt);
2020-03-24 19:53:54 +08:00
}
if (polygons[data.name] !== undefined) {
polygons[data.name].lay = lay;
if (opt.clickable === true) {
var words = "<b>"+data.name+"</b>";
2019-11-25 21:48:43 +08:00
if (data.popup) { words = words + "<br/>" + data.popup; }
polygons[data.name].bindPopup(words, {autoClose:false, closeButton:true, closeOnClick:false, minWidth:200});
}
//polygons[data.name] = rightmenu(polygons[data.name]); // DCJ Investigate
layers[lay].addLayer(polygons[data.name]);
}
if (typeof data.coordinates == "object") { ll = new L.LatLng(data.coordinates[1],data.coordinates[0]); }
else if (data.hasOwnProperty("position") && data.position.hasOwnProperty("lat") && data.position.hasOwnProperty("lon")) {
data.lat = data.position.lat*1;
data.lon = data.position.lon*1;
data.alt = data.position.alt;
if (parseFloat(data.position.alt) == data.position.alt) { data.alt = data.position.alt + " m"; }
delete data.position;
ll = new L.LatLng((data.lat*1), (data.lon*1));
}
else if (data.hasOwnProperty("lat") && data.hasOwnProperty("lon")) { ll = new L.LatLng((data.lat*1), (data.lon*1)); }
else if (data.hasOwnProperty("latitude") && data.hasOwnProperty("longitude")) { ll = new L.LatLng((data.latitude*1), (data.longitude*1)); }
else {
// console.log("No location:",data);
return;
}
if (ll.lat === 0 && ll.lng === 0) {
// Add a little wobble so we can zoom into each if required.
console.log(data.name,"is at null island.");
ll.lat = Math.round(1000000 * ll.lat + Math.random() * 10000 - 5000) / 1000000;
ll.lng = Math.round(1000000 * ll.lng + Math.random() * 10000 - 5000) / 1000000;
}
// Adding new L.LatLng object (lli) when optional intensity value is defined. Only for use in heatmap layer
if (typeof data.coordinates == "object") { lli = new L.LatLng(data.coordinates[2],data.coordinates[1],data.coordinates[0]); }
else if (data.hasOwnProperty("lat") && data.hasOwnProperty("lon") && data.hasOwnProperty("intensity")) { lli = new L.LatLng((data.lat*1), (data.lon*1), (data.intensity*1)); }
else if (data.hasOwnProperty("latitude") && data.hasOwnProperty("longitude") && data.hasOwnProperty("intensity")) { lli = new L.LatLng((data.latitude*1), (data.longitude*1), (data.intensity*1)); }
else { lli = ll }
// Create the icons... handle plane, car, ship, wind, earthquake as specials
var marker, myMarker;
var icon, q;
var words = "";
var labelOffset = [12,0];
var drag = false;
if (data.draggable === true) { drag = true; }
if (data.hasOwnProperty("icon")) {
var dir = parseFloat(data.hdg ?? data.heading ?? data.bearing ?? "0");
if (data.icon === "ship") {
marker = L.boatMarker(ll, {
title: data.name,
color: (data.iconColor ?? "blue")
});
marker.setHeading(dir);
q = 'https://www.bing.com/images/search?q='+data.icon+'%20%2B"'+encodeURIComponent(data.name)+'"';
words += '<a href=\''+q+'\' target="_thingpic">Pictures</a><br>';
}
else if (data.icon === "plane") {
data.iconColor = data.iconColor || "black";
if (data.hasOwnProperty("squawk")) {
2019-10-16 18:26:00 +08:00
if (data.squawk == 7500 || data.squawk == 7600 || data.squawk == 7700) {
data.iconColor = "red";
2019-10-16 18:26:00 +08:00
}
}
icon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="310px" height="310px" viewBox="0 0 310 310">';
icon += '<path d="M134.875,19.74c0.04-22.771,34.363-22.771,34.34,0.642v95.563L303,196.354v35.306l-133.144-43.821v71.424l30.813,24.072v27.923l-47.501-14.764l-47.501,14.764v-27.923l30.491-24.072v-71.424L3,231.66v-35.306l131.875-80.409V19.74z" fill="'+data.iconColor+'"/></svg>';
var svgplane = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
className:"planeicon",
iconAnchor: [16, 16],
html:'<img src="'+svgplane+'" style="width:32px; height:32px; -webkit-transform:rotate('+dir+'deg); -moz-transform:rotate('+dir+'deg);"/>'
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
2021-10-18 00:12:05 +08:00
}
else if (data.icon === "smallplane") {
data.iconColor = data.iconColor ?? "black";
2021-10-18 00:12:05 +08:00
icon = '<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="20" height="20">';
icon += '<path d="M15.388 4.781c.068.068.061.154-.171.656-.028.06-.18.277-.18.277s.102.113.13.14c.054.055.078.175.056.27-.068.295-.89 1.47-1.35 1.93-.285.286-.432.481-.422.56.009.068.117.356.24.64.219.5.3.599 2.762 3.339 1.95 2.169 2.546 2.87 2.582 3.028.098.439-.282.847-1.264 1.356l-.507.263-7.389-5.29-4.43 3.365.102.18c.056.099.519.676 1.029 1.283.51.607.933 1.161.94 1.232.026.284-1.111 1.177-1.282 1.006-.27-.27-1.399-1.131-1.494-1.14-.068-.007-1.04-.747-1.37-1.077-.329-.33-1.07-1.301-1.076-1.37-.01-.094-.871-1.224-1.14-1.493-.171-.171.722-1.308 1.006-1.282.07.007.625.43 1.231.94.607.51 1.185.973 1.283 1.029l.18.101 3.365-4.43-5.29-7.388.263-.507c.51-.982.918-1.362 1.357-1.264.158.035.859.632 3.028 2.581 2.74 2.462 2.838 2.544 3.339 2.762.284.124.572.232.639.24.08.01.274-.136.56-.422.46-.46 1.635-1.282 1.93-1.35.095-.022.216.003.27.057.028.028.139.129.139.129s.217-.153.277-.18c.502-.233.59-.238.657-.17z" fill="'+data.iconColor+'"/></svg>';
var svgplane = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
className:"planeicon",
iconAnchor: [16, 16],
html:'<img src="'+svgplane+'" style="width:32px; height:32px; -webkit-transform:rotate('+(dir - 45)+'deg); -moz-transform:rotate('+(dir - 45)+'deg);"/>'
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
}
2020-04-02 00:53:29 +08:00
else if (data.icon === "bus") {
dir = dir - 90;
2020-04-02 00:53:29 +08:00
var sc = 1;
if (dir < -90 || dir >= 90) { sc = -1; }
data.iconColor = data.iconColor ?? "#910000";
2020-04-02 00:53:29 +08:00
var p = "m595.5 97.332-30.898-68.199c-11.141-24.223-35.344-39.762-62.004-39.801h-443.3c-32.738 0.035157-59.266 26.562-59.301 59.305v148.2c0 17.949 14.551 32.5 32.5 32.5h48.5c4.7344 23.309 25.219 40.051 49 40.051s44.266-16.742 49-40.051h242c4.7344 23.309 25.219 40.051 49 40.051s44.266-16.742 49-40.051h53.203c12.348-0.003906 23.219-8.1484 26.699-20 0.72266-2.5391 1.0898-5.1602 1.0977-7.7969v-83.5c-0.003906-7.1445-1.5391-14.203-4.5-20.703zm-545.5 12c-5.5234 0-10-4.4766-10-10v-80c0-5.5195 4.4766-10 10-10h70c5.5234 0 10 4.4805 10 10v80c0 5.5234-4.4766 10-10 10zm80 140c-16.566 0-30-13.43-30-30 0-16.566 13.434-30 30-30s30 13.434 30 30c-0.046875 16.551-13.453 29.953-30 30zm110-150c0 5.5234-4.4766 10-10 10h-70c-5.5234 0-10-4.4766-10-10v-80c0-5.5195 4.4766-10 10-10h70c5.5234 0 10 4.4805 10 10zm110 0c0 5.5234-4.4766 10-10 10h-70c-5.5234 0-10-4.4766-10-10v-80c0-5.5195 4.4766-10 10-10h70c5.5234 0 10 4.4805 10 10zm30 10c-5.5234 0-10-4.4766-10-10v-80c0-5.5195 4.4766-10 10-10h70c5.5234 0 10 4.4805 10 10v80c0 5.5234-4.4766 10-10 10zm90 140c-16.566 0-30-13.43-30-30 0-16.566 13.434-30 30-30s30 13.434 30 30c-0.046875 16.551-13.453 29.953-30 30zm19.199-140c-5.1836-0.46094-9.168-4.793-9.1992-10v-80.086c0-5.4727 4.4375-9.9141 9.9141-9.9141h12.684c18.824 0.050781 35.914 11.012 43.805 28.102l30.898 68.199c1.6133 3.5547 2.5 7.3984 2.6016 11.297z";
icon = '<svg width="640pt" height="640pt" viewBox="-20 -180 640 640" xmlns="http://www.w3.org/2000/svg">';
icon += '<path d="'+p+'" fill="'+data.iconColor+'"/></svg>';
var svgbus = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
className:"busicon",
iconAnchor: [16, 16],
html:'<img src="'+svgbus+'" style="width:32px; height:32px; -webkit-transform:scaleY('+sc+') rotate('+dir*sc+'deg); -moz-transform:scaleY('+sc+') rotate('+dir*sc+'deg);"/>'
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
}
else if (data.icon === "helicopter") {
data.iconColor = data.iconColor ?? "black";
if (data.hasOwnProperty("squawk")) {
2019-10-16 18:26:00 +08:00
if (data.squawk == 7500 || data.squawk == 7600 || data.squawk == 7700) {
data.iconColor = "red";
2019-10-16 18:26:00 +08:00
}
}
icon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="314" height="314" viewBox="0 0 314.5 314.5">';
icon += '<path d="M268.8 3c-3.1-3.1-8.3-2.9-11.7 0.5L204.9 55.7C198.5 23.3 180.8 0 159.9 0c-21.9 0-40.3 25.5-45.7 60.2L57.4 3.5c-3.4-3.4-8.6-3.6-11.7-0.5 -3.1 3.1-2.9 8.4 0.5 11.7l66.3 66.3c0 0.2 0 0.4 0 0.6 0 20.9 4.6 39.9 12.1 54.4l-78.4 78.4c-3.4 3.4-3.6 8.6-0.5 11.7 3.1 3.1 8.3 2.9 11.7-0.5l76.1-76.1c3.2 3.7 6.7 6.7 10.4 8.9v105.8l-47.7 32.2v18l50.2-22.3h26.9l50.2 22.3v-18L175.8 264.2v-105.8c2.7-1.7 5.4-3.8 7.8-6.2l73.4 73.4c3.4 3.4 8.6 3.6 11.7 0.5 3.1-3.1 2.9-8.3-0.5-11.7l-74.9-74.9c8.6-14.8 14-35.2 14-57.8 0-1.9-0.1-3.8-0.2-5.8l61.2-61.2C271.7 11.3 271.9 6.1 268.8 3z" fill="'+data.iconColor+'"/></svg>';
var svgheli = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
className:"heliicon",
iconAnchor: [16, 16],
html:'<img src="'+svgheli+'" style="width:32px; height:32px; -webkit-transform:rotate('+dir+'deg); -moz-transform:rotate('+dir+'deg);"/>'
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
}
else if (data.icon === "uav") {
data.iconColor = data.iconColor || "black";
if (data.hasOwnProperty("squawk")) {
2019-10-16 18:26:00 +08:00
if (data.squawk == 7500 || data.squawk == 7600 || data.squawk == 7700) {
data.iconColor = "red";
2019-10-16 18:26:00 +08:00
}
}
icon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">';
icon+= '<path d="M62 82h-8V64h36c0-5-4-9-9-9H54v-8c0-3 4-5 4-11.1 0-4.4-3.6-8-8-8-4.4 0-8 3.6-8 8 0 5.1 4 8.1 4 11.1V55h-27c-5 0-9 4-9 9h36v18H38c-2.4 0-5 2.3-5 5L50 92l17-5C67 84.3 64.4 82 62 82z" fill="'+data.iconColor+'"/></svg>';
var svguav = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
className:"uavicon",
iconAnchor: [16, 16],
html:'<img src="'+svguav+'" style="width:32px; height:32px; -webkit-transform:rotate('+dir+'deg); -moz-transform:rotate('+dir+'deg);"/>',
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
}
else if (data.icon === "car") {
data.iconColor = data.iconColor || "black";
icon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="47px" height="47px" viewBox="0 0 47 47">';
icon += '<path d="M29.395,0H17.636c-3.117,0-5.643,3.467-5.643,6.584v34.804c0,3.116,2.526,5.644,5.643,5.644h11.759 c3.116,0,5.644-2.527,5.644-5.644V6.584C35.037,3.467,32.511,0,29.395,0z M34.05,14.188v11.665l-2.729,0.351v-4.806L34.05,14.188z M32.618,10.773c-1.016,3.9-2.219,8.51-2.219,8.51H16.631l-2.222-8.51C14.41,10.773,23.293,7.755,32.618,10.773z M15.741,21.713 v4.492l-2.73-0.349V14.502L15.741,21.713z M13.011,37.938V27.579l2.73,0.343v8.196L13.011,37.938z M14.568,40.882l2.218-3.336 h13.771l2.219,3.336H14.568z M31.321,35.805v-7.872l2.729-0.355v10.048L31.321,35.805z" fill="'+data.iconColor+'"/></svg>';
var svgcar = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
className:"caricon",
iconAnchor: [16, 16],
html:'<img src="'+svgcar+'" style="width:32px; height:32px; -webkit-transform:rotate('+dir+'deg); -moz-transform:rotate('+dir+'deg);"/>',
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
}
else if (data.icon === "arrow") {
data.iconColor = data.iconColor || "black";
icon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="32px" viewBox="0 0 32 32">';
icon += '<path d="m16.2 0.6l-10.9 31 10.7-11.1 10.5 11.1 -10.3-31z" fill="'+data.iconColor+'"/></svg>';
var svgarrow = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
className:"arrowicon",
iconAnchor: [16, 16],
html:"'<img src='"+svgarrow+"' style='width:32px; height:32px; -webkit-transform:translate(0px,-16px) rotate("+dir+"deg); -moz-transform:translate(0px,-16px) rotate("+dir+"deg);'/>",
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
}
else if (data.icon === "wind") {
data.iconColor = data.iconColor || "black";
icon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="32px" viewBox="0 0 32 32">';
icon += '<path d="M16.7 31.7l7-6.9c0.4-0.4 0.4-1 0-1.4 -0.4-0.4-1-0.4-1.4 0l-5.3 5.2V17.3l6.7-6.6c0.2-0.2 0.3-0.5 0.3-0.7v-9c0-0.9-1.1-1.3-1.7-0.7l-6.3 6.2L9.7 0.3C9.1-0.3 8 0.1 8 1.1v8.8c0 0.3 0.1 0.6 0.3 0.8l6.7 6.6v11.3l-5.3-5.2c-0.4-0.4-1-0.4-1.4 0 -0.4 0.4-0.4 1 0 1.4l7 6.9c0.2 0.2 0.5 0.3 0.7 0.3C16.2 32 16.5 31.9 16.7 31.7zM10 9.6V3.4l5 4.9v6.2L10 9.6zM17 8.3l5-4.9v6.2l-5 4.9V8.3z" fill="'+data.iconColor+'"/></svg>';
var svgwind = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
className:"windicon",
iconAnchor: [16, 16],
html:'<img src="'+svgwind+'" style="width:32px; height:32px; -webkit-transform:rotate('+dir+'deg); -moz-transform:rotate('+dir+'deg);"/>',
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
}
else if (data.icon === "satellite") {
data.iconColor = data.iconColor || "black";
icon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">';
icon += '<polygon points="38.17 39.4 45.24 32.33 43.34 27.92 24.21 8.78 14.59 18.4 33.72 37.53" fill="'+data.iconColor+'"/>';
icon += '<path d="M69.22 44.57L54.38 29.73c-1.1-1.1-2.91-1.1-4.01 0L35.53 44.57c-1.1 1.1-1.1 2.91 0 4.01l14.84 14.84c1.1 1.1 2.91 1.1 4.01 0l14.84-14.84C70.32 47.47 70.32 45.67 69.22 44.57z" fill="'+data.iconColor+'"/>';
icon += '<polygon points="71.04 55.61 66.58 53.75 59.52 60.82 61.42 65.23 80.55 84.36 90.17 74.75" fill="'+data.iconColor+'"/>';
icon += '<path d="M28.08 55.26l-6.05 0.59C23.11 68.13 32.78 77.94 45 79.22l0.59-6.05C36.26 72.15 28.89 64.66 28.08 55.26z" fill="'+data.iconColor+'"/>';
icon += '<path d="M15.88 56.54L9.83 57.13c1.67 18.06 16.03 32.43 34.08 34.09l0.59-6.04C29.34 83.76 17.29 71.71 15.88 56.54z" fill="'+data.iconColor+'"/>';
icon += '</svg>';
var svgsat = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
className:"satelliteicon",
iconAnchor: [16, 16],
html:'<img src="'+svgsat+'" style="width:32px; height:32px;"/>',
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
}
else if ((data.icon === "iss") || (data.icon === "ISS")) {
data.iconColor = data.iconColor || "black";
icon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48">';
icon += '<path id="iss" d="m4.55 30.97l6.85-12.68 0.59 0.32 -6.85 12.68 4.27 2.3 6.85-12.68 0.49 0.27 -0.81 1.5c-0.26 0.48-0.07 1.1 0.44 1.37l5.09 2.75c0.5 0.27 1.12 0.1 1.38-0.39l0.81-1.5 0.72 0.39 -1.49 2.75c-0.41 0.76-0.38 1.58 0.08 1.82l4.61 2.49c0.45 0.24 1.15-0.18 1.56-0.94l1.49-2.75 0.69 0.37 -6.85 12.68 4.26 2.3 6.85-12.68 0.59 0.32 -6.85 12.69 4.26 2.3 14.46-26.78 -4.26-2.3 -6.88 12.74 -0.59-0.32 6.88-12.74 -4.26-2.3 -6.88 12.74 -0.69-0.37 1.49-2.75c0.41-0.76 ';
icon += '0.38-1.58-0.08-1.82l-1.4-0.75 0.5-0.92c1.02 0.17 2.09-0.32 2.62-1.3 0.67-1.23 0.22-2.76-0.99-3.42 -1.21-0.65-2.74-0.19-3.4 1.05 -0.53 0.98-0.35 2.14 0.35 2.9l-0.5 0.92 -1.8-0.97c-0.45-0.24-1.15 0.17-1.57 0.94l-1.49 2.75 -0.72-0.39 0.81-1.5c0.26-0.48 0.07-1.1-0.44-1.36l-5.09-2.75c-0.5-0.27-1.12-0.1-1.38 0.39l-0.81 1.5 -0.49-0.27 6.88-12.74 -4.26-2.3 -6.88 12.74 -0.59-0.32 6.88-12.74 -4.26-2.3 -14.46 26.78 4.26 2.3zm14.26-11.72c0.2-0.37 0.68-0.51 1.06-0.3l3.93 ';
icon += '2.12c0.39 0.21 0.54 0.68 0.34 1.05l-1.81 3.35c-0.2 0.37-0.68 0.51-1.06 0.3l-3.93-2.12c-0.38-0.21-0.53-0.68-0.33-1.05l1.81-3.35zm12.01-1.46c0.45-0.83 1.47-1.14 2.28-0.7 0.81 0.44 1.11 1.46 0.66 2.29 -0.44 0.83-1.47 1.14-2.28 0.7 -0.81-0.44-1.11-1.46-0.66-2.29zm-3.78 4.26c0.35-0.66 0.93-1.04 1.28-0.85l3.57 1.93c0.35 0.19 0.35 0.88-0.01 1.53l-3.19 5.91c-0.35 0.66-0.93 1.04-1.28 0.85l-3.56-1.92c-0.35-0.19-0.35-0.88 0.01-1.53l3.19-5.91zm0.19 7.49c-0.26 0.49-0.87 ';
icon += '0.67-1.36 0.41 -0.49-0.26-0.67-0.87-0.41-1.36 0.27-0.49 0.87-0.67 1.36-0.4 0.49 0.26 0.67 0.87 0.41 1.36zm-7.46-6.31c-0.26 0.49-0.87 0.67-1.36 0.41s-0.67-0.87-0.41-1.36c0.27-0.49 0.87-0.67 1.36-0.4s0.67 0.87 0.41 1.36zm2.32 1.25c-0.26 0.49-0.87 0.67-1.36 0.41 -0.49-0.26-0.67-0.87-0.41-1.36 0.27-0.49 0.87-0.67 1.36-0.41 0.49 0.26 0.67 0.87 0.41 1.36z" fill="'+data.iconColor+'"/>';
icon += '</svg>';
var svgiss = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
className:"issicon",
iconAnchor: [25, 25],
html:'<img src="'+svgiss+'" style="width:50px; height:50px;"/>',
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
}
2021-04-13 00:42:47 +08:00
else if (data.icon === "mayflower") {
data.iconColor = data.iconColor || "#910000";
2021-04-13 00:42:47 +08:00
icon = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="60" viewBox="0 0 4 10" aria-hidden="true" stroke-width="1.5">';
icon += '<path d="M2 .2L1.5 5l-1 .5L.2 4S.09 5.551.1 6.25c.01.759.1 2.25.1 2.25L.5 7l1 .3.3 2.5h.4l.3-2.5 1-.3.3 1.5s.058-1.518.1-2.25C3.945 5.455 3.8 4 3.8 4l-.3 1.5-1-.5z" fill="'+data.iconColor+'"/></svg>';
var svgmay = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
className:"mayflowericon",
iconAnchor: [12, 24],
html:'<img src="'+svgmay+'" style="width:24px; height:48px; -webkit-transform:rotate('+dir+'deg); -moz-transform:rotate('+dir+'deg);"/>',
2021-04-13 00:42:47 +08:00
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
}
else if (data.icon === "locate") {
data.iconColor = data.iconColor || "cyan";
icon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="468px" height="468px" viewBox="0 0 468 468">';
icon += '<polygon points="32 32 104 32 104 0 0 0 0 104 32 104" fill="'+data.iconColor+'"/>';
icon += '<polygon points="468 0 364 0 364 32 436 32 436 104 468 104" fill="'+data.iconColor+'"/>';
icon += '<polygon points="0 468 104 468 104 436 32 436 32 364 0 364" fill="'+data.iconColor+'"/>';
icon += '<polygon points="436 436 364 436 364 468 468 468 468 364 436 364" fill="'+data.iconColor+'"/>';
//icon += '<circle cx="234" cy="234" r="22" fill="'+data.iconColor+'"/>';
icon += '</svg>';
var svglocate = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
className:"locateicon",
iconAnchor: [16, 16],
html:'<img src="'+svglocate+'" style="width:32px; height:32px;"/>',
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
labelOffset = [12,-4];
}
else if (data.icon === "friend") {
marker = L.marker(ll, { icon: L.divIcon({ className: 'circle f', iconSize: [20, 12] }), title: data.name, draggable:drag });
}
else if (data.icon === "hostile") {
marker = L.marker(ll, { icon: L.divIcon({ className: 'circle h', iconSize: [16, 16] }), title: data.name, draggable:drag });
}
else if (data.icon === "neutral") {
marker = L.marker(ll, { icon: L.divIcon({ className: 'circle n', iconSize: [16, 16] }), title: data.name, draggable:drag });
}
else if (data.icon === "unknown") {
marker = L.marker(ll, { icon: L.divIcon({ className: 'circle', iconSize: [16, 16] }), title: data.name, draggable:drag });
}
else if (data.icon === "danger") {
marker = L.marker(ll, { icon: L.divIcon({ className: 'up-triangle' }), title: data.name, draggable:drag });
}
else if (data.icon === "earthquake") {
marker = L.marker(ll, { icon: L.divIcon({ className: 'circle e', iconSize: [data.mag*5, data.mag*5] }), title: data.name, draggable:drag });
}
else if (data.icon.match(/^:.*:$/g)) {
var em = emojify(data.icon);
var col = data.iconColor ?? "#910000";
myMarker = L.divIcon({
className:"emicon",
html: '<center><span style="font-size:2em; color:'+col+'">'+em+'</span></center>',
iconSize: [32, 32]
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
labelOffset = [12,-4];
}
else if (data.icon.match(/^https?:.*$|^\//)) {
var sz = data.iconSize ?? 32;
myMarker = L.icon({
iconUrl: data.icon,
iconSize: [sz, sz],
iconAnchor: [sz/2, sz/2],
popupAnchor: [0, -sz/2]
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag, rotationAngle:dir, rotationOrigin:"center"});
2020-07-02 16:03:35 +08:00
labelOffset = [sz/2-4,-4];
2020-07-10 05:09:57 +08:00
delete data.iconSize;
}
else if (data.icon.substr(0,3) === "fa-") {
var col = data.iconColor ?? "#910000";
var imod = "";
if (data.icon.indexOf(" ") === -1) { imod = "fa-2x "; }
myMarker = L.divIcon({
className:"faicon",
html: '<center><i class="fa fa-fw '+imod+data.icon+'" style="color:'+col+'"></i></center>',
iconSize: [32, 32],
iconAnchor: [16, 12],
popupAnchor: [0, -16]
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
labelOffset = [8,-8];
}
else if (data.icon.substr(0,3) === "wi-") {
var col = data.iconColor ?? "#910000";
var imod = "";
if (data.icon.indexOf(" ") === -1) { imod = "wi-2x "; }
myMarker = L.divIcon({
className:"wiicon",
html: '<center><i class="wi wi-fw '+imod+data.icon+'" style="color:'+col+'"></i></center>',
iconSize: [32, 32],
iconAnchor: [16, 16],
popupAnchor: [0, -16]
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
labelOffset = [16,-16];
}
2019-05-23 15:41:04 +08:00
else {
myMarker = L.VectorMarkers.icon({
icon: data.icon ?? "circle",
markerColor: (data.iconColor ?? "#910000"),
2019-05-23 15:41:04 +08:00
prefix: 'fa',
iconColor: 'white'
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
labelOffset = [6,-6];
}
}
else if (data.hasOwnProperty("SIDC")) {
// "SIDC":"SFGPU------E***","name":"1.C2 komp","fullname":"1.C2 komp/FTS/INSS"
2021-07-02 20:15:03 +08:00
myMarker = new ms.Symbol( data.SIDC.toUpperCase(), { uniqueDesignation:unescape(encodeURIComponent(data.name)) });
// Now that we have a symbol we can ask for the echelon and set the symbol size
var opts = data.options || {};
2021-06-24 23:14:58 +08:00
var sz = 25;
if (myMarker.hasOwnProperty("getProperties") && myMarker.getProperties().hasOwnProperty("echelon")) {
sz = iconSz[myMarker.getProperties().echelon];
}
opts.size = opts.size || sz;
2021-06-24 23:14:58 +08:00
opts.size = opts.size * (opts.scale || 1);
2021-07-02 20:15:03 +08:00
// escape out any isocodes eg flag symbols
2021-11-19 19:19:44 +08:00
var optfields = ["additionalInformation","higherFormation","specialHeadquarters","staffComments","type","uniqueDesignation","speed","country"];
//const regex = /\p{Extended_Pictographic}/ug;
const regex = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/gi;
2021-07-02 20:15:03 +08:00
optfields.forEach(function (item) {
if (opts.hasOwnProperty(item) && regex.test(opts[item])) {
opts[item] = unescape(encodeURIComponent(opts[item]));
}
2021-07-02 20:15:03 +08:00
});
myMarker = myMarker.setOptions(opts);
var myicon = L.icon({
iconUrl: myMarker.toDataURL(),
iconAnchor: [myMarker.getAnchor().x, myMarker.getAnchor().y],
className: "natoicon",
});
marker = L.marker(ll, { title:data.name, icon:myicon, draggable:drag });
edgeAware();
}
else {
myMarker = L.VectorMarkers.icon({
icon: "circle",
markerColor: (data.iconColor ?? "#910000"),
prefix: 'fa',
iconColor: 'white'
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
labelOffset = [6,-6];
}
marker.name = data.name;
// var createLabelIcon = function(labelText) {
// return L.marker(new L.LatLng(51.05, -1.35), {icon:L.divIcon({ html:labelText })});
// }
// send new position at end of move event if point is draggable
if (data.draggable === true) {
if (data.icon) { marker.icon = data.icon; }
if (data.iconColor) { marker.iconColor = data.iconColor; }
if (data.SIDC) { marker.SIDC = data.SIDC.toUpperCase(); }
var oldll;
marker.on('dragstart', function (e) {
oldll = marker.getLatLng();
var ola = parseFloat(oldll.lat.toFixed(6));
var olo = parseFloat(oldll.lng.toFixed(6));
oldll = {lat:ola, lon:olo};
});
marker.on('dragend', function (e) {
var l = marker.getLatLng().toString().replace('LatLng(','lat, lon : ').replace(')','')
marker.setPopupContent(marker.getPopup().getContent().split("lat, lon")[0] + l);
var b = marker.getPopup().getContent().split("heading : ");
if (b.length === 2) { b = parseFloat(b[1].split("<br")[0]); }
else { b = undefined; }
2021-10-22 05:57:51 +08:00
var fb = allData[marker.name];
fb.action = "move";
fb.lat = parseFloat(marker.getLatLng().lat.toFixed(6));
fb.lon = parseFloat(marker.getLatLng().lng.toFixed(6));
fb.from = oldll;
ws.send(JSON.stringify(fb));
});
}
// tidy up altitude
if (data.hasOwnProperty("alt")) {
var reft = new RegExp('feet|ft','i');
var refm = new RegExp('metres|m','i');
if ( reft.test(""+data.alt) ) {
2021-10-23 06:40:32 +08:00
data.alt = +(parseFloat(data.alt)).toFixed(2) + " ft";
}
else if ( refm.test(""+data.alt) ) {
2021-10-23 06:40:32 +08:00
data.alt = +(parseFloat(data.alt)).toFixed(2) + " m";
}
else {
2021-10-23 06:40:32 +08:00
data.alt = +(parseFloat(data.alt)).toFixed(2);
}
}
// remove icon from list of properties, then add all others to popup
if (data.hasOwnProperty("SIDC") && data.hasOwnProperty("options")) { delete data.options; }
if (data.hasOwnProperty("icon")) { delete data.icon; }
if (data.hasOwnProperty("iconColor")) { delete data.iconColor; }
if (data.hasOwnProperty("photourl")) {
words += "<img src=\"" + data.photourl + "\" style=\"max-width:100%; max-height:250px; margin-top:10px;\"><br/>";
delete data.photourl;
}
if (data.hasOwnProperty("photoUrl")) {
words += "<img src=\"" + data.photoUrl + "\" style=\"max-width:100%; max-height:250px; margin-top:10px;\"><br/>";
delete data.photoUrl;
}
if (data.hasOwnProperty("videoUrl")) {
2021-10-25 06:36:39 +08:00
words += '<video controls muted autoplay width="320" height="240"><source src="'+data.videoUrl+'" type="video/mp4">Your browser does not support the video tag.</video><br/>';
delete data.videoUrl;
}
if (data.hasOwnProperty("ttl")) { // save expiry time for this marker
if (data.ttl != 0) {
marker.ts = parseInt(Date.now()/1000) + Number(data.ttl);
}
delete data.ttl;
}
else if (maxage != 0) {
marker.ts = parseInt(Date.now()/1000) + Number(maxage);
}
if (data.hasOwnProperty("weblink")) {
if (!Array.isArray(data.weblink) || !data.weblink.length) {
if (typeof data.weblink === "string") {
words += "<b><a href='"+ data.weblink + "' target='_new'>more information...</a></b><br/>";
} else {
var tgt = data.weblink.target || "_new";
words += "<b><a href='"+ data.weblink.url + "' target='"+ tgt + "'>" + data.weblink.name + "</a></b><br/>";
}
}
else {
data.weblink.forEach(function(weblink){
if (typeof weblink === "string") {
words += "<b><a href='"+ weblink + "' target='_new'>more information...</a></b><br/>";
}
else {
var tgt = weblink.target || "_new";
words += "<b><a href='"+ weblink.url + "' target='"+ tgt + "'>" + weblink.name + "</a></b><br/>";
}
});
}
delete data.weblink;
}
var p;
if (data.hasOwnProperty("popped") && (data.popped === true)) {
p = true;
delete data.popped;
}
if (data.hasOwnProperty("popped") && (data.popped === false)) {
marker.closePopup();
p = false;
delete data.popped;
}
// If .label then use that rather than name tooltip
if (data.label) {
if (typeof data.label === "boolean" && data.label === true) {
marker.bindTooltip(data.name, { permanent:true, direction:"right", offset:labelOffset });
}
else if (typeof data.label === "string" && data.label.length > 0) {
marker.bindTooltip(data.label, { permanent:true, direction:"right", offset:labelOffset });
}
delete marker.options.title;
delete data.label;
}
// otherwise check for .tooltip then use that rather than name tooltip
else if (data.tooltip) {
if (typeof data.tooltip === "string" && data.tooltip.length > 0) {
marker.bindTooltip(data.tooltip, { direction:"bottom", offset:[0,4] });
delete marker.options.title;
delete data.tooltip;
}
}
marker = rightmenu(marker);
// Add any remaining properties to the info box
var llc = data.lineColor || data.color;
delete data.lat;
delete data.lon;
2019-11-25 21:48:43 +08:00
if (data.arc) { delete data.arc; }
if (data.layer) { delete data.layer; }
if (data.lineColor) { delete data.lineColor; }
if (data.color) { delete data.color; }
if (data.weight) { delete data.weight; }
if (data.tracklength) { delete data.tracklength; }
if (data.dashArray) { delete data.dashArray; }
if (data.fill) { delete data.fill; }
if (data.draggable) { delete data.draggable; }
2021-11-27 00:35:14 +08:00
//if (!isNaN(data.speed)) { data.speed = data.speed.toFixed(2); }
if (data.hasOwnProperty("fillColor")) { delete data.fillColor; }
if (data.hasOwnProperty("radius")) { delete data.radius; }
2022-01-25 17:46:19 +08:00
if (data.hasOwnProperty("greatcircle")) { delete data.greatcircle; }
for (var i in data) {
if ((i != "name") && (i != "length") && (i != "clickable")) {
if (typeof data[i] === "object") {
words += i +" : "+JSON.stringify(data[i])+"<br/>";
} else {
words += i +" : "+data[i]+"<br/>";
}
}
}
if (data.popup) { words = data.popup; }
else { words = words + marker.getLatLng().toString().replace('LatLng(','lat, lon : ').replace(')',''); }
words = "<b>"+data.name+"</b><br/>" + words; //"<button style=\"border-radius:4px; float:right; background-color:lightgrey;\" onclick='popped=false;popmark.closePopup();'>X</button><br/>" + words;
var wopt = {autoClose:false, closeButton:true, closeOnClick:false, minWidth:200};
2021-05-26 20:34:34 +08:00
if (words.indexOf('<video ') >=0 || words.indexOf('<img ') >=0 ) { wopt.maxWidth="640"; }
if (!data.hasOwnProperty("clickable") && data.clickable != false) {
marker.bindPopup(words, wopt);
marker._popup.dname = data.name;
}
marker.lay = lay; // and the layer it is on
// marker.on('click', function(e) {
// //ws.send(JSON.stringify({action:"click",name:marker.name,layer:marker.lay,icon:marker.icon,iconColor:marker.iconColor,SIDC:marker.SIDC,draggable:true,lat:parseFloat(marker.getLatLng().lat.toFixed(6)),lon:parseFloat(marker.getLatLng().lng.toFixed(6))}));
// var fb = allData[marker.name];
// fb.action = "click";
// ws.send(JSON.stringify(fb));
// });
2021-12-22 01:22:23 +08:00
if (heat && ((data.addtoheatmap != false) || (!data.hasOwnProperty("addtoheatmap")))) { // Added to give ability to control if points from active layer contribute to heatmap
if (heatAll || map.hasLayer(layers[lay])) { heat.addLatLng(lli); }
}
markers[data.name] = marker;
layers[lay].addLayer(marker);
var track;
if (data.track !== undefined) { track = data.track; }
else if (data.hdg !== undefined) { track = data.hdg; }
else if (data.heading !== undefined) { track = data.heading; }
else if (data.bearing !== undefined) { track = data.bearing; }
if (track != undefined) { // if there is a heading
if (data.speed != null && data.length === undefined) { // and a speed - lets convert to a leader length
2021-05-26 20:34:34 +08:00
data.length = parseFloat(data.speed || "0") * 60;
var re1 = new RegExp('kn|knot|kt','i');
var re2 = new RegExp('kph|kmh','i');
var re3 = new RegExp('mph','i');
if ( re1.test(""+data.speed) ) { data.length = data.length * 0.514444; }
else if ( re2.test(""+data.speed) ) { data.length = data.length * 0.44704; }
else if ( re3.test(""+data.speed) ) { data.length = data.length * 0.277778; }
}
if (data.length !== undefined) {
if (polygons[data.name] != null && !polygons[data.name].hasOwnProperty("_layers")) {
map.removeLayer(polygons[data.name]);
2019-11-25 21:48:43 +08:00
}
if (polygons[data.name] != null && polygons[data.name].hasOwnProperty("name") ) {
delete(layers[lay]._layers[polygons[data.name]._leaflet_id]);
}
var x = ll.lng * 1; // X coordinate
var y = ll.lat * 1; // Y coordinate
var ll1 = ll;
var angle = parseFloat(track);
var lengthAsDegrees = parseFloat(data.length || "0") / 110540; // metres in a degree..ish
var polygon = null;
if (data.accuracy != null) {
data.accuracy = Number(data.accuracy);
2019-11-25 21:48:43 +08:00
var y2 = y + Math.sin((90-angle+data.accuracy)/180*Math.PI)*lengthAsDegrees;
var x2 = x + Math.cos((90-angle+data.accuracy)/180*Math.PI)*lengthAsDegrees/Math.cos(y/180*Math.PI);
var ll2 = new L.LatLng(y2,x2);
2019-11-25 21:48:43 +08:00
var y3 = y + Math.sin((90-angle-data.accuracy)/180*Math.PI)*lengthAsDegrees;
var x3 = x + Math.cos((90-angle-data.accuracy)/180*Math.PI)*lengthAsDegrees/Math.cos(y/180*Math.PI);
var ll3 = new L.LatLng(y3,x3);
polygon = L.polygon([ ll1, ll2, ll3 ], {weight:2, color:llc||'#900', fillOpacity:0.06, clickable:false});
} else {
2019-11-25 21:48:43 +08:00
var ya = y + Math.sin((90-angle)/180*Math.PI)*lengthAsDegrees;
var xa = x + Math.cos((90-angle)/180*Math.PI)*lengthAsDegrees/Math.cos(y/180*Math.PI);
var lla = new L.LatLng(ya,xa);
polygon = L.polygon([ ll1, lla ], {weight:2, color:llc||'#900', clickable:false});
}
if (typeof layers[lay].getVisibleParent === 'function') {
var vis = layers[lay].getVisibleParent(marker);
if ((polygon !== null) && (vis !== null) && (!vis.hasOwnProperty("lay"))) {
polygon.setStyle({opacity:0});
}
}
polygon.name = data.name;
2019-11-25 21:48:43 +08:00
if (polygons[data.name] != null && polygons[data.name].hasOwnProperty("_layers")) {
polygons[data.name].addLayer(polygon);
2019-11-25 21:48:43 +08:00
}
else {
polygons[data.name] = polygon;
2019-11-25 21:48:43 +08:00
}
polygons[data.name].lay = lay;
layers[lay].addLayer(polygon);
}
}
if (panit === true) { map.setView(ll,map.getZoom()); }
if (p === true) { marker.openPopup(); }
}
// handle any incoming COMMANDS to control the map remotely
function doCommand(cmd) {
// console.log("COMMAND",cmd);
if (cmd.init && cmd.hasOwnProperty("maplist")) {
2022-01-10 23:48:26 +08:00
//basemaps = {};
addBaseMaps(cmd.maplist,cmd.layer);
}
if (cmd.init && cmd.hasOwnProperty("overlist")) {
2021-12-18 06:56:58 +08:00
overlays = [];
addOverlays(cmd.overlist);
}
2020-11-17 22:45:52 +08:00
if (cmd.hasOwnProperty("toptitle")) {
if (!inIframe ) {
document.title = cmd.toptitle;
document.getElementById("topwords").innerText = cmd.toptitle;
}
}
if (cmd.hasOwnProperty("toplogo")) {
if (!inIframe ) {
document.getElementById("toplogo").src = cmd.toplogo;
document.getElementById("toplink").setAttribute("style", "pointer-events:none");
}
}
if (cmd.hasOwnProperty("clear")) {
doTidyUp(cmd.clear);
}
if (cmd.hasOwnProperty("panit")) {
if (cmd.panit == true || cmd.panit === "true") { panit = true; }
else { panit = false; }
document.getElementById("panit").checked = panit;
}
if (cmd.hasOwnProperty("hiderightclick")) {
if (cmd.hiderightclick == "true" || cmd.hiderightclick == true) { hiderightclick = true; }
else { hiderightclick = false; }
}
if (cmd.hasOwnProperty("showmenu")) {
if ((cmd.showmenu === "hide") && (showUserMenu === true)) {
showUserMenu = false;
if (inIframe) {
if (menuButton) {
try { map.removeControl(menuButton); }
catch(e) {}
}
}
else { document.getElementById("bars").style.display="none"; }
}
else if ((cmd.showmenu === "show") && (showUserMenu === false)) {
showUserMenu = true;
if (inIframe) { map.addControl(menuButton); }
else { document.getElementById("bars").style.display="unset"; }
}
}
if (cmd.hasOwnProperty("showlayers")) {
if ((cmd.showlayers === "hide") && (showLayerMenu === true)) {
showLayerMenu = false;
if (layercontrol) { map.removeControl(layercontrol); }
}
else if ((cmd.showlayers === "show") && (showLayerMenu === false)) {
showLayerMenu = true;
layercontrol = L.control.layers(basemaps, overlays).addTo(map);
}
}
if (cmd.hasOwnProperty("grid")) {
if (cmd.grid.hasOwnProperty("showgrid")) {
var changed = false;
if ((cmd.grid.showgrid == "true" || cmd.grid.showgrid == true ) && !showGrid) { changed = true; }
if ((cmd.grid.showgrid == "false" || cmd.grid.showgrid == false ) && showGrid) { changed = true; }
if (changed) {
showGrid = !showGrid;
2022-09-23 20:41:24 +08:00
if (showGrid) { Lgrid.addTo(map);}
else { Lgrid.removeFrom(map);}
}
}
if (cmd.grid.hasOwnProperty("opt")) {
Lgrid.initialize(cmd.grid.opt);
if (showGrid) {
Lgrid.removeFrom(map);
Lgrid.addTo(map);
}
}
}
2022-09-23 20:41:24 +08:00
if (cmd.hasOwnProperty("ruler")) {
if (cmd.ruler.hasOwnProperty("showruler")) {
var changed = false;
if ((cmd.ruler.showruler == "true" || cmd.ruler.showruler == true ) && !showRuler) { changed = true; }
if ((cmd.ruler.showruler == "false" || cmd.ruler.showruler == false ) && showRuler) { changed = true; }
if (changed) {
showRuler = !showRuler;
if (showRuler) { rulerButton.addTo(map); }
else { rulerButton.remove(); }
}
}
}
if (cmd.hasOwnProperty("button")) {
2022-06-10 21:05:30 +08:00
if (!Array.isArray(cmd.button)) { cmd.button = [cmd.button]; }
2022-06-09 02:18:11 +08:00
cmd.button.forEach(function(b) {
if (b.icon) {
if (!buttons[b.name]) {
buttons[b.name] = L.easyButton( b.icon, function() {
ws.send(JSON.stringify({action:"button", name:b.name}));
}, b.name, { position:b.position||'topright' }).addTo(map);
}
}
2022-06-09 02:18:11 +08:00
else {
if (buttons[b.name]) {
buttons[b.name].removeFrom(map);
delete buttons[b.name];
}
}
2022-06-09 02:18:11 +08:00
})
}
if (cmd.hasOwnProperty("trackme")) {
if (cmd.trackme === false) {
followState = false;
map.stopLocate();
if (errRing) { errRing.removeFrom(map); }
delMarker(followMode.name || "self")
if (trackMeButton !== undefined) { trackMeButton.state('track-off'); }
}
else {
followState = true;
if (cmd.trackme === true) { followMode = { accuracy:true }; }
else { followMode = cmd.trackme; }
map.locate({setView:false, watch:followState, enableHighAccuracy:true});
if (trackMeButton !== undefined) { trackMeButton.state('track-on'); }
}
}
if (cmd.hasOwnProperty("contextmenu")) {
if (typeof cmd.contextmenu === "string") {
addmenu = cmd.contextmenu;
rightmenuMap.setContent(addmenu);
}
}
2020-12-07 07:53:47 +08:00
if (cmd.hasOwnProperty("allowFileDrop")) {
if (typeof cmd.allowFileDrop === "string") {
allowFileDrop = cmd.allowFileDrop === "false" ? false : true;
}
}
if (cmd.hasOwnProperty("coords")) {
try { coords.removeFrom(map); }
catch(e) {}
var opts = {gps:false, gpsLong:false, utm:false, utmref:false, position:"bottomleft"}
if (cmd.coords == "deg") { opts.gps = true; }
if (cmd.coords == "dms") { opts.gpsLong = true; }
if (cmd.coords == "utm") { opts.utm = true; }
if (cmd.coords == "mgrs") { opts.utmref = true; }
if (cmd.coords == "qth") { opts.qth = true; }
coords.options = opts;
coords.addTo(map);
}
2019-11-13 07:56:50 +08:00
if (cmd.hasOwnProperty("legend")) {
if (typeof cmd.legend === "string" && cmd.legend.length > 0) {
if (!legend.getContainer()) { //if legend not exist create it
legend.onAdd = function() {
var div = L.DomUtil.create("div", "legend");
div.innerHTML = cmd.legend;
return div;
};
legend.addTo(map);
}
legend.getContainer().style.visibility = 'visible'; // if already exist use visibility to show/hide
legend.getContainer().innerHTML = cmd.legend; // set content of legend
}
else {
if (legend.getContainer()) {
legend.getContainer().style.visibility = 'hidden'; //if empty string and legend already created hide it
}
2019-11-13 07:56:50 +08:00
}
}
var existsalready = false;
// Add a new base map layer
if (cmd.map && cmd.map.hasOwnProperty("name") && cmd.map.name.length>0 && cmd.map.hasOwnProperty("url") && cmd.map.hasOwnProperty("opt")) {
// console.log("BASE",cmd.map);
if (basemaps.hasOwnProperty(cmd.map.name)) { existsalready = true; }
if (cmd.map.hasOwnProperty("wms")) { // special case for wms
console.log("New WMS:",cmd.map.name);
if (cmd.map.wms === "grey") {
basemaps[cmd.map.name] = L.tileLayer.graywms(cmd.map.url, cmd.map.opt);
}
else {
basemaps[cmd.map.name] = L.tileLayer.wms(cmd.map.url, cmd.map.opt);
}
}
else {
console.log("New Map:",cmd.map.name);
basemaps[cmd.map.name] = L.tileLayer(cmd.map.url, cmd.map.opt);
}
//if (!existsalready && !inIframe) {
if (!existsalready) {
layercontrol.addBaseLayer(basemaps[cmd.map.name],cmd.map.name);
}
// if new layer is only layer then show it.
if (Object.keys(basemaps).length === 1) {
baselayername = cmd.map.name;
basemaps[baselayername].addTo(map);
}
}
2020-08-03 20:16:53 +08:00
// Add or swap new minimap layer
if (cmd.map && cmd.map.hasOwnProperty("minimap")) {
if (minimap) { map.removeControl(minimap); }
if (cmd.map.minimap == false) { return; }
if (basemaps[cmd.map.minimap]) {
minimap = new L.Control.MiniMap(basemaps[cmd.map.minimap], cmd.map.opt).addTo(map);
}
else {
console.log("Invalid base layer for minimap:",cmd.map.minimap);
}
2020-08-03 20:16:53 +08:00
}
// Remove one or more map layers (base or overlay)
if (cmd.map && cmd.map.hasOwnProperty("delete")) {
if (!Array.isArray(cmd.map.delete)) { cmd.map.delete = [cmd.map.delete]; }
for (var a=0; a < cmd.map.delete.length; a++) {
if (basemaps.hasOwnProperty(cmd.map.delete[a])) { delete basemaps[cmd.map.delete[a]]; }
if (overlays.hasOwnProperty(cmd.map.delete[a])) { delete overlays[cmd.map.delete[a]]; }
}
if (showLayerMenu) {
map.removeControl(layercontrol);
layercontrol = L.control.layers(basemaps, overlays).addTo(map);
}
}
// Add a new geojson overlay layer
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("geojson") ) {
if (overlays.hasOwnProperty(cmd.map.overlay)) {
map.removeLayer(overlays[cmd.map.overlay]);
existsalready = true;
}
var opt = cmd.map.opt || { style:function(feature) {
var st = { stroke:true, weight:2, fill:true };
if (feature.hasOwnProperty("properties")) {
st.color = feature.properties.color||feature.properties.roofColor||"black";
if (feature.properties.hasOwnProperty("color")) { delete feature.properties.color; }
if (feature.properties.hasOwnProperty("roofColor")) { delete feature.properties.roofColor; }
}
if (feature.hasOwnProperty("properties") && feature.properties.hasOwnProperty('style')) {
if (feature.properties.style.hasOwnProperty('stroke')) {
st.color = feature.properties.style.stroke;
}
if (feature.properties.style.hasOwnProperty('stroke-width')) {
st.weight = feature.properties.style["stroke-width"];
}
if (feature.properties.style.hasOwnProperty('stroke-opacity')) {
st.opacity = feature.properties.style["stroke-opacity"];
}
if (feature.properties.style.hasOwnProperty('fill')) {
if (feature.properties.style.fill == "none") { st.fill = false; }
else { st.fillColor = feature.properties.style.fill; }
}
if (feature.properties.style.hasOwnProperty('fill-opacity')) {
st.fillOpacity = feature.properties.style["fill-opacity"];
}
}
delete feature.properties.style;
return st;
}};
opt.onEachFeature = function (f,l) {
var pw = '<pre>'+JSON.stringify(f.properties,null,' ').replace(/[\{\}"]/g,'')+'</pre>';
if (pw.length > 11) { l.bindPopup(pw); }
if (cmd.map.hasOwnProperty("clickable") && cmd.map.clickable === true) {
l.on('click', function (e) {
ws.send(JSON.stringify({action:"clickgeo",name:cmd.map.overlay,type:f.type,properties:f.properties,geometry:f.geometry}));
});
}
}
overlays[cmd.map.overlay] = L.geoJson(cmd.map.geojson,opt);
if (!existsalready) {
layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
}
if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
map.addLayer(overlays[cmd.map.overlay]);
}
if (cmd.map.hasOwnProperty("fly") && (cmd.map.fly === true)) { map.flyToBounds(overlays[cmd.map.overlay].getBounds()); }
else if (cmd.map.hasOwnProperty("fit") && (cmd.map.fit === true)) { map.fitBounds(overlays[cmd.map.overlay].getBounds()); }
}
// Add a new NVG XML overlay layer
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("nvg") ) {
if (overlays.hasOwnProperty(cmd.map.overlay)) {
map.removeLayer(overlays[cmd.map.overlay]);
existsalready = true;
}
var parser = new NVG(cmd.map.nvg);
var geoj = parser.toGeoJSON();
overlays[cmd.map.overlay] = L.geoJson(geoj,{
style: function(feature) {
var st = { stroke:true, color:"black", weight:2, fill:true };
if (feature.hasOwnProperty("properties") && feature.properties.hasOwnProperty('style')) {
if (feature.properties.style.hasOwnProperty('stroke')) {
st.color = feature.properties.style.stroke;
}
if (feature.properties.style.hasOwnProperty('stroke-width')) {
st.weight = feature.properties.style["stroke-width"];
}
if (feature.properties.style.hasOwnProperty('stroke-opacity')) {
st.opacity = feature.properties.style["stroke-opacity"];
}
if (feature.properties.style.hasOwnProperty('fill')) {
if (feature.properties.style.fill == "none") { st.fill = false; }
else { st.fillColor = feature.properties.style.fill; }
}
if (feature.properties.style.hasOwnProperty('fill-opacity')) {
st.fillOpacity = feature.properties.style["fill-opacity"];
}
}
return st;
},
pointToLayer: function (feature, latlng) {
if (feature.hasOwnProperty("properties") && feature.properties.hasOwnProperty('symbol')) {
var sidc = feature.properties.symbol.toUpperCase().replace("APP6A:",'')//.substr(0,13);
var country;
if (sidc.length > 12) { country = sidc.substr(12).replace(/-/g,''); sidc = sidc.substr(0,12); }
myMarker = new ms.Symbol( sidc, {
uniqueDesignation:feature.properties.label,
country:country,
direction:feature.properties.course,
additionalInformation:feature.properties.modifier,
size:24
});
var myicon = L.icon({
iconUrl: myMarker.toDataURL(),
iconAnchor: [myMarker.getAnchor().x, myMarker.getAnchor().y],
className: "natoicon",
});
return L.marker(latlng, { name:feature.properties.label, icon:myicon });
}
else {
var geojsonMarkerOptions = {
radius: 10,
fillColor: "#ff7800",
color: "#000",
weight: 1,
opacity: 1,
fillOpacity: 0.8
};
return L.circleMarker(latlng, geojsonMarkerOptions);
}
}
});
if (!existsalready) {
layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
}
if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
map.addLayer(overlays[cmd.map.overlay]);
}
if (cmd.map.hasOwnProperty("fly") && cmd.map.fly === true) { map.flyToBounds(overlays[cmd.map.overlay].getBounds()); }
else if (cmd.map.hasOwnProperty("fit") && cmd.map.fit === true) { map.fitBounds(overlays[cmd.map.overlay].getBounds()); }
}
var custIco = function() {
var col = cmd.map.iconColor ?? "#910000";
var myMarker = L.VectorMarkers.icon({
icon: "circle",
markerColor: col,
prefix: 'fa',
iconColor: 'white'
});
if (cmd.map.hasOwnProperty("icon")) {
myMarker = L.divIcon({
className:"faicon",
html: '<center><i class="fa fa-fw '+cmd.map.icon+'" style="color:'+col+'"></i></center>',
iconSize: [16, 16],
});
}
var customLayer = L.geoJson(null, {
pointToLayer: function(geoJsonPoint, latlng) {
//console.log("KML/GPX point",geoJsonPoint)
var d = (geoJsonPoint.properties.description || "").trim();
var mypop = '<b>'+geoJsonPoint.properties.name + '</b><br>'+d+'<br>lat,lon : ' + geoJsonPoint.geometry.coordinates[1] + ', ' + geoJsonPoint.geometry.coordinates[0];
if (geoJsonPoint.geometry.coordinates[2]) {
mypop = '<b>'+geoJsonPoint.properties.name + '</b><br>'+d+'<br>lat,lon.alt : ' + geoJsonPoint.geometry.coordinates[1] + ', ' + geoJsonPoint.geometry.coordinates[0] + ', ' + geoJsonPoint.geometry.coordinates[2];
}
return L.marker(latlng, {icon:myMarker, title:geoJsonPoint.properties.name}).bindPopup(mypop);
}
});
return customLayer;
}
// Add a new KML overlay layer
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("kml") ) {
if (overlays.hasOwnProperty(cmd.map.overlay)) {
2021-04-01 04:33:07 +08:00
overlays[cmd.map.overlay].removeFrom(map);
existsalready = true;
}
try {
const parser = new DOMParser();
const kml = parser.parseFromString(cmd.map.kml, 'text/xml');
const track = new L.KML(kml);
overlays[cmd.map.overlay] = track;
} catch(e) { console.log("Failed to parse KML") }
if (!existsalready) {
layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
}
if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
2021-04-01 04:33:07 +08:00
overlays[cmd.map.overlay].addTo(map);
}
if (cmd.map.hasOwnProperty("fly") && cmd.map.fly === true) { map.flyToBounds(overlays[cmd.map.overlay].getBounds()); }
else if (cmd.map.hasOwnProperty("fit") && cmd.map.fit === true) { map.fitBounds(overlays[cmd.map.overlay].getBounds()); }
}
// Add a new TOPOJSON overlay layer
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("topojson") ) {
if (overlays.hasOwnProperty(cmd.map.overlay)) {
2021-04-01 04:33:07 +08:00
overlays[cmd.map.overlay].removeFrom(map);
existsalready = true;
}
overlays[cmd.map.overlay] = omnivore.topojson.parse(cmd.map.topojson);
if (!existsalready) {
layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
}
if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
2021-04-01 04:33:07 +08:00
overlays[cmd.map.overlay].addTo(map);
}
if (cmd.map.hasOwnProperty("fly") && cmd.map.fly === true) { map.flyToBounds(overlays[cmd.map.overlay].getBounds()); }
else if (cmd.map.hasOwnProperty("fit") && cmd.map.fit === true) { map.fitBounds(overlays[cmd.map.overlay].getBounds()); }
}
// Add a new GPX overlay layer
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("gpx") ) {
if (overlays.hasOwnProperty(cmd.map.overlay)) {
2021-04-01 04:33:07 +08:00
overlays[cmd.map.overlay].removeFrom(map);
existsalready = true;
}
overlays[cmd.map.overlay] = omnivore.gpx.parse(cmd.map.gpx, null, custIco());
if (!existsalready) {
layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
}
if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
2021-04-01 04:33:07 +08:00
overlays[cmd.map.overlay].addTo(map);
}
if (cmd.map.hasOwnProperty("fly") && cmd.map.fly === true) { map.flyToBounds(overlays[cmd.map.overlay].getBounds()); }
else if (cmd.map.hasOwnProperty("fit") && cmd.map.fit === true) { map.fitBounds(overlays[cmd.map.overlay].getBounds()); }
}
// Add a new velocity overlay layer
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("velocity") ) {
if (overlays.hasOwnProperty(cmd.map.overlay)) {
2021-04-01 04:33:07 +08:00
overlays[cmd.map.overlay].removeFrom(map);
layercontrol.removeOverlay(overlays[cmd.map.overlay]);
}
overlays[cmd.map.overlay] = L.velocityLayer(cmd.map.velocity);
2021-04-01 04:33:07 +08:00
layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
2021-04-01 04:33:07 +08:00
overlays[cmd.map.overlay].addTo(map);
}
if (cmd.map.hasOwnProperty("fly") && cmd.map.fly === true) { map.flyToBounds(overlays[cmd.map.overlay].getBounds()); }
else if (cmd.map.hasOwnProperty("fit") && cmd.map.fit === true) { map.fitBounds(overlays[cmd.map.overlay].getBounds()); }
}
// Add a new overlay layer
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("url") && cmd.map.hasOwnProperty("opt")) {
console.log("New overlay:",cmd.map.overlay);
if (overlays.hasOwnProperty(cmd.map.overlay)) { existsalready = true; }
if (cmd.map.hasOwnProperty("wms")) { // special case for wms
if (cmd.map.wms === "grey") {
overlays[cmd.map.overlay] = L.tileLayer.graywms(cmd.map.url, cmd.map.opt);
}
else {
overlays[cmd.map.overlay] = L.tileLayer.wms(cmd.map.url, cmd.map.opt);
}
}
else if (cmd.map.hasOwnProperty("bounds")) { //Image Overlay in the bounds specified (2D Array)
if (cmd.map.bounds.length === 2 && cmd.map.bounds[0].length === 2 && cmd.map.bounds[1].length === 2) {
overlays[cmd.map.overlay] = new L.imageOverlay(cmd.map.url, L.latLngBounds(cmd.map.bounds), cmd.map.opt);
}
}
2021-10-29 18:30:03 +08:00
else if (cmd.map.url.slice(-4).toLowerCase() === ".pbf") {
overlays[cmd.map.overlay] = VectorTileLayer(cmd.map.url, cmd.map.opt);
}
else if (cmd.map.hasOwnProperty("transparentPixels")) {
cmd.map.opt.pixelCodes = cmd.map.transparentPixels;
cmd.map.opt.matchRGBA = [ 0,0,0,0 ];
overlays[cmd.map.overlay] = L.tileLayerPixelFilter(cmd.map.url, cmd.map.opt);
}
else {
overlays[cmd.map.overlay] = L.tileLayer(cmd.map.url, cmd.map.opt);
}
//if (!existsalready && !inIframe) {
if (!existsalready) {
layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
}
if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
2021-04-01 04:33:07 +08:00
overlays[cmd.map.overlay].addTo(map);
}
}
// Swap a base layer
if (cmd.layer && basemaps.hasOwnProperty(cmd.layer)) {
map.removeLayer(basemaps[baselayername]);
baselayername = cmd.layer;
basemaps[baselayername].addTo(map);
}
// If set to none then remove the baselayer...
if (cmd.layer && (cmd.layer === "none")) {
map.removeLayer(basemaps[baselayername]);
baselayername = cmd.layer;
}
// Add search command
if (cmd.hasOwnProperty("search") && (typeof cmd.search === "string")) {
document.getElementById('search').value = cmd.search;
if (cmd.search !== "") {
openMenu();
doSearch();
}
else {
closeMenu();
clearSearch();
}
}
// Add side by side control
if (cmd.side && (cmd.side === "none")) {
sidebyside.remove();
map.removeLayer(basemaps[sidebyside.lay]);
sidebyside = undefined;
}
if (cmd.side && basemaps.hasOwnProperty(cmd.side)) {
if (sidebyside) { sidebyside.remove(); map.removeLayer(basemaps[sidebyside.lay]); }
basemaps[cmd.side].addTo(map);
sidebyside = L.control.sideBySide(basemaps[baselayername], basemaps[cmd.side]);
sidebyside.addTo(map);
sidebyside.lay = cmd.side;
}
if (cmd.split && sidebyside && (cmd.split <= 100) && (cmd.split >= 0)) { sidebyside.setSplit(cmd.split); }
// Turn on an existing overlay(s)
if (cmd.hasOwnProperty("showlayer")) {
if (typeof cmd.showlayer === "string") { cmd.showlayer = [ cmd.showlayer ]; }
2021-11-10 17:39:36 +08:00
var sn = cmd.showlayer.indexOf("ship nav");
if (sn !== -1) { cmd.showlayer[sn] = "ship navigation"; }
for (var i=0; i < cmd.showlayer.length; i++) {
if (overlays.hasOwnProperty(cmd.showlayer[i])) {
map.addLayer(overlays[cmd.showlayer[i]]);
}
}
}
// Turn off an existing overlay(s)
if (cmd.hasOwnProperty("hidelayer")) {
if (typeof cmd.hidelayer === "string") { cmd.hidelayer = [ cmd.hidelayer ]; }
2021-11-10 17:39:36 +08:00
var sn = cmd.hidelayer.indexOf("ship nav");
if (sn !== -1) { cmd.hidelayer[sn] = "ship navigation"; }
for (var i=0; i < cmd.hidelayer.length; i++) {
if (overlays.hasOwnProperty(cmd.hidelayer[i])) {
map.removeLayer(overlays[cmd.hidelayer[i]]);
}
}
}
// Lock the pan so map can be moved
if (cmd.hasOwnProperty("panlock")) {
if (cmd.panlock == "true" || cmd.panlock == true) { lockit = true; }
else { lockit = false; doLock(false); }
document.getElementById("lockit").checked = lockit;
}
// move to a new position
var clat = map.getCenter().lat;
var clon = map.getCenter().lng;
var czoom = map.getZoom();
if (cmd.hasOwnProperty("lat")) { clat = cmd.lat; }
if (cmd.hasOwnProperty("lon")) { clon = cmd.lon; }
if (cmd.hasOwnProperty("zoom")) { czoom = cmd.zoom; }
map.setView([clat,clon],czoom);
if (cmd.hasOwnProperty("cluster")) {
clusterAt = cmd.cluster;
document.getElementById("setclus").value = cmd.cluster;
setCluster(clusterAt);
}
if (cmd.hasOwnProperty("maxage")) {
document.getElementById("maxage").value = cmd.maxage;
setMaxAge();
}
2022-12-13 23:45:27 +08:00
// Replace heatmap layer with new array (and optionally options)
if (cmd.hasOwnProperty("heatmap") && heat) {
if (cmd.hasOwnProperty("options")) { heat.setOptions(cmd.options); }
heat.setLatLngs(cmd.heatmap);
// heat.setOptions(cmd.heatmap);
// document.getElementById("heatall").checked = !!cmd.heatmap;
// heat.redraw();
}
if (cmd.hasOwnProperty("panlock") && lockit === true) { doLock(true); }
if (cmd.hasOwnProperty("zoomlock")) {
if (cmd.zoomlock == "true" || cmd.zoomlock == true) {
if (map.doubleClickZoom.enabled()) { map.removeControl(map.zoomControl); }
map.doubleClickZoom.disable();
map.scrollWheelZoom.disable();
2021-02-05 19:46:04 +08:00
map.touchZoom.disable();
}
else {
if (!map.doubleClickZoom.enabled()) { map.addControl(map.zoomControl); }
map.doubleClickZoom.enable();
map.scrollWheelZoom.enable();
2021-02-05 19:46:04 +08:00
map.touchZoom.enable();
}
}
2022-01-16 23:35:27 +08:00
if (cmd.hasOwnProperty("bounds")) { // Move/Zoom map to new bounds
if (cmd.bounds.length === 2 && cmd.bounds[0].length === 2 && cmd.bounds[1].length === 2) {
if (cmd.hasOwnProperty("fly") && cmd.fly === true) {
map.flyToBounds(cmd.bounds);
} else {
map.fitBounds(cmd.bounds);
}
2022-01-16 23:35:27 +08:00
}
}
}
// handle any incoming GEOJSON directly - may style badly
2020-05-06 06:37:27 +08:00
function doGeojson(n,g,l,o) {
2023-07-28 16:16:35 +08:00
// console.log("GEOJSON",n,g,l,o)
var lay = l ?? g.name ?? "unknown";
2020-05-06 06:37:27 +08:00
// if (!basemaps[lay]) {
var opt = { style: function(feature) {
var st = { stroke:true, color:"#910000", weight:1, fill:true, fillColor:"#910000", fillOpacity:0.15 };
2020-05-06 06:37:27 +08:00
st = Object.assign(st,o);
if (feature.hasOwnProperty("properties")) {
2020-05-29 04:14:45 +08:00
//console.log("GPROPS", feature.properties)
st.color = feature.properties["stroke"] ?? st.color;
st.weight = feature.properties["stroke-width"] ?? st.weight;
st.fillColor = feature.properties["fill-color"] ?? feature.properties["fill"] ?? st.fillColor;
st.fillOpacity = feature.properties["fill-opacity"] ?? st.fillOpacity;
2020-05-29 05:55:14 +08:00
delete feature.properties["stroke"];
delete feature.properties["stroke-width"];
delete feature.properties["fill-color"];
delete feature.properties["fill"];
delete feature.properties["fill-opacity"];
delete feature.properties["stroke-opacity"];
2020-05-06 06:37:27 +08:00
}
if (feature.hasOwnProperty("style")) {
2020-05-29 04:14:45 +08:00
//console.log("GSTYLE", feature.style)
st.color = feature.style["stroke"] ?? st.color;
st.weight = feature.style["stroke-width"] ?? st.weight;
st.fillColor = feature.style["fill-color"] ?? feature.style["fill"] ?? st.fillColor;
st.fillOpacity = feature.style["fill-opacity"] ?? st.fillOpacity;
2020-05-06 06:37:27 +08:00
}
2022-01-13 01:05:44 +08:00
if (feature.hasOwnProperty("geometry") && feature.geometry.hasOwnProperty("type") && (feature.geometry.type === "LineString" || feature.geometry.type === "MultiLineString") ) {
2020-05-06 06:37:27 +08:00
st.fill = false;
}
return st;
}}
2020-05-29 05:55:14 +08:00
opt.pointToLayer = function (feature, latlng) {
var myMarker;
2022-04-03 06:52:27 +08:00
if (feature.properties.hasOwnProperty("icon")) {
var regi = /^[S,G,E,I,O][A-Z]{3}.*/i; // if it looks like a SIDC code
if (regi.test(feature.properties.icon)) {
feature.properties.SIDC = (feature.properties.icon.toUpperCase()+"------------").substr(0,12);
delete feature.properties.icon;
}
}
if (feature.properties.hasOwnProperty("SIDC")) {
myMarker = new ms.Symbol( feature.properties.SIDC.toUpperCase(), {
2022-04-03 06:52:27 +08:00
uniqueDesignation:unescape(encodeURIComponent(feature.properties.title||feature.properties.unit)),
country:feature.properties.country,
direction:feature.properties.bearing,
additionalInformation:feature.properties.modifier,
2022-04-03 06:52:27 +08:00
size:25
});
2022-04-03 06:52:27 +08:00
if (myMarker.hasOwnProperty("metadata") && myMarker.metadata.hasOwnProperty("echelon")) {
var sz = iconSz[myMarker.metadata.echelon];
myMarker.setOptions({size:sz});
}
myMarker = L.icon({
iconUrl: myMarker.toDataURL(),
iconAnchor: [myMarker.getAnchor().x, myMarker.getAnchor().y],
className: "natoicon",
});
}
2023-07-29 23:28:03 +08:00
else if (feature.properties.hasOwnProperty("marker-symbol") && feature.properties["marker-symbol"].substr(0,3) === "fa-") {
2023-07-31 16:34:55 +08:00
var col = feature.properties["marker-color"] ?? "#910000";
var imod = "";
if (feature.properties["marker-symbol"].indexOf(" ") === -1) { imod = "fa-2x "; }
myMarker = L.divIcon({
className:"faicon",
html: '<center><i class="fa fa-fw '+imod+feature.properties["marker-symbol"]+'" style="color:'+col+'"></i></center>',
iconSize: [32, 32],
iconAnchor: [16, 12],
popupAnchor: [0, -16]
});
2023-07-29 22:56:22 +08:00
}
else {
myMarker = L.VectorMarkers.icon({
icon: feature.properties["marker-symbol"] ?? "circle",
markerColor: (feature.properties["marker-color"] ?? "#910000"),
prefix: 'fa',
iconColor: 'white'
});
}
2020-05-29 05:55:14 +08:00
if (!feature.properties.hasOwnProperty("title")) {
feature.properties.title = feature.properties["marker-symbol"];
}
if (feature.properties.hasOwnProperty("url")) {
feature.properties.url = "<a target='_new' href='"+feature.properties.url+"'>"+feature.properties.url+"</a>";
}
var nf = {title:feature.properties.title, name:feature.properties.name};
feature.properties = Object.assign(nf, feature.properties);
return L.marker(latlng, {title:feature.properties.title ?? "", icon:myMarker});
2020-05-29 05:55:14 +08:00
}
2020-05-06 06:37:27 +08:00
opt.onEachFeature = function (f,l) {
2021-09-10 21:57:52 +08:00
if (f.properties && Object.keys(f.properties).length > 0) {
2023-07-29 22:56:22 +08:00
var tx = JSON.parse(JSON.stringify(f.properties,null,' '));
delete tx["marker-symbol"];
delete tx["marker-color"];
delete tx["marker-size"];
tx = JSON.stringify(tx,null,' ');
2021-09-10 21:57:52 +08:00
if ( tx !== "{}") {
2023-07-29 22:56:22 +08:00
l.bindPopup('<pre style="overflow-x: scroll">'+tx.replace(/[\{\}"]/g,'')+'</pre>');
2021-09-10 21:57:52 +08:00
}
}
if (o && o.hasOwnProperty("clickable") && o.clickable === true) {
l.on('click', function (e) {
ws.send(JSON.stringify({action:"clickgeo",name:n,type:f.type,properties:f.properties,geometry:f.geometry}));
});
}
2020-05-06 06:37:27 +08:00
}
markers[n] = L.geoJson(g,opt);
markers[n].lay = lay;
if (typeof layers[lay] == "undefined") { // add layer if if doesn't exist
layers[lay] = new L.LayerGroup();
overlays[lay] = layers[lay];
2020-05-06 22:52:20 +08:00
layercontrol.addOverlay(overlays[lay],lay);
2020-05-06 06:37:27 +08:00
}
layers[lay].addLayer(markers[n]);
map.addLayer(layers[lay]);
}
2023-08-11 22:21:34 +08:00
// handle TAK messages from TAK server tcp - XML->JSON
function doTAKjson(p) {
// console.log("TAK event",p);
if (p.type.indexOf('a') === 0) {
var d = {};
d.lat = Number(p.point.lat);
d.lon = Number(p.point.lon);
2023-08-13 21:06:05 +08:00
d.team = p.detail?.__group?.name;
d.team = d.team + ' <i style="color:' + d.team + '" class="fa fa-square"></i>';
2023-08-11 22:21:34 +08:00
d.role = p.detail?.__group?.role;
d.type = p.type;
d.uid = p.uid;
d.name = p.detail?.contact?.callsign || p.uid;
d.hdg = p.detail?.track?.course;
d.speed = p.detail?.track?.speed;
2023-08-13 21:06:05 +08:00
try {
var st = (new Date(p.time)).getTime() / 1000;
var et = (new Date(p.stale)).getTime() / 1000;
d.timestamp = (new Date(p.time)).toISOString();
d.staletime = (new Date(p.stale)).toISOString();
d.ttl = parseInt(et-st);
} catch(e) { console.log(e); }
2023-08-11 22:21:34 +08:00
d.alt = Number(p.point.hae) || 9999999;
2023-08-13 21:06:05 +08:00
if (d.alt === 9999999) { delete d.alt; }
handleCoTtypes(d);
2023-08-11 22:21:34 +08:00
setMarker(d);
}
else {
console.log("Skip TAK type",p.type);
}
}
// handle TAK messages from TAK Multicast - Protobuf->JSON
function doTAKMCjson(p) {
// console.log("TAK Multicast event",p);
if (p.type.indexOf('a') === 0) {
var d = {};
d.lat = p.lat;
d.lon = p.lon;
2023-08-13 21:06:05 +08:00
d.team = p.detail?.group?.name;
d.team = d.team + ' <i style="color:' + d.team + '" class="fa fa-square"></i>';
2023-08-11 22:21:34 +08:00
d.role = p.detail?.group?.role;
d.type = p.type;
d.uid = p.uid;
d.name = p.detail?.contact?.callsign || p.uid;
d.hdg = p.detail?.track?.course;
d.speed = p.detail?.track?.speed;
2023-08-13 21:06:05 +08:00
try {
d.timestamp = (new Date(+p.sendTime)).toISOString();
d.staletime = (new Date(+p.staleTime)).toISOString();
d.ttl = parseInt((+p.staleTime / 1000) - (+p.sendTime / 1000));
} catch(e) { console.log(e); }
2023-08-11 22:21:34 +08:00
d.alt = p.hae || 9999999;
2023-08-13 21:06:05 +08:00
if (d.alt === 9999999) { delete d.alt; }
handleCoTtypes(d);
2023-08-11 22:21:34 +08:00
setMarker(d);
}
else {
console.log("Skip TAK type",p.type);
}
}
function handleCoTtypes(d) {
var i = d.type.split('-').join('').toUpperCase();
if (i[0] === 'A') { i = 'S' + i.substr(1,2) + 'P' + i.substr(3); }
if (d.role === 'Team Lead') { i = i + '----B'; }
if (d.role === 'HQ') { i = 'SFGPUH' };
if (d.role === "Medic") { i = 'SFGPUSM----A'; }
if (d.role === "RTO") { i = 'SFGPUUS'; }
if (d.role === 'K9') { i = 'SFGPUU'; }
d.SIDC = (i + '-------').substr(0,12);
// Handle "special" types
if (d.type === "a-h-X-i-o") { d.SIDC = "EHIP--------" }
if (d.type === "a-h-X-i-m-d") { d.SIDC = "EHNPBB------" }
if (d.type === "a-h-X-i-g-e") { d.SIDC = "EHNPAC------" }
// Other non-atom types - tbd
// b-a-o (Alert) - "ESOPB-------"
// b-a-g (Geofence alert) - "ESOPEC------"
// b-a-o-can (cancel Alert) - "ENOSB-------" short ttl
// b-m-p-s-m Marker point (dot with colour)
// b-m-p-s-p-loc Located object eg video
// b-m-p-s-p-i Location indicator
// b-i-x-i Camera image
// b-m-r Route
// b-m-p-c Waypoint
// b-r-f-h-c Medevac
// b-d Drawings -c-c circle -c-e ellipse -r rectangle -f freehand
// b-t-f Geochat (f = file)
return d;
2023-08-11 22:21:34 +08:00
}