More TAK msg handling

spots, waypoints, alerts, sensors
This commit is contained in:
Dave Conway-Jones 2023-08-21 16:07:18 +01:00
parent fa242b2887
commit 164c6dcedb
No known key found for this signature in database
GPG Key ID: 1DDB0E91A28C2643
4 changed files with 145 additions and 44 deletions

View File

@ -1,5 +1,6 @@
### Change Log for Node-RED Worldmap ### Change Log for Node-RED Worldmap
- v2.41.1 - Add handling for TAK type spots, waypoints, alerts, sensors
- v2.41.0 - Bump leaflet libs to latest stable (1.9.4) - v2.41.0 - Bump leaflet libs to latest stable (1.9.4)
- v2.40.1 - Fix missing countries overlay when starting disconnected. - v2.40.1 - Fix missing countries overlay when starting disconnected.
- v2.40.0 - Add handling for TAK event points from TAK ingest node. - v2.40.0 - Add handling for TAK event points from TAK ingest node.

View File

@ -13,6 +13,7 @@ Feel free to [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%
### Updates ### Updates
- v2.41.1 - Add handling for TAK type spots, waypoints, alerts, sensors
- v2.41.0 - Bump leaflet libs to latest stable (1.9.4) - v2.41.0 - Bump leaflet libs to latest stable (1.9.4)
- v2.40.1 - Fix missing countries overlay when starting disconnected. - v2.40.1 - Fix missing countries overlay when starting disconnected.
- v2.40.0 - Add handling for TAK event points from TAK ingest node. - v2.40.0 - Add handling for TAK event points from TAK ingest node.

View File

@ -1,6 +1,6 @@
{ {
"name": "node-red-contrib-web-worldmap", "name": "node-red-contrib-web-worldmap",
"version": "2.41.0", "version": "2.41.1",
"description": "A Node-RED node to provide a web page of a world map for plotting things on.", "description": "A Node-RED node to provide a web page of a world map for plotting things on.",
"dependencies": { "dependencies": {
"@turf/bezier-spline": "~6.5.0", "@turf/bezier-spline": "~6.5.0",

View File

@ -289,14 +289,14 @@ function onLocationFound(e) {
setMarker(self); setMarker(self);
} }
if (followMode.accuracy) { if (followMode.accuracy) {
errRing = L.circle(e.latlng, e.accuracy, {color:followMode.color ?? "cyan", weight:3, opacity:0.6, fill:false, clickable:false}); errRing = L.circle(e.latlng, e.accuracy, {color:followMode.color ?? "#00ffff", weight:3, opacity:0.6, fill:false, clickable:false});
errRing.addTo(map); errRing.addTo(map);
// if (e.hasOwnProperty("heading")) { // if (e.hasOwnProperty("heading")) {
// var lengthAsDegrees = e.speed * 60 / 110540; // 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 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 xa = e.latlng.lng + Math.cos((90-e.heading)/180*Math.PI)*lengthAsDegrees;
// var lla = new L.LatLng(ya,xa); // var lla = new L.LatLng(ya,xa);
// L.polygon([ e.latlng, lla ], {color:"cyan", weight:3, opacity:0.5, clickable:false}).addTo(map); // L.polygon([ e.latlng, lla ], {color:"00ffff", weight:3, opacity:0.5, clickable:false}).addTo(map);
// } // }
} }
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)})); 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)}));
@ -770,7 +770,7 @@ map.on('locationerror', onLocationError);
// single right click to add a marker // single right click to add a marker
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)'/>"; 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>'; } if (navigator.onLine) { addmenu += '<br/><a href="https://www.spatialillusions.com/unitgenerator-legacy/" target="_new">MilSymbol SIDC generator</a>'; }
var rightmenuMap = L.popup({keepInView:true, minWidth:260}).setContent(addmenu); 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 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('')}`;
@ -1337,8 +1337,8 @@ var coords = L.control.mouseCoordinate({position:"bottomleft"});
var legend = L.control({ position: "bottomleft" }); var legend = L.control({ position: "bottomleft" });
// Add the dialog box for messages // Add the dialog box for messages
var dialogue = L.control.dialog({initOpen:false, size:[600,400], anchor:[50,150]}).addTo(map); // var dialogue = L.control.dialog({initOpen:false, size:[600,400], anchor:[50,150]}).addTo(map);
dialogue.freeze(); // dialogue.freeze();
var doDialog = function(d) { var doDialog = function(d) {
//console.log("DIALOGUE",d); //console.log("DIALOGUE",d);
@ -1849,7 +1849,7 @@ function setMarker(data) {
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag}); marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
} }
else if (data.icon === "locate") { else if (data.icon === "locate") {
data.iconColor = data.iconColor || "cyan"; data.iconColor = data.iconColor || "#00ffff";
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 = '<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="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="468 0 364 0 364 32 436 32 436 104 468 104" fill="'+data.iconColor+'"/>';
@ -2943,18 +2943,24 @@ function doGeojson(n,g,l,o) {
// handle TAK messages from TAK server tcp - XML->JSON // handle TAK messages from TAK server tcp - XML->JSON
function doTAKjson(p) { function doTAKjson(p) {
//console.log("TAK event",p); //console.log("TAK event",p);
if (p.type.indexOf('a') === 0) { if (p.type.indexOf('a-') === 0 || p.type.indexOf('b-m-p-') === 0 || p.type.indexOf('b-a-o-') === 0 || p.type.indexOf('b-a-g') === 0) {
var d = {}; var d = {};
d.name = p.detail?.contact?.callsign || p.uid;
d.lat = Number(p.point.lat); d.lat = Number(p.point.lat);
d.lon = Number(p.point.lon); d.lon = Number(p.point.lon);
if (p.type.indexOf('a') === 0) {
d.hdg = p.detail?.track?.course;
d.speed = p.detail?.track?.speed;
d.team = p.detail?.__group?.name; d.team = p.detail?.__group?.name;
d.team = d.team + ' <i style="color:' + d.team + '" class="fa fa-square"></i>'; d.team = d.team + ' <i style="color:' + d.team + '" class="fa fa-square"></i>';
d.role = p.detail?.__group?.role; d.role = p.detail?.__group?.role;
}
d.type = p.type; d.type = p.type;
d.remarks = p.detail?.remarks
if (p.detail?.remarks && p.detail.remarks.hasOwnProperty["#text"]) {
d.remarks = p.detail.remarks["#text"];
}
d.uid = p.uid; d.uid = p.uid;
d.name = p.detail?.contact?.callsign || p.uid;
d.hdg = p.detail?.track?.course;
d.speed = p.detail?.track?.speed;
try { try {
var st = (new Date(p.time)).getTime() / 1000; var st = (new Date(p.time)).getTime() / 1000;
@ -2962,10 +2968,11 @@ function doTAKjson(p) {
d.timestamp = (new Date(p.time)).toISOString(); d.timestamp = (new Date(p.time)).toISOString();
d.staletime = (new Date(p.stale)).toISOString(); d.staletime = (new Date(p.stale)).toISOString();
d.ttl = parseInt(et-st); d.ttl = parseInt(et-st);
} catch(e) { console.log(e); } }
catch(e) { console.log(e); }
d.alt = Number(p.point.hae) || 9999999; d.alt = Number(p.point.hae) || 9999999;
if (d.alt === 9999999) { delete d.alt; } if (d.alt === 9999999) { delete d.alt; }
handleCoTtypes(d); handleCoTtypes(d,p);
setMarker(d); setMarker(d);
} }
else { else {
@ -2988,6 +2995,7 @@ function doTAKMCjson(p) {
d.name = p.detail?.contact?.callsign || p.uid; d.name = p.detail?.contact?.callsign || p.uid;
d.hdg = p.detail?.track?.course; d.hdg = p.detail?.track?.course;
d.speed = p.detail?.track?.speed; d.speed = p.detail?.track?.speed;
try { try {
d.timestamp = (new Date(+p.sendTime)).toISOString(); d.timestamp = (new Date(+p.sendTime)).toISOString();
d.staletime = (new Date(+p.staleTime)).toISOString(); d.staletime = (new Date(+p.staleTime)).toISOString();
@ -2995,7 +3003,7 @@ function doTAKMCjson(p) {
} catch(e) { console.log(e); } } catch(e) { console.log(e); }
d.alt = p.hae || 9999999; d.alt = p.hae || 9999999;
if (d.alt === 9999999) { delete d.alt; } if (d.alt === 9999999) { delete d.alt; }
handleCoTtypes(d); handleCoTtypes(d,p);
setMarker(d); setMarker(d);
} }
else { else {
@ -3003,9 +3011,38 @@ function doTAKMCjson(p) {
} }
} }
function handleCoTtypes(d) { function convertCOTtoCIFColour(color) {
const c = parseInt(color);
const arr = new ArrayBuffer(4);
const view = new DataView(arr);
view.setUint32(0, color, false);
const b2h = buf2hex(arr);
return "#" + b2h.substr(2);
}
function buf2hex(buffer) { // buffer is an ArrayBuffer
return [...new Uint8Array(buffer)]
.map(x => x.toString(16).padStart(2, '0'))
.join('');
}
function createRings(r) {
if (r <= 100) { return r; }
var rings = [];
var step = 100;
if (r > 1000) { step = 1000; }
if (r > 10000) { step = 10000; }
for (var i = step; i < r; i += step) {
rings.push(i);
}
rings.push(r);
return rings;
}
function handleCoTtypes(d,p) {
if (d.type.indexOf('a-') === 0) { // handle a- types
var i = d.type.split('-').join('').toUpperCase(); var i = d.type.split('-').join('').toUpperCase();
if (i[0] === 'A') { i = 'S' + i.substr(1,2) + 'P' + i.substr(3); } i = 'S' + i.substr(1,2) + 'P' + i.substr(3);
if (d.role === 'Team Lead') { i = i + '----B'; } if (d.role === 'Team Lead') { i = i + '----B'; }
if (d.role === 'HQ') { i = 'SFGPUH' }; if (d.role === 'HQ') { i = 'SFGPUH' };
if (d.role === "Medic") { i = 'SFGPUSM----A'; } if (d.role === "Medic") { i = 'SFGPUSM----A'; }
@ -3016,19 +3053,81 @@ function handleCoTtypes(d) {
if (d.type === "a-h-X-i-o") { d.SIDC = "EHIP--------" } 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-m-d") { d.SIDC = "EHNPBB------" }
if (d.type === "a-h-X-i-g-e") { d.SIDC = "EHNPAC------" } if (d.type === "a-h-X-i-g-e") { d.SIDC = "EHNPAC------" }
return d;
// Other non-atom types - tbd }
// b-a-o (Alert) - "ESOPB-------" else { // handle b- types
// b-a-g (Geofence alert) - "ESOPEC------" // console.log("TYPE",d.type);
// b-a-o-can (cancel Alert) - "ENOSB-------" short ttl try {
// b-m-p-s-m Marker point (dot with colour) if (d.type === 'b-m-p-s-m') { // small spot marker
// b-m-p-s-p-loc Located object eg video d.icon = "fa-circle fa-fw";
// b-m-p-s-p-i Location indicator d.ttl = 0;
// b-i-x-i Camera image d.iconColor = convertCOTtoCIFColour(p.detail.color.argb);
// b-m-r Route delete d.SIDC;
// b-m-p-c Waypoint }
// b-r-f-h-c Medevac if (d.type.indexOf('b-m-p-s-p') === 0) { // it's a position indicator
// b-d Drawings -c-c circle -c-e ellipse -r rectangle -f freehand delete d.SIDC;
// b-t-f Geochat (f = file) d.ttl = 0;
if (d.type.indexOf('b-m-p-s-p-loc') === 0) {
if (p.detail?.sensor) {
if (p.detail?.__video) {
d.icon = "fa-video-camera";
d.video_link = p.detail?.__video?.ConnectionEntry?.protocol+'://'+p.detail?.__video?.url
}
else {
d.SIDC = "SFGPUUMRS---";
}
if (p.detail.sensor?.fov) {
d.arc = {
fov: +p.detail.sensor.fov,
pan: +p.detail.sensor.azimuth,
ranges: createRings(+p.detail.sensor.range),
color: convertCOTtoCIFColour(p.detail.sensor.strokeColor)
}
}
}
else { d.icon = "locate"; }
}
if (d.type.indexOf('b-m-p-s-p-op') === 0) {
d.icon = "fa-binoculars";
}
}
if (d.type === 'b-m-p-w-GOTO') {
d.SIDC = "GFGPGPRP----";
}
if (d.type === 'b-m-p-c') {
d.SIDC = "GFGPGPRW----";
}
if (d.type === 'b-a-o-tbl' || d.type === 'b-a-o-pan' || d.type === 'b-a-o-opn') {
d.remarks = p.detail.emergency["#text"] + " " + p.detail.emergency.type;
d.icon = 'fa-exclamation-circle';
if (d.type === 'b-a-o-tbl') { d.iconColor = 'gold'; }
if (d.type === 'b-a-o-pan') { d.iconColor = 'orange'; }
if (d.type === 'b-a-o-opn') { d.iconColor = 'red'; }
// d.SIDC = 'ESOPB-------';
d.ttl = 0;
}
if (d.type === 'b-a-g') { // geofence alert
d.remarks = p.detail.emergency["#text"] + " " + p.detail.emergency.type;
d.icon = 'fa-crosshairs';
d.iconColor = 'orange';
// d.SIDC = 'ESOPEC------';
d.ttl = 0;
}
if (d.type === 'b-a-o-can') { // cancelled alert
d.name = p.detail.emergency["#text"] + "-Alert";
d.deleted = true;
}
}
catch(e) {
console.log(e);
}
// console.log("D",d)
// Other non-atom types - tbd
// b-i-x-i Camera image ?
// b-m-r Route
// b-r-f-h-c Medevac "EFOPBD------"
// b-d Drawings -c-c circle -c-e ellipse -r rectangle -f freehand
// b-t-f Geochat (f = file) just No
}
return d; return d;
} }