node-red-contrib-web-worldmap 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,9 +16,9 @@ var buttons = {};
16
16
  var marksIndex = 0;
17
17
  var menuOpen = false;
18
18
  var clusterAt = 0;
19
- var maxage = 900; // default max age of icons on map in seconds - cleared after 10 mins
19
+ var maxage = 900; // default max age of icons on map in seconds - cleared after 15 mins
20
20
  var baselayername = "OSM grey"; // Default base layer OSM but uniform grey
21
- var pagefoot = " © DCJ 2023"
21
+ var pagefoot = " © DCJ 2023";
22
22
  var inIframe = false;
23
23
  var showUserMenu = true;
24
24
  var showLayerMenu = true;
@@ -28,10 +28,12 @@ var heat;
28
28
  var minimap;
29
29
  var sidebyside;
30
30
  var layercontrol;
31
- // var drawControl;
31
+ var colorControl;
32
+ var drawCount = 0;
32
33
  var drawingColour = "#910000";
34
+ var drawcontextmenu = "";
33
35
  var sendDrawing;
34
- var colorControl;
36
+ var rmenudata = {};
35
37
  var sendRoute;
36
38
  var oldBounds = {ne:{lat:0, lng:0}, sw:{lat:0, lng:0}};
37
39
  var edgeLayer = new L.layerGroup();
@@ -54,7 +56,32 @@ var iconSz = {
54
56
  "Command": 44
55
57
  };
56
58
 
57
- // L.PM.setOptIn(true);
59
+ var filesAdded = '';
60
+
61
+ var loadStatic = function(fileName) {
62
+ if (filesAdded.indexOf(fileName) !== -1) { return; }
63
+ var head = document.getElementsByTagName('head')[0]
64
+ if (fileName.indexOf('js') !== -1) {
65
+ var script = document.createElement('script');
66
+ script.src = fileName;
67
+ script.type = 'text/javascript';
68
+ console.log("Loading: ",fileName);
69
+ head.append(script);
70
+ filesAdded += ' ' + fileName;
71
+ }
72
+ else if (fileName.indexOf('css') !== -1) {
73
+ var style = document.createElement('link');
74
+ style.href = fileName;
75
+ style.type = 'text/css';
76
+ style.rel = 'stylesheet';
77
+ console.log("Loading: ",fileName);
78
+ head.append(style);;
79
+ filesAdded += ' ' + fileName;
80
+ }
81
+ else {
82
+ console.log("Unsupported file type: ",fileName);
83
+ }
84
+ }
58
85
 
59
86
  // Create the socket
60
87
  var connect = function() {
@@ -81,17 +108,15 @@ var connect = function() {
81
108
  if (data.hasOwnProperty("type") && data.hasOwnProperty("data") && data.type === "Buffer") { data = data.data.toString(); }
82
109
  handleData(data);
83
110
  }
84
- catch (e) { if (data) { console.log("BAD DATA",data); console.log(e) } }
111
+ catch (e) { if (data) { console.log("BAD DATA",data); console.log(e); } }
85
112
  // console.log("DATA",typeof data,data);
86
113
  };
87
- }
114
+ };
88
115
  console.log("CONNECT TO",location.pathname + 'socket');
89
116
 
90
117
  var handleData = function(data) {
91
118
  if (Array.isArray(data)) {
92
119
  //console.log("ARRAY");
93
- // map.closePopup();
94
- // var bnds= L.latLngBounds([0,0]);
95
120
  for (var prop in data) {
96
121
  if (data[prop].command) { doCommand(data[prop].command); delete data[prop].command; }
97
122
  if (data[prop].hasOwnProperty("name")) {
@@ -102,11 +127,11 @@ var handleData = function(data) {
102
127
  data = {command:{map:{overlay:"KML", kml:data[prop].payload}}};
103
128
  doCommand(data.command); return;
104
129
  }
105
- else { console.log("SKIP A",data[prop]); }
130
+ else { console.log("SKIP array item",data[prop]); }
106
131
  }
107
- // map.fitBounds(bnds.pad(0.25));
108
132
  }
109
133
  else {
134
+ // Handle some raw string data overlays
110
135
  if (typeof data === "string" && data.indexOf("<?xml") == 0) {
111
136
  if (data.indexOf("<nvg") != -1) {
112
137
  data = {command:{map:{overlay:"NVG", nvg:data}}};
@@ -118,6 +143,8 @@ var handleData = function(data) {
118
143
  data = {command:{map:{overlay:"GPX", gpx:data}}};
119
144
  }
120
145
  }
146
+
147
+ // handle any commands in the data
121
148
  if (data.command) { doCommand(data.command); delete data.command; }
122
149
 
123
150
  // handle raw geojson type msg
@@ -131,7 +158,7 @@ var handleData = function(data) {
131
158
  else if (data.hasOwnProperty("event") && data.event.hasOwnProperty("point")) {
132
159
  doTAKjson(data.event);
133
160
  }
134
- // handle TAK json (from multicast Protobuf)
161
+ // handle TAK json (from multicast Protobuf via tak-ingest node)
135
162
  else if (data.hasOwnProperty("cotEvent") && data.cotEvent.hasOwnProperty("lat") && data.cotEvent.hasOwnProperty("lon")) {
136
163
  doTAKMCjson(data.cotEvent);
137
164
  }
@@ -147,7 +174,7 @@ var handleData = function(data) {
147
174
  }
148
175
  }
149
176
 
150
- window.onunload = function() { if (ws) ws.close(); }
177
+ window.onunload = function() { if (ws) { ws.close(); } }
151
178
 
152
179
  var customTopoLayer = L.geoJson(null, {clickable:false, style: {color:"blue", weight:2, fillColor:"#cf6", fillOpacity:0.04}});
153
180
  layers["_countries"] = omnivore.topojson('images/world-50m-flat.json',null,customTopoLayer);
@@ -181,23 +208,16 @@ if (inIframe === true) {
181
208
  startzoom = window.localStorage.getItem("lastzoom");
182
209
  }
183
210
  }
184
- // if ( window.localStorage.hasOwnProperty("clusterat") ) {
185
- // clusterAt = window.localStorage.getItem("clusterat");
186
- // document.getElementById("setclus").value = clusterAt;
187
- // }
188
- // if ( window.localStorage.hasOwnProperty("maxage") ) {
189
- // maxage = window.localStorage.getItem("maxage");
190
- // document.getElementById("maxage").value = maxage;
191
- // }
192
211
 
193
212
  // Create the Initial Map object.
194
213
  map = new L.map('map',{
195
214
  zoomSnap: 0.1,
196
215
  rotate: true,
197
- rotateControl: {
198
- closeOnZeroBearing: true,
199
- position: 'topleft'
200
- },
216
+ rotateControl: false,
217
+ // rotateControl: {
218
+ // closeOnZeroBearing: true,
219
+ // position: 'topleft'
220
+ // },
201
221
  bearing: 0}).setView(startpos, startzoom);
202
222
  map.whenReady(function() {
203
223
  connect();
@@ -343,7 +363,7 @@ if (inIframe) {
343
363
  document.getElementById("menu").style.borderRadius="6px";
344
364
  }
345
365
  else {
346
- console.log("NOT in an iframe");
366
+ //console.log("NOT in an iframe");
347
367
  if (!showUserMenu) { document.getElementById("bars").style.display="none"; }
348
368
 
349
369
  // Add the fullscreen button
@@ -418,17 +438,17 @@ var Lgrid = L.latlngGraticule({
418
438
  // Copyright (c) 2013 Måns Beckman, All rights reserved.
419
439
  var edgeAware = function() {
420
440
  if (!edgeEnabled) { return; }
421
- map.removeLayer(edgeLayer)
422
- edgeLayer = new L.layerGroup();
423
- var mapBounds = map.getBounds();
424
- var mapBoundsCenter = mapBounds.getCenter();
441
+ map.removeLayer(edgeLayer)
442
+ edgeLayer = new L.layerGroup();
443
+ var mapBounds = map.getBounds();
444
+ var mapBoundsCenter = mapBounds.getCenter();
425
445
 
426
- pSW = map.options.crs.latLngToPoint(mapBounds.getSouthWest(), map.getZoom());
427
- pNE = map.options.crs.latLngToPoint(mapBounds.getNorthEast(), map.getZoom());
428
- pCenter = map.options.crs.latLngToPoint(mapBoundsCenter, map.getZoom());
446
+ pSW = map.options.crs.latLngToPoint(mapBounds.getSouthWest(), map.getZoom());
447
+ pNE = map.options.crs.latLngToPoint(mapBounds.getNorthEast(), map.getZoom());
448
+ pCenter = map.options.crs.latLngToPoint(mapBoundsCenter, map.getZoom());
429
449
 
430
- 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()) );
431
- for (var id in markers) {
450
+ 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()) );
451
+ for (var id in markers) {
432
452
  if (allData[id] && allData[id].hasOwnProperty("SIDC")) {
433
453
  markerLatLng = markers[id].getLatLng();
434
454
  if ( viewBounds.contains(markerLatLng) && !mapBounds.contains(markerLatLng) ) {
@@ -471,15 +491,15 @@ var edgeAware = function() {
471
491
  edgeLayer.addLayer(L.marker([lat,lng],{icon:myicon}))
472
492
  }
473
493
  }
474
- }
475
- edgeLayer.addTo(map)
494
+ }
495
+ edgeLayer.addTo(map)
476
496
  }
477
- // end of edge function
497
+ // end of edgeAware function
478
498
 
479
499
  var panit = false;
480
500
  function doPanit(v) {
481
501
  if (v !== undefined) { panit = v; }
482
- console.log("Panit set :",panit);
502
+ // console.log("Panit set :",panit);
483
503
  }
484
504
 
485
505
  var heatAll = false;
@@ -489,24 +509,23 @@ function doHeatAll(v) {
489
509
  }
490
510
 
491
511
  var lockit = false;
492
- var mb = new L.LatLngBounds([[-120,-360],[120,360]]);
512
+ var mbnds = new L.LatLngBounds([[-120,-360],[120,360]]);
493
513
  function doLock(v) {
494
514
  if (v !== undefined) { lockit = v; }
495
515
  if (lockit === false) {
496
- mb = new L.LatLngBounds([[-120,-360],[120,360]]);
516
+ mbnds = new L.LatLngBounds([[-120,-360],[120,360]]);
497
517
  map.dragging.enable();
498
518
  }
499
519
  else {
500
- mb = map.getBounds();
520
+ mbnds = map.getBounds();
501
521
  map.dragging.disable();
502
522
  window.localStorage.setItem("lastpos",JSON.stringify(map.getCenter()));
503
523
  window.localStorage.setItem("lastzoom", map.getZoom());
504
524
  window.localStorage.setItem("lastlayer", baselayername);
505
- //window.localStorage.setItem("clusterat", clusterAt);
506
525
  window.localStorage.setItem("maxage", maxage);
507
526
  console.log("Saved :",JSON.stringify(map.getCenter()),map.getZoom(),baselayername);
508
527
  }
509
- map.setMaxBounds(mb);
528
+ map.setMaxBounds(mbnds);
510
529
  //console.log("Map bounds lock :",lockit);
511
530
  }
512
531
 
@@ -581,7 +600,7 @@ function doSearch() {
581
600
  marks = [];
582
601
  marksIndex = 0;
583
602
  for (var key in markers) {
584
- if ( (~(key.toLowerCase()).indexOf(value.toLowerCase())) && (mb.contains(markers[key].getLatLng()))) {
603
+ if ( (~(key.toLowerCase()).indexOf(value.toLowerCase())) && (mbnds.contains(markers[key].getLatLng()))) {
585
604
  marks.push(markers[key]);
586
605
  }
587
606
  if (markers[key].icon === value) {
@@ -616,7 +635,8 @@ function doSearch() {
616
635
  else {
617
636
  if (lockit) {
618
637
  document.getElementById('searchResult').innerHTML = "&nbsp;<font color='#ff0'>Found "+marks.length+" results within bounds.</font>";
619
- } else {
638
+ }
639
+ else {
620
640
  document.getElementById('searchResult').innerHTML = "&nbsp;<font color='#ff0'>Found "+marks.length+" results.</font>";
621
641
  }
622
642
  }
@@ -639,7 +659,7 @@ function clearSearch() {
639
659
  marks = [];
640
660
  marksIndex = 0;
641
661
  for (var key in markers) {
642
- if ( (~(key.toLowerCase()).indexOf(value.toLowerCase())) && (mb.contains(markers[key].getLatLng()))) {
662
+ if ( (~(key.toLowerCase()).indexOf(value.toLowerCase())) && (mbnds.contains(markers[key].getLatLng()))) {
643
663
  marks.push(markers[key]);
644
664
  }
645
665
  }
@@ -665,7 +685,8 @@ function toggleMenu() {
665
685
  menuOpen = !menuOpen;
666
686
  if (menuOpen) {
667
687
  document.getElementById("menu").style.display = 'block';
668
- } else {
688
+ }
689
+ else {
669
690
  document.getElementById("menu").style.display = 'none';
670
691
  dialogue.close();
671
692
  }
@@ -814,7 +835,7 @@ var addThing = function() {
814
835
  //popped = false;
815
836
  var bits = thing.split(",");
816
837
  var icon = (bits[1] || "circle").trim();
817
- var lay = (bits[2] || "_drawing").trim();
838
+ var lay = (bits[2] || "unknown").trim(); // TODO: Do we want _drawing here or unknown ?
818
839
  var colo = (bits[3] ?? "#910000").trim();
819
840
  colo = colorKeywordToRGB(colo);
820
841
  var hdg = parseFloat(bits[4] || 0);
@@ -836,28 +857,30 @@ var addThing = function() {
836
857
  map.addLayer(layers[lay]);
837
858
  }
838
859
 
860
+ var form = {};
861
+ var addToForm = function(n,v) { form[n] = v; }
839
862
  var feedback = function(n,v,a,c) {
840
- if (v === "$form") { v = form; }
863
+ if (v === "_form") { v = form; }
841
864
  if (markers[n]) {
842
- //var fp = markers[n]._latlng;
843
- // ws.send(JSON.stringify({action:a||"feedback", name:n, value:v, layer:markers[n].lay, lat:fp.lat, lon:fp.lng}));
844
- var fb = allData[n];
845
- fb.action = a || "feedback";
846
- if (v !== undefined) { fb.value = v; }
847
- ws.send(JSON.stringify(fb));
865
+ console.log("FB1",n,v,a,c)
866
+ allData[n].action = a || "feedback";
867
+ if (v !== undefined) { allData[n][a||"value"] = v; }
868
+ ws.send(JSON.stringify(allData[n]));
869
+ setMarker(allData[n]);
870
+ }
871
+ else if (polygons[n]) {
872
+ console.log("FB2",n,v,a)
873
+ sendDrawing(n,v,a)
848
874
  }
849
875
  else {
850
876
  if (n === undefined) { n = "map"; }
877
+ console.log("FB3",n,v,a,c)
878
+ rmenudata = v;
851
879
  ws.send(JSON.stringify({action:a||"feedback", name:n, value:v, lat:rclk.lat, lon:rclk.lng}));
852
880
  }
853
881
  if (c === true) { map.closePopup(); }
854
882
  }
855
883
 
856
- var form = {};
857
- var addToForm = function(n,v) {
858
- form[n] = v;
859
- }
860
-
861
884
  // map.on('click', function(e) {
862
885
  // ws.send(JSON.stringify({action:"click", lat:e.latlng.lat.toFixed(5), lon:e.latlng.lng.toFixed(5)}));
863
886
  // });
@@ -882,6 +905,12 @@ map.on('contextmenu', function(e) {
882
905
  if ((hiderightclick !== true) && (addmenu.length > 0)) {
883
906
  rclk = e.latlng;
884
907
  form = {};
908
+ var ramen = ""+addmenu;
909
+ for (const item in rmenudata) {
910
+ ramen = ramen.replace(new RegExp("\\${"+item+"}","g"),rmenudata[item]);
911
+ }
912
+ ramen = ramen.replace(/\${.*?}/g,'')
913
+ rightmenuMap.setContent(ramen);
885
914
  rightmenuMap.setLatLng(e.latlng);
886
915
  map.openPopup(rightmenuMap);
887
916
  setTimeout( function() {
@@ -893,7 +922,6 @@ map.on('contextmenu', function(e) {
893
922
  }
894
923
  });
895
924
 
896
-
897
925
  // Layer control based on select box rather than radio buttons.
898
926
  //var layercontrol = L.control.selectLayers(basemaps, overlays).addTo(map);
899
927
  layercontrol = L.control.layers(basemaps, overlays);
@@ -1086,7 +1114,6 @@ var addOverlays = function(overlist) {
1086
1114
 
1087
1115
  layers["_drawing"] = new L.FeatureGroup();
1088
1116
  overlays["drawing"] = layers["_drawing"];
1089
- var drawCount = 0;
1090
1117
  map.pm.addControls({
1091
1118
  position: 'topleft',
1092
1119
  drawMarker: false,
@@ -1102,19 +1129,27 @@ var addOverlays = function(overlist) {
1102
1129
  color: drawingColour,
1103
1130
  fillColor: drawingColour,
1104
1131
  fillOpacity: 0.4
1105
- });
1132
+ });
1106
1133
  }
1107
1134
 
1108
1135
  var shape;
1109
1136
  map.on("pm:create", (e) => {
1110
- var name = e.shape + drawCount;
1111
1137
  drawCount = drawCount + 1;
1138
+ var name = e.shape + drawCount;
1112
1139
 
1113
1140
  e.layer.on('contextmenu', function(e) {
1114
1141
  L.DomEvent.stopPropagation(e);
1142
+ var name = e.target.name;
1115
1143
  var rmen = L.popup({offset:[0,-12]}).setLatLng(e.latlng);
1116
- rmen.setContent("<input type='text' value='"+e.target.name+"' id='dinput' placeholder='name (,icon, layer)'/><br/><button onclick='editPoly(\""+e.target.name+"\");'>Edit points</button><button onclick='editPoly(\""+e.target.name+"\",\"drag\");'>Drag</button><button onclick='editPoly(\""+e.target.name+"\",\"rot\");'>Rotate</button><button onclick='delMarker(\""+e.target.name+"\",true);'>Delete</button><button onclick='sendDrawing();'>OK</button>");
1117
- map.openPopup(rmen);
1144
+ var d = drawcontextmenu || "<input type='text' value='${name}' id='dinput' placeholder='name (,icon, layer)'/><br/><button onclick='editPoly(\"${name}\");'>Edit points</button><button onclick='editPoly(\"${name}\",\"drag\");'>Drag</button><button onclick='editPoly(\"${name}\",\"rot\");'>Rotate</button><button onclick='delMarker(\"${name}\",true);'>Delete</button><button onclick='sendDrawing();'>OK</button>";
1145
+ d = d.replace(/\${name}/g,name);
1146
+ if (e.target.value) {
1147
+ for (const item in e.target.value) {
1148
+ d = d.replace(new RegExp("\\${"+item+"}","g"),e.target.value[item]);
1149
+ }
1150
+ }
1151
+ rmen.setContent(d);
1152
+ setImmediate(function() { map.openPopup(rmen) });
1118
1153
  });
1119
1154
  e.layer.bindPopup(name);
1120
1155
 
@@ -1127,7 +1162,7 @@ var addOverlays = function(overlist) {
1127
1162
  else {
1128
1163
  cent = e.layer.getBounds().getCenter();
1129
1164
  }
1130
- var m = {action:"draw", name:name, type:e.shape, layer:"_drawing", options:e.layer.options, radius:e.layer._mRadius, lat:la, lon:lo};
1165
+ var m = {action:"draw", name:name, type:e.shape, layer:"_drawing", options:e.layer.options, radius:e.layer._mRadius, lat:la, lon:lo, drawCount:drawCount};
1131
1166
  if (e.layer.hasOwnProperty("_latlngs")) {
1132
1167
  if (e.layer.options.fill === false) { m.line = e.layer._latlngs; }
1133
1168
  else { m.area = e.layer._latlngs[0]; }
@@ -1139,21 +1174,24 @@ var addOverlays = function(overlist) {
1139
1174
  polygons[name].name = name;
1140
1175
  layers["_drawing"].addLayer(shape.layer);
1141
1176
 
1142
- 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+"\");'>Edit points</button><button onclick='editPoly(\""+name+"\",\"drag\");'>Drag</button><button onclick='editPoly(\""+name+"\",\"rot\");'>Rotate</button><button onclick='delMarker(\""+name+"\",true);'>Delete</button><button onclick='sendDrawing(\""+name+"\");'>OK</button>");
1177
+ var rightmenuMarker = L.popup({offset:[0,-12]}).setContent(drawcontextmenu.replace(/\${name}/g,name).replace(/\${.*?}/g,'') || "<input type='text' autofocus value='"+name+"' id='dinput' placeholder='name (,icon, layer)'/><br/><button onclick='editPoly(\""+name+"\");'>Edit points</button><button onclick='editPoly(\""+name+"\",\"drag\");'>Drag</button><button onclick='editPoly(\""+name+"\",\"rot\");'>Rotate</button><button onclick='delMarker(\""+name+"\",true);'>Delete</button><button onclick='sendDrawing(\""+name+"\");'>OK</button>");
1143
1178
  if (e.layer.options.fill === false && navigator.onLine) {
1144
- rightmenuMarker = L.popup({offset:[0,-12]}).setContent("<input type='text' autofocus value='"+name+"' id='dinput' placeholder='name (,icon, layer)'/><br/><button onclick='editPoly(\""+name+"\");'>Edit points</button><button onclick='editPoly(\""+name+"\",\"drag\");'>Drag</button><button onclick='editPoly(\""+name+"\",\"rot\");'>Rotate</button><button onclick='delMarker(\""+name+"\",true);'>Delete</button><button onclick='sendRoute(\""+name+"\");'>Route</button><button onclick='sendDrawing(\""+name+"\");'>OK</button>");
1179
+ rightmenuMarker = L.popup({offset:[0,-12]}).setContent(drawcontextmenu.replace(/\${name}/g,name).replace(/\${.*?}/g,'') || "<input type='text' autofocus value='"+name+"' id='dinput' placeholder='name (,icon, layer)'/><br/><button onclick='editPoly(\""+name+"\");'>Edit points</button><button onclick='editPoly(\""+name+"\",\"drag\");'>Drag</button><button onclick='editPoly(\""+name+"\",\"rot\");'>Rotate</button><button onclick='delMarker(\""+name+"\",true);'>Delete</button><button onclick='sendRoute(\""+name+"\");'>Route</button><button onclick='sendDrawing(\""+name+"\");'>OK</button>");
1145
1180
  }
1146
1181
  rightmenuMarker.setLatLng(cent);
1147
1182
  setTimeout(function() {map.openPopup(rightmenuMarker)},25);
1148
1183
  });
1149
1184
 
1150
- sendDrawing = function(n) {
1151
- var thing = document.getElementById('dinput').value;
1185
+ sendDrawing = function(n,v,a) {
1186
+ var thing = document.getElementById('dinput')?.value || n;
1152
1187
  map.closePopup();
1153
1188
  shape.m.name = thing;
1154
1189
  shape.layer.bindPopup(thing);
1155
1190
  delMarker(n,true);
1156
-
1191
+ if (v) {
1192
+ shape.layer.value = v;
1193
+ shape.m.value = v;
1194
+ }
1157
1195
  polygons[thing] = shape.layer;
1158
1196
  polygons[thing].lay = "_drawing";
1159
1197
  polygons[thing].name = thing;
@@ -1224,7 +1262,8 @@ var addOverlays = function(overlist) {
1224
1262
  numbers.push(current);
1225
1263
  current = 0;
1226
1264
  shift = 0;
1227
- } else {
1265
+ }
1266
+ else {
1228
1267
  shift += 5;
1229
1268
  }
1230
1269
  }
@@ -1250,6 +1289,8 @@ var addOverlays = function(overlist) {
1250
1289
  sendDrawing(n);
1251
1290
  });
1252
1291
  }
1292
+
1293
+ changeDrawColour("#4040F0"); // Set default drawing color to blue on start
1253
1294
  }
1254
1295
 
1255
1296
  // Add the countries (world-110m) for offline use
@@ -1346,10 +1387,11 @@ var addOverlays = function(overlist) {
1346
1387
  if (!inIframe) { layercontrol.addTo(map); }
1347
1388
  else { showLayerMenu = false;}
1348
1389
 
1390
+ // Add optional mouse co-ordinates display
1349
1391
  var coords = L.control.mouseCoordinate({position:"bottomleft"});
1350
1392
 
1351
1393
  // Add an optional legend
1352
- var legend = L.control({ position: "bottomleft" });
1394
+ var legend = L.control({position:"bottomleft"});
1353
1395
 
1354
1396
  // Add the dialog box for messages
1355
1397
  // var dialogue = L.control.dialog({initOpen:false, size:[600,400], anchor:[50,150]}).addTo(map);
@@ -1382,9 +1424,11 @@ helpText += 'The default is that only visible layers add to the heatmap.</p>';
1382
1424
  // Delete a marker or shape (and notify websocket)
1383
1425
  var delMarker = function(dname,note) {
1384
1426
  if (note) { map.closePopup(); }
1427
+ var pol = false;
1385
1428
  if (typeof polygons[dname] != "undefined") {
1386
1429
  layers[polygons[dname].lay].removeLayer(polygons[dname]);
1387
1430
  delete polygons[dname];
1431
+ pol = true;
1388
1432
  }
1389
1433
  if (typeof polygons[dname+"_"] != "undefined") {
1390
1434
  layers[polygons[dname+"_"].lay].removeLayer(polygons[dname+"_"]);
@@ -1400,7 +1444,10 @@ var delMarker = function(dname,note) {
1400
1444
  delete markers[dname];
1401
1445
  }
1402
1446
  delete allData[dname];
1403
- if (note) { ws.send(JSON.stringify({action:"delete", name:dname, deleted:true})); }
1447
+ if (note) {
1448
+ if (pol === true) { ws.send(JSON.stringify({action:"drawdelete", name:dname, deleted:true})); }
1449
+ else { ws.send(JSON.stringify({action:"delete", name:dname, deleted:true})); }
1450
+ }
1404
1451
  }
1405
1452
 
1406
1453
  var editPoly = function(pname,fun) {
@@ -1419,6 +1466,7 @@ var editPoly = function(pname,fun) {
1419
1466
  lo = e.target._latlng.lng;
1420
1467
  }
1421
1468
  var m = {action:"draw", name:pname, layer:polygons[pname].lay, options:e.target.options, radius:e.target._mRadius, lat:la, lon:lo};
1469
+ if (e.target.value) { m.value = e.target.value; }
1422
1470
  if (e.target.hasOwnProperty("_latlngs")) {
1423
1471
  if (e.target.options.fill === false) { m.line = e.target._latlngs; }
1424
1472
  else { m.area = e.target._latlngs[0]; }
@@ -1427,7 +1475,6 @@ var editPoly = function(pname,fun) {
1427
1475
  })
1428
1476
  }
1429
1477
 
1430
-
1431
1478
  var rangerings = function(latlng, options) {
1432
1479
  options = L.extend({
1433
1480
  ranges: [250,500,750,1000],
@@ -1448,7 +1495,7 @@ var rangerings = function(latlng, options) {
1448
1495
  return rings;
1449
1496
  }
1450
1497
 
1451
- // the MAIN add something to map function
1498
+ // the MAIN add marker or shape to map function
1452
1499
  function setMarker(data) {
1453
1500
  var rightmenu = function(m) {
1454
1501
  m.on('click', function(e) {
@@ -1465,9 +1512,13 @@ function setMarker(data) {
1465
1512
  rightcontext = "<button onclick='editPoly(\""+data.name+"\");'>Edit</button><button onclick='delMarker(\""+data.name+"\",true);'>Delete</button>";
1466
1513
  }
1467
1514
  if ((data.contextmenu !== undefined) && (typeof data.contextmenu === "string")) {
1468
- rightcontext = data.contextmenu.replace(/\$name/g,'"'+data.name+'"');
1515
+ rightcontext = data.contextmenu.replace(/\${name}/g,data.name);
1469
1516
  delete data.contextmenu;
1470
1517
  }
1518
+ for (const item in allData[data.name].value) {
1519
+ rightcontext = rightcontext.replace(new RegExp("\\${"+item+"}","g"),allData[data.name].value[item]);
1520
+ }
1521
+ rightcontext = rightcontext.replace(/\${.*?}/g,'')
1471
1522
  if (rightcontext.length > 0) {
1472
1523
  var rightmenuMarker = L.popup({offset:[0,-12]}).setContent("<b>"+data.name+"</b><br/>"+rightcontext);
1473
1524
  if (hiderightclick !== true) {
@@ -1496,15 +1547,15 @@ function setMarker(data) {
1496
1547
 
1497
1548
  var ll;
1498
1549
  var lli = null;
1499
- var opt = {};
1500
- opt.color = data.color ?? data.lineColor ?? "#910000";
1501
- opt.fillColor = data.fillColor ?? "#910000";
1502
- opt.stroke = (data.hasOwnProperty("stroke")) ? data.stroke : true;
1503
- opt.weight = data.weight;
1504
- opt.opacity = data.opacity;
1505
- opt.fillOpacity = data.fillOpacity;
1550
+ var opt = data.options || {};
1551
+ opt.color = opt.color ?? data.color ?? data.lineColor ?? "#910000";
1552
+ opt.fillColor = opt.fillColor ?? data.fillColor ?? "#910000";
1553
+ opt.stroke = opt.stroke ?? (data.hasOwnProperty("stroke")) ? data.stroke : true;
1554
+ opt.weight = opt.weight ?? data.weight;
1555
+ opt.opacity = opt.opacity ?? data.opacity;
1556
+ opt.fillOpacity = opt.fillOpacity ?? data.fillOpacity;
1506
1557
  opt.clickable = (data.hasOwnProperty("clickable")) ? data.clickable : false;
1507
- opt.fill = (data.hasOwnProperty("fill")) ? data.fill : true;
1558
+ opt.fill = opt.fill ?? (data.hasOwnProperty("fill")) ? data.fill : true;
1508
1559
  if (data.hasOwnProperty("dashArray")) { opt.dashArray = data.dashArray; }
1509
1560
  if (opt.fillOpacity === undefined) { opt.fillOpacity = 0.2; }
1510
1561
  if (opt.opacity === undefined) { opt.opacity = 1; }
@@ -1571,29 +1622,23 @@ function setMarker(data) {
1571
1622
 
1572
1623
  if (typeof polygons[data.name] != "undefined") { layers[lay].removeLayer(polygons[data.name]); }
1573
1624
 
1625
+ if (data.hasOwnProperty("drawCount")) { drawCount = data.drawCount; }
1626
+ // Draw lines
1574
1627
  if (data.hasOwnProperty("line") && Array.isArray(data.line)) {
1575
1628
  delete opt.fill;
1576
1629
  if (!data.hasOwnProperty("weight")) { opt.weight = 3; } //Standard settings different for lines
1577
1630
  if (!data.hasOwnProperty("opacity")) { opt.opacity = 0.8; }
1578
1631
  var polyln = L.polyline(data.line, opt);
1579
1632
  polygons[data.name] = rightmenu(polyln);
1580
- if (data.hasOwnProperty("fly") && data.fly === true) {
1581
- map.flyToBounds(polygons[data.name].getBounds(),{padding:[50,50]})
1582
- } else if (data.hasOwnProperty("fit") && data.fit === true) {
1583
- map.fitBounds(polygons[data.name].getBounds(),{padding:[50,50]})
1584
- }
1585
1633
  }
1634
+ // Draw Areas
1586
1635
  else if (data.hasOwnProperty("area") && Array.isArray(data.area)) {
1587
1636
  var polyarea;
1588
1637
  if (data.area.length === 2) { polyarea = L.rectangle(data.area, opt); }
1589
1638
  else { polyarea = L.polygon(data.area, opt); }
1590
1639
  polygons[data.name] = rightmenu(polyarea);
1591
- if (data.hasOwnProperty("fly") && data.fly === true) {
1592
- map.flyToBounds(polygons[data.name].getBounds(),{padding:[50,50]})
1593
- } else if (data.hasOwnProperty("fit") && data.fit === true) {
1594
- map.fitBounds(polygons[data.name].getBounds(),{padding:[50,50]})
1595
- }
1596
1640
  }
1641
+ // Draw Great circles
1597
1642
  if (data.hasOwnProperty("greatcircle") && Array.isArray(data.greatcircle) && data.greatcircle.length === 2) {
1598
1643
  delete opt.fill;
1599
1644
  opt.vertices = opt.vertices || 20;
@@ -1601,25 +1646,20 @@ function setMarker(data) {
1601
1646
  if (!data.hasOwnProperty("opacity")) { opt.opacity = 0.8; }
1602
1647
  var greatc = L.Polyline.Arc(data.greatcircle[0], data.greatcircle[1], opt);
1603
1648
  var aml = new L.Wrapped.Polyline(greatc._latlngs, opt);
1604
-
1605
1649
  polygons[data.name] = rightmenu(aml);
1606
- if (data.hasOwnProperty("fly") && data.fly === true) {
1607
- map.flyToBounds(polygons[data.name].getBounds(),{padding:[50,50]})
1608
- } else if (data.hasOwnProperty("fit") && data.fit === true) {
1609
- map.fitBounds(polygons[data.name].getBounds(),{padding:[50,50]})
1610
- }
1611
1650
  }
1651
+ // Draw error ellipses
1612
1652
  else if (data.hasOwnProperty("sdlat") && data.hasOwnProperty("sdlon")) {
1613
1653
  if (!data.hasOwnProperty("iconColor")) { opt.color = "blue"; } //different standard Color Settings
1614
1654
  if (!data.hasOwnProperty("fillColor")) { opt.fillColor = "blue"; }
1615
1655
  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);
1616
1656
  polygons[data.name] = rightmenu(ellipse);
1617
1657
  }
1658
+ // Draw circles and ellipses
1618
1659
  else if (data.hasOwnProperty("radius")) {
1619
1660
  if (data.hasOwnProperty("lat") && data.hasOwnProperty("lon")) {
1620
1661
  var polycirc;
1621
1662
  if (Array.isArray(data.radius)) {
1622
- //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);
1623
1663
  polycirc = L.ellipse(new L.LatLng((data.lat*1), (data.lon*1)), [data.radius[0], data.radius[1]], data.tilt || 0, opt);
1624
1664
  }
1625
1665
  else {
@@ -1632,26 +1672,41 @@ function setMarker(data) {
1632
1672
  }
1633
1673
  }
1634
1674
  }
1675
+ // Draw arcs (and range rings)
1635
1676
  else if (data.hasOwnProperty("arc")) {
1636
1677
  if (data.hasOwnProperty("lat") && data.hasOwnProperty("lon")) {
1637
1678
  polygons[data.name] = rangerings(new L.LatLng((data.lat*1), (data.lon*1)), data.arc);
1638
1679
  }
1639
1680
  }
1681
+ // Draw a geojson "shape"
1640
1682
  else if (data.hasOwnProperty("geojson")) {
1641
1683
  doGeojson(data.name,data.geojson,(data.layer || "unknown"),opt);
1642
1684
  }
1643
1685
 
1686
+ // If we created a shape then apply some generic things to it
1644
1687
  if (polygons[data.name] !== undefined) {
1688
+ // Set the layer
1645
1689
  polygons[data.name].lay = lay;
1690
+ // if clickable then add popup
1646
1691
  if (opt.clickable === true) {
1647
1692
  var words = "<b>"+data.name+"</b>";
1648
1693
  if (data.popup) { words = words + "<br/>" + data.popup; }
1649
- polygons[data.name].bindPopup(words, {autoClose:false, closeButton:true, closeOnClick:false, minWidth:200});
1694
+ polygons[data.name].bindPopup(words, {autoClose:false, closeButton:true, closeOnClick:true, minWidth:200});
1650
1695
  }
1651
- //polygons[data.name] = rightmenu(polygons[data.name]); // DCJ Investigate
1696
+ // add a tooltip (if supplied)
1697
+ if (data.hasOwnProperty("tooltip")) { polygons[data.name].bindTooltip(data.tooltip); }
1698
+ // add to the layers
1652
1699
  layers[lay].addLayer(polygons[data.name]);
1700
+ // fly or fit to the bounds if required
1701
+ if (data.hasOwnProperty("fly") && data.fly === true) {
1702
+ map.flyToBounds(polygons[data.name].getBounds(),{padding:[50,50]})
1703
+ }
1704
+ else if (data.hasOwnProperty("fit") && data.fit === true) {
1705
+ map.fitBounds(polygons[data.name].getBounds(),{padding:[50,50]})
1706
+ }
1653
1707
  }
1654
1708
 
1709
+ // Now handle the markers
1655
1710
  if (typeof data.coordinates == "object") { ll = new L.LatLng(data.coordinates[1],data.coordinates[0]); }
1656
1711
  else if (data.hasOwnProperty("position") && data.position.hasOwnProperty("lat") && data.position.hasOwnProperty("lon")) {
1657
1712
  data.lat = data.position.lat*1;
@@ -1691,7 +1746,7 @@ function setMarker(data) {
1691
1746
  if (data.draggable === true) { drag = true; }
1692
1747
 
1693
1748
  if (data.hasOwnProperty("icon")) {
1694
- var dir = parseFloat(data.hdg ?? data.heading ?? data.bearing ?? "0");
1749
+ var dir = parseFloat(data.track ?? data.hdg ?? data.heading ?? data.bearing ?? "0") + map.getBearing();
1695
1750
  if (data.icon === "ship") {
1696
1751
  marker = L.boatMarker(ll, {
1697
1752
  title: data.name,
@@ -1792,6 +1847,17 @@ function setMarker(data) {
1792
1847
  });
1793
1848
  marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
1794
1849
  }
1850
+ else if (data.icon === "sensor") {
1851
+ data.iconColor = data.iconColor || "#F39C12";
1852
+ icon = '<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg"><path fill="'+data.iconColor+'" d="M 478.281 5.437 L 367.741 118.227 L 367.741 84.075 C 367.741 38.352 344.315 1.298 315.417 1.298 L 53.768 1.298 C 24.87 1.298 1.434 38.352 1.434 84.075 L 1.434 415.183 C 1.434 460.893 24.87 497.959 53.768 497.959 L 315.417 497.959 C 344.315 497.959 367.741 460.893 367.741 415.183 L 367.741 381.031 L 478.281 493.808 C 490.714 504.155 498.566 486.571 498.566 476.224 L 498.566 21.993 C 498.566 11.646 491.37 -6.979 478.281 5.437 Z M 341.573 415.183 C 341.573 438.044 329.86 456.571 315.417 456.571 L 53.768 456.571 C 39.314 456.571 27.612 438.044 27.612 415.183 L 27.612 84.075 C 27.612 61.226 39.314 42.687 53.768 42.687 L 315.417 42.687 C 329.86 42.687 341.573 61.226 341.573 84.075 L 341.573 415.183 Z M 472.398 438.975 L 367.741 332.406 L 367.741 166.853 L 472.398 60.27 L 472.398 438.975 Z" style="transform-origin: 250.000025px 249.628505px;" transform="matrix(0, -1, 1, 0, -0.000013709068, 0.000009864569)"/></svg>';
1853
+ var svgcam = "data:image/svg+xml;base64," + btoa(icon);
1854
+ myMarker = L.divIcon({
1855
+ className:"camicon",
1856
+ iconAnchor: [12, 12],
1857
+ html:'<img src="'+svgcam+'" style="width:24px; height:24px; -webkit-transform:rotate('+dir+'deg); -moz-transform:rotate('+dir+'deg);"/>',
1858
+ });
1859
+ marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
1860
+ }
1795
1861
  else if (data.icon === "arrow") {
1796
1862
  data.iconColor = data.iconColor || "black";
1797
1863
  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">';
@@ -1897,7 +1963,7 @@ function setMarker(data) {
1897
1963
  else if (data.icon === "earthquake") {
1898
1964
  marker = L.marker(ll, { icon: L.divIcon({ className: 'circle e', iconSize: [data.mag*5, data.mag*5] }), title: data.name, draggable:drag });
1899
1965
  }
1900
- else if (data.icon.match(/^:.*:$/g)) {
1966
+ else if (data.icon.match(/^:.*:$/g)) { // emoji icon :smile:
1901
1967
  var em = emojify(data.icon);
1902
1968
  var col = data.iconColor ?? "#910000";
1903
1969
  myMarker = L.divIcon({
@@ -1908,7 +1974,7 @@ function setMarker(data) {
1908
1974
  marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
1909
1975
  labelOffset = [12,-4];
1910
1976
  }
1911
- else if (data.icon.match(/^https?:.*$|^\//)) {
1977
+ else if (data.icon.match(/^https?:.*$|^\//)) { // web url icon https://...
1912
1978
  var sz = data.iconSize ?? 32;
1913
1979
  myMarker = L.icon({
1914
1980
  iconUrl: data.icon,
@@ -1920,7 +1986,7 @@ function setMarker(data) {
1920
1986
  labelOffset = [sz/2-4,-4];
1921
1987
  delete data.iconSize;
1922
1988
  }
1923
- else if (data.icon.substr(0,3) === "fa-") {
1989
+ else if (data.icon.substr(0,3) === "fa-") { // fa icon
1924
1990
  var col = data.iconColor ?? "#910000";
1925
1991
  var imod = "";
1926
1992
  if (data.icon.indexOf(" ") === -1) { imod = "fa-2x "; }
@@ -1934,7 +2000,7 @@ function setMarker(data) {
1934
2000
  marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
1935
2001
  labelOffset = [8,-8];
1936
2002
  }
1937
- else if (data.icon.substr(0,3) === "wi-") {
2003
+ else if (data.icon.substr(0,3) === "wi-") { // weather icon
1938
2004
  var col = data.iconColor ?? "#910000";
1939
2005
  var imod = "";
1940
2006
  if (data.icon.indexOf(" ") === -1) { imod = "wi-2x "; }
@@ -1949,7 +2015,7 @@ function setMarker(data) {
1949
2015
  labelOffset = [16,-16];
1950
2016
  }
1951
2017
  else {
1952
- myMarker = L.VectorMarkers.icon({
2018
+ myMarker = L.VectorMarkers.icon({ // default - fa-icon in a marker shape
1953
2019
  icon: data.icon ?? "circle",
1954
2020
  markerColor: (data.iconColor ?? "#910000"),
1955
2021
  prefix: 'fa',
@@ -1959,7 +2025,7 @@ function setMarker(data) {
1959
2025
  labelOffset = [6,-6];
1960
2026
  }
1961
2027
  }
1962
- else if (data.hasOwnProperty("SIDC")) {
2028
+ else if (data.hasOwnProperty("SIDC")) { // NATO mil2525 icons
1963
2029
  // "SIDC":"SFGPU------E***","name":"1.C2 komp","fullname":"1.C2 komp/FTS/INSS"
1964
2030
  myMarker = new ms.Symbol( data.SIDC.toUpperCase(), { uniqueDesignation:unescape(encodeURIComponent(data.name)) });
1965
2031
  // Now that we have a symbol we can ask for the echelon and set the symbol size
@@ -1988,7 +2054,7 @@ function setMarker(data) {
1988
2054
  marker = L.marker(ll, { title:data.name, icon:myicon, draggable:drag });
1989
2055
  edgeAware();
1990
2056
  }
1991
- else {
2057
+ else { // Otherwise just a generic map marker pin
1992
2058
  myMarker = L.VectorMarkers.icon({
1993
2059
  icon: "circle",
1994
2060
  markerColor: (data.iconColor ?? "#910000"),
@@ -2033,7 +2099,8 @@ function setMarker(data) {
2033
2099
  }
2034
2100
 
2035
2101
  // tidy up altitude
2036
- if (data.hasOwnProperty("alt")) {
2102
+ if (data.hasOwnProperty("alt")||data.hasOwnProperty("altitude")) {
2103
+ data.alt = data.alt ?? data.altitude;
2037
2104
  var reft = new RegExp('feet|ft','i');
2038
2105
  var refm = new RegExp('metres|m','i');
2039
2106
  if ( reft.test(""+data.alt) ) {
@@ -2047,8 +2114,8 @@ function setMarker(data) {
2047
2114
  }
2048
2115
  }
2049
2116
 
2050
- // remove icon from list of properties, then add all others to popup
2051
- if (data.hasOwnProperty("SIDC") && data.hasOwnProperty("options")) { delete data.options; }
2117
+ // remove items from list of properties, then add all others to popup
2118
+ if (data.hasOwnProperty("options")) { delete data.options; }
2052
2119
  if (data.hasOwnProperty("icon")) { delete data.icon; }
2053
2120
  if (data.hasOwnProperty("iconColor")) { delete data.iconColor; }
2054
2121
  if (data.hasOwnProperty("photourl")) {
@@ -2076,13 +2143,14 @@ function setMarker(data) {
2076
2143
  if (!Array.isArray(data.weblink) || !data.weblink.length) {
2077
2144
  if (typeof data.weblink === "string") {
2078
2145
  words += "<b><a href='"+ data.weblink + "' target='_new'>more information...</a></b><br/>";
2079
- } else {
2146
+ }
2147
+ else {
2080
2148
  var tgt = data.weblink.target || "_new";
2081
2149
  words += "<b><a href='"+ data.weblink.url + "' target='"+ tgt + "'>" + data.weblink.name + "</a></b><br/>";
2082
2150
  }
2083
2151
  }
2084
2152
  else {
2085
- data.weblink.forEach(function(weblink){
2153
+ data.weblink.forEach(function(weblink) {
2086
2154
  if (typeof weblink === "string") {
2087
2155
  words += "<b><a href='"+ weblink + "' target='_new'>more information...</a></b><br/>";
2088
2156
  }
@@ -2124,10 +2192,11 @@ function setMarker(data) {
2124
2192
  }
2125
2193
  }
2126
2194
 
2195
+ // Add right click contextmenu
2127
2196
  marker = rightmenu(marker);
2128
2197
 
2129
- // Add any remaining properties to the info box
2130
- var llc = data.lineColor || data.color;
2198
+ // Delete more already handled properties
2199
+ var llc = data.lineColor ?? data.color;
2131
2200
  delete data.lat;
2132
2201
  delete data.lon;
2133
2202
  if (data.arc) { delete data.arc; }
@@ -2143,15 +2212,17 @@ function setMarker(data) {
2143
2212
  if (data.hasOwnProperty("fillColor")) { delete data.fillColor; }
2144
2213
  if (data.hasOwnProperty("radius")) { delete data.radius; }
2145
2214
  if (data.hasOwnProperty("greatcircle")) { delete data.greatcircle; }
2215
+
2216
+ // then any remaining properties to the info box
2146
2217
  if (data.popup) { words = data.popup; }
2147
2218
  else {
2148
2219
  words += '<table>';
2149
2220
  for (var i in data) {
2150
2221
  if ((i != "name") && (i != "length") && (i != "clickable")) {
2151
2222
  if (typeof data[i] === "object") {
2152
- //
2153
2223
  words += '<tr><td>'+ i +'</td><td>' + JSON.stringify(data[i]) + '</td></tr>';
2154
- } else {
2224
+ }
2225
+ else {
2155
2226
  // words += i +" : "+data[i]+"<br/>";
2156
2227
  words += '<tr><td>'+ i +'</td><td>' + data[i] + '</td></tr>';
2157
2228
  }
@@ -2160,9 +2231,9 @@ function setMarker(data) {
2160
2231
  words += '<tr><td>lat, lon</td><td>'+ marker.getLatLng().toString().replace('LatLng(','').replace(')','') + '</td></tr>';
2161
2232
  words += '</table>';
2162
2233
  }
2163
- 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;
2234
+ words = "<b>"+data.name+"</b><br/>" + words.replace(/\${name}/g,data.name); //"<button style=\"border-radius:4px; float:right; background-color:lightgrey;\" onclick='popped=false;popmark.closePopup();'>X</button><br/>" + words;
2164
2235
  var wopt = {autoClose:false, closeButton:true, closeOnClick:false, minWidth:200};
2165
- if (words.indexOf('<video ') >=0 || words.indexOf('<img ') >=0 ) { wopt.maxWidth="640"; }
2236
+ if (words.indexOf('<video ') >=0 || words.indexOf('<img ') >=0 ) { wopt.maxWidth="640"; } // make popup wider if it has an image or video
2166
2237
  if (!data.hasOwnProperty("clickable") && data.clickable != false) {
2167
2238
  marker.bindPopup(words, wopt);
2168
2239
  marker._popup.dname = data.name;
@@ -2180,11 +2251,15 @@ function setMarker(data) {
2180
2251
  }
2181
2252
  markers[data.name] = marker;
2182
2253
  layers[lay].addLayer(marker);
2183
- var track;
2184
- if (data.track !== undefined) { track = data.track; }
2185
- else if (data.hdg !== undefined) { track = data.hdg; }
2186
- else if (data.heading !== undefined) { track = data.heading; }
2187
- else if (data.bearing !== undefined) { track = data.bearing; }
2254
+
2255
+ // var track;
2256
+ // if (data.track !== undefined) { track = data.track; }
2257
+ // else if (data.hdg !== undefined) { track = data.hdg; }
2258
+ // else if (data.heading !== undefined) { track = data.heading; }
2259
+ // else if (data.bearing !== undefined) { track = data.bearing; }
2260
+
2261
+ // Now add any leader lines
2262
+ var track = data.track ?? data.hdg ?? data.heading ?? data.bearing;
2188
2263
  if (track != undefined) { // if there is a heading
2189
2264
  if (data.speed != null && data.length === undefined) { // and a speed - lets convert to a leader length
2190
2265
  data.length = parseFloat(data.speed || "0") * 60;
@@ -2217,7 +2292,8 @@ function setMarker(data) {
2217
2292
  var x3 = x + Math.cos((90-angle-data.accuracy)/180*Math.PI)*lengthAsDegrees/Math.cos(y/180*Math.PI);
2218
2293
  var ll3 = new L.LatLng(y3,x3);
2219
2294
  polygon = L.polygon([ ll1, ll2, ll3 ], {weight:2, color:llc||'#900', fillOpacity:0.06, clickable:false});
2220
- } else {
2295
+ }
2296
+ else {
2221
2297
  var ya = y + Math.sin((90-angle)/180*Math.PI)*lengthAsDegrees;
2222
2298
  var xa = x + Math.cos((90-angle)/180*Math.PI)*lengthAsDegrees/Math.cos(y/180*Math.PI);
2223
2299
  var lla = new L.LatLng(ya,xa);
@@ -2246,7 +2322,7 @@ function setMarker(data) {
2246
2322
 
2247
2323
  // handle any incoming COMMANDS to control the map remotely
2248
2324
  function doCommand(cmd) {
2249
- // console.log("COMMAND",cmd);
2325
+ //console.log("COMMAND",cmd);
2250
2326
  if (cmd.init && cmd.hasOwnProperty("maplist")) {
2251
2327
  //basemaps = {};
2252
2328
  addBaseMaps(cmd.maplist,cmd.layer);
@@ -2275,10 +2351,6 @@ function doCommand(cmd) {
2275
2351
  else { panit = false; }
2276
2352
  document.getElementById("panit").checked = panit;
2277
2353
  }
2278
- if (cmd.hasOwnProperty("hiderightclick")) {
2279
- if (cmd.hiderightclick == "true" || cmd.hiderightclick == true) { hiderightclick = true; }
2280
- else { hiderightclick = false; }
2281
- }
2282
2354
  if (cmd.hasOwnProperty("showmenu")) {
2283
2355
  if ((cmd.showmenu === "hide") && (showUserMenu === true)) {
2284
2356
  showUserMenu = false;
@@ -2371,10 +2443,18 @@ function doCommand(cmd) {
2371
2443
  if (trackMeButton !== undefined) { trackMeButton.state('track-on'); }
2372
2444
  }
2373
2445
  }
2446
+ if (cmd.hasOwnProperty("hiderightclick")) {
2447
+ if (cmd.hiderightclick == "true" || cmd.hiderightclick == true) { hiderightclick = true; }
2448
+ else { hiderightclick = false; }
2449
+ }
2374
2450
  if (cmd.hasOwnProperty("contextmenu")) {
2375
2451
  if (typeof cmd.contextmenu === "string") {
2376
2452
  addmenu = cmd.contextmenu;
2377
- rightmenuMap.setContent(addmenu);
2453
+ }
2454
+ }
2455
+ if (cmd.hasOwnProperty("drawcontextmenu")) {
2456
+ if (typeof cmd.drawcontextmenu === "string") {
2457
+ drawcontextmenu = cmd.drawcontextmenu;
2378
2458
  }
2379
2459
  }
2380
2460
  if (cmd.hasOwnProperty("allowFileDrop")) {
@@ -2467,57 +2547,69 @@ function doCommand(cmd) {
2467
2547
  }
2468
2548
  }
2469
2549
  // Add a new geojson overlay layer
2470
- if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("geojson") ) {
2550
+ if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("geojson")) {
2471
2551
  if (overlays.hasOwnProperty(cmd.map.overlay)) {
2472
2552
  map.removeLayer(overlays[cmd.map.overlay]);
2473
2553
  existsalready = true;
2474
2554
  }
2475
- var opt = cmd.map.opt || { style:function(feature) {
2476
- var st = { stroke:true, weight:2, fill:true };
2477
- if (feature.hasOwnProperty("properties")) {
2478
- st.color = feature.properties.color||feature.properties.roofColor||"black";
2479
- if (feature.properties.hasOwnProperty("color")) { delete feature.properties.color; }
2480
- if (feature.properties.hasOwnProperty("roofColor")) { delete feature.properties.roofColor; }
2555
+ try {
2556
+ var opt = cmd.map.opt || {};
2557
+ if (opt.hasOwnProperty("style")) { opt.style = new Function('return ' + opt.style)(); }
2558
+ else {
2559
+ opt.style = function(feature) {
2560
+ var st = { stroke:true, weight:2, fill:true };
2561
+ if (feature.hasOwnProperty("properties")) {
2562
+ st.color = feature.properties.color||feature.properties.roofColor||"black";
2563
+ if (feature.properties.hasOwnProperty("color")) { delete feature.properties.color; }
2564
+ if (feature.properties.hasOwnProperty("roofColor")) { delete feature.properties.roofColor; }
2565
+ }
2566
+ if (feature.hasOwnProperty("properties") && feature.properties.hasOwnProperty('style')) {
2567
+ if (feature.properties.style.hasOwnProperty('stroke')) {
2568
+ st.color = feature.properties.style.stroke;
2569
+ }
2570
+ if (feature.properties.style.hasOwnProperty('stroke-width')) {
2571
+ st.weight = feature.properties.style["stroke-width"];
2572
+ }
2573
+ if (feature.properties.style.hasOwnProperty('stroke-opacity')) {
2574
+ st.opacity = feature.properties.style["stroke-opacity"];
2575
+ }
2576
+ if (feature.properties.style.hasOwnProperty('fill')) {
2577
+ if (feature.properties.style.fill == "none") { st.fill = false; }
2578
+ else { st.fillColor = feature.properties.style.fill; }
2579
+ }
2580
+ if (feature.properties.style.hasOwnProperty('fill-opacity')) {
2581
+ st.fillOpacity = feature.properties.style["fill-opacity"];
2582
+ }
2583
+ }
2584
+ delete feature.properties.style;
2585
+ return st;
2586
+ };
2481
2587
  }
2482
- if (feature.hasOwnProperty("properties") && feature.properties.hasOwnProperty('style')) {
2483
- if (feature.properties.style.hasOwnProperty('stroke')) {
2484
- st.color = feature.properties.style.stroke;
2485
- }
2486
- if (feature.properties.style.hasOwnProperty('stroke-width')) {
2487
- st.weight = feature.properties.style["stroke-width"];
2488
- }
2489
- if (feature.properties.style.hasOwnProperty('stroke-opacity')) {
2490
- st.opacity = feature.properties.style["stroke-opacity"];
2491
- }
2492
- if (feature.properties.style.hasOwnProperty('fill')) {
2493
- if (feature.properties.style.fill == "none") { st.fill = false; }
2494
- else { st.fillColor = feature.properties.style.fill; }
2495
- }
2496
- if (feature.properties.style.hasOwnProperty('fill-opacity')) {
2497
- st.fillOpacity = feature.properties.style["fill-opacity"];
2588
+ if (opt.hasOwnProperty("pointToLayer")) { opt.pointToLayer = new Function('return ' + opt.pointToLayer)(); }
2589
+ if (opt.hasOwnProperty("filter")) { opt.filter = new Function('return ' + opt.filter)(); }
2590
+ if (opt.hasOwnProperty("onEachFeature")) { opt.onEachFeature = new Function('return ' + opt.onEachFeature)(); }
2591
+ else {
2592
+ opt.onEachFeature = function (f,l) {
2593
+ var pw = '<pre>'+JSON.stringify(f.properties,null,' ').replace(/[\{\}"]/g,'')+'</pre>';
2594
+ if (pw.length > 11) { l.bindPopup(pw); }
2595
+ if (cmd.map.hasOwnProperty("clickable") && cmd.map.clickable === true) {
2596
+ l.on('click', function (e) {
2597
+ ws.send(JSON.stringify({action:"clickgeo",name:cmd.map.overlay,type:f.type,properties:f.properties,geometry:f.geometry}));
2598
+ });
2599
+ }
2498
2600
  }
2499
2601
  }
2500
- delete feature.properties.style;
2501
- return st;
2502
- }};
2503
- opt.onEachFeature = function (f,l) {
2504
- var pw = '<pre>'+JSON.stringify(f.properties,null,' ').replace(/[\{\}"]/g,'')+'</pre>';
2505
- if (pw.length > 11) { l.bindPopup(pw); }
2506
- if (cmd.map.hasOwnProperty("clickable") && cmd.map.clickable === true) {
2507
- l.on('click', function (e) {
2508
- ws.send(JSON.stringify({action:"clickgeo",name:cmd.map.overlay,type:f.type,properties:f.properties,geometry:f.geometry}));
2509
- });
2602
+ overlays[cmd.map.overlay] = L.geoJson(cmd.map.geojson,opt);
2603
+ if (!existsalready) {
2604
+ layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
2510
2605
  }
2606
+ if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
2607
+ map.addLayer(overlays[cmd.map.overlay]);
2608
+ }
2609
+ if (cmd.map.hasOwnProperty("fly") && (cmd.map.fly === true)) { map.flyToBounds(overlays[cmd.map.overlay].getBounds()); }
2610
+ else if (cmd.map.hasOwnProperty("fit") && (cmd.map.fit === true)) { map.fitBounds(overlays[cmd.map.overlay].getBounds()); }
2511
2611
  }
2512
- overlays[cmd.map.overlay] = L.geoJson(cmd.map.geojson,opt);
2513
- if (!existsalready) {
2514
- layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
2515
- }
2516
- if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
2517
- map.addLayer(overlays[cmd.map.overlay]);
2518
- }
2519
- if (cmd.map.hasOwnProperty("fly") && (cmd.map.fly === true)) { map.flyToBounds(overlays[cmd.map.overlay].getBounds()); }
2520
- else if (cmd.map.hasOwnProperty("fit") && (cmd.map.fit === true)) { map.fitBounds(overlays[cmd.map.overlay].getBounds()); }
2612
+ catch(e) { console.log(e); }
2521
2613
  }
2522
2614
  // Add a new NVG XML overlay layer
2523
2615
  if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("nvg") ) {
@@ -2660,6 +2752,29 @@ function doCommand(cmd) {
2660
2752
  if (cmd.map.hasOwnProperty("fly") && cmd.map.fly === true) { map.flyToBounds(overlays[cmd.map.overlay].getBounds()); }
2661
2753
  else if (cmd.map.hasOwnProperty("fit") && cmd.map.fit === true) { map.fitBounds(overlays[cmd.map.overlay].getBounds()); }
2662
2754
  }
2755
+ // Add a new ESRI feature layer
2756
+ if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("esri") ) {
2757
+ try {
2758
+ if (overlays.hasOwnProperty(cmd.map.overlay)) {
2759
+ overlays[cmd.map.overlay].removeFrom(map);
2760
+ existsalready = true;
2761
+ }
2762
+ var opt = {};
2763
+ if (cmd.map.hasOwnProperty("opt")) { opt = cmd.map.opt; }
2764
+ if (opt.hasOwnProperty("style")) { opt.style = new Function('return ' + opt.style)(); }
2765
+ if (opt.hasOwnProperty("pointToLayer")) { opt.pointToLayer = new Function('return ' + opt.pointToLayer)(); }
2766
+ if (opt.hasOwnProperty("onEachFeature")) { opt.onEachFeature = new Function('return ' + opt.onEachFeature)(); }
2767
+ opt.url = cmd.map.esri;
2768
+ overlays[cmd.map.overlay] = L.esri.featureLayer(opt);
2769
+ if (!existsalready) {
2770
+ layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
2771
+ }
2772
+ if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
2773
+ overlays[cmd.map.overlay].addTo(map);
2774
+ }
2775
+ // NOTE can't fit or fly to bounds as they keep reloading
2776
+ } catch(e) { console.log(e); }
2777
+ }
2663
2778
  // Add a new TOPOJSON overlay layer
2664
2779
  if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("topojson") ) {
2665
2780
  if (overlays.hasOwnProperty(cmd.map.overlay)) {
@@ -2805,7 +2920,7 @@ function doCommand(cmd) {
2805
2920
  }
2806
2921
  }
2807
2922
  }
2808
- // Lock the pan so map can be moved
2923
+ // Lock the pan so map can't be moved
2809
2924
  if (cmd.hasOwnProperty("panlock")) {
2810
2925
  if (cmd.panlock == "true" || cmd.panlock == true) { lockit = true; }
2811
2926
  else { lockit = false; doLock(false); }
@@ -2821,8 +2936,14 @@ function doCommand(cmd) {
2821
2936
  map.setView([clat,clon],czoom);
2822
2937
 
2823
2938
  // Set rotation of map
2824
- if (cmd.hasOwnProperty("rotation") && !isNaN(cmd.rotation)) { map.setBearing(-cmd.rotation); }
2825
-
2939
+ if (cmd.hasOwnProperty("rotation") && !isNaN(cmd.rotation)) {
2940
+ map.setBearing(-cmd.rotation);
2941
+ for (const item in allData) {
2942
+ if (allData[item].hasOwnProperty("hdg") || allData[item].hasOwnProperty("heading") || allData[item].hasOwnProperty("bearing") || allData[item].hasOwnProperty("track")) {
2943
+ setMarker(allData[item]);
2944
+ }
2945
+ }
2946
+ }
2826
2947
  if (cmd.hasOwnProperty("cluster")) {
2827
2948
  clusterAt = cmd.cluster;
2828
2949
  document.getElementById("setclus").value = cmd.cluster;
@@ -2859,15 +2980,29 @@ function doCommand(cmd) {
2859
2980
  if (cmd.bounds.length === 2 && cmd.bounds[0].length === 2 && cmd.bounds[1].length === 2) {
2860
2981
  if (cmd.hasOwnProperty("fly") && cmd.fly === true) {
2861
2982
  map.flyToBounds(cmd.bounds);
2862
- } else {
2983
+ }
2984
+ else {
2863
2985
  map.fitBounds(cmd.bounds);
2864
2986
  }
2865
2987
  }
2866
2988
  }
2989
+ if (cmd.hasOwnProperty("loadStatic")) {
2990
+ console.log("load Static files",JSON.stringify(cmd.loadStatic.fileNames));
2991
+ cmd.loadStatic.fileNames.forEach(fileName => loadStatic(fileName));
2992
+ }
2993
+ if (cmd.hasOwnProperty("customCmd")) {
2994
+ console.log("custom Command",JSON.stringify(cmd.customCmd));
2995
+ try {
2996
+ eval(cmd.customCmd);
2997
+ }
2998
+ catch (e) {
2999
+ console.log("ERR - custom command: ", JSON.stringify(cmd.customCmd));
3000
+ }
3001
+ }
2867
3002
  }
2868
3003
 
2869
3004
  // handle any incoming GEOJSON directly - may style badly
2870
- function doGeojson(n,g,l,o) {
3005
+ function doGeojson(n,g,l,o) { // name, geojson, layer, options
2871
3006
  var lay = l ?? g.name ?? "unknown";
2872
3007
  // if (!basemaps[lay]) {
2873
3008
  var opt = { style: function(feature) {