diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..1cd0cf7
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,6 @@
+from nodered/node-red:latest-minimal
+
+RUN npm install node-red-contrib-web-worldmap node-red-contrib-config
+
+COPY WebMap.json /data/WebMap.json
+ENV FLOWS /data/WebMap.json
diff --git a/WebMap.json b/WebMap.json
index a6a6d35..bc45063 100644
--- a/WebMap.json
+++ b/WebMap.json
@@ -1 +1,516 @@
-[{"id":"db2b165142e8c36a","type":"tab","label":"web Map 2","disabled":false,"info":"Gets the flow of COT from FTS and displays on a web map (credit @ampledata#8354).\n The FTH WebMap integration can be installed as part of FTS UI or a stand alone, e.g. for a dashboard. ","env":[]},{"id":"9946c2419fd3c3ae","type":"inject","z":"db2b165142e8c36a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"120","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":170,"y":160,"wires":[["33593e8ece650844"]]},{"id":"33593e8ece650844","type":"function","z":"db2b165142e8c36a","name":"CoT Ping","func":" msg.host = global.get('FTH_FTS_URL');\n msg.port = global.get('FTH_FTS_TCP_Port');\nconst dt = Date.now();\nconst dtD = new Date(dt).toISOString();\nconst dtD5 = new Date(dt + 250000).toISOString();\n\nmsg.payload = {\n event: {\n \"$\": { \n version: \"2.0\", \n type: \"t-x-d-d\", \n uid : \"node-red\",\n time: dtD,\n start: dtD,\n stale: dtD5,\n how: \"m-g\"\n }\n }\n \n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":160,"wires":[["7a0f7d3e4099e946"]]},{"id":"7a0f7d3e4099e946","type":"xml","z":"db2b165142e8c36a","name":"","property":"payload","attr":"","chr":"","x":450,"y":160,"wires":[["52446a6c25605721"]]},{"id":"7c52084b496b83bc","type":"comment","z":"db2b165142e8c36a","name":"Keep Server Connection Alive","info":"","x":200,"y":120,"wires":[]},{"id":"52446a6c25605721","type":"tcp request","z":"db2b165142e8c36a","server":"","port":"","out":"sit","ret":"buffer","splitc":" ","name":"FTS Server","x":590,"y":160,"wires":[["3c62054705949d69","4134eaa89c5c71e0"]]},{"id":"3c62054705949d69","type":"function","z":"db2b165142e8c36a","name":"buf->str","func":"/*\nlet oldPayload = msg.payload;\nmsg.payload = oldPayload.toString();\nreturn msg;\n*/\n\nvar rest = context.rest || \"\";\nvar p = rest + msg.payload.toString();\nvar b = p.split(\"\");\nwhile (b.length > 1) {\n var m = b.shift();\n node.send({ payload: m + \"\" });\n}\ncontext.rest = b;\nreturn;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":740,"y":160,"wires":[["91213e2448bab185"]]},{"id":"91213e2448bab185","type":"xml","z":"db2b165142e8c36a","name":"","property":"payload","attr":"","chr":"","x":870,"y":160,"wires":[["4b3df29696d9fc8f"]]},{"id":"05f4656c40958267","type":"function","z":"db2b165142e8c36a","name":"CoT to Map","func":"/*\nThe following block of code transforms a JSONified CoT Event into a Worldmap \nPoint Payload.\n*/\n\nlet invalid = 9999999;\nlet event = msg.payload[\"event\"];\n\n/* \nIf the CoT Event contains Detail Elements, extract the first one.\nIf there are no Detail Elements, break.\n*/\nlet _detail = event[\"detail\"];\nif (_detail === undefined) { return null; }\nlet detail = _detail[0];\n\n/*\nIf the CoT Event contains a Point element, use it. If not, break.\n*/\nlet point = event[\"point\"];\nif (point === undefined) { return null; }\n\n/* We'll use UID a couple of times, so lets set it as a variable here. */\nlet uid = event[\"$\"][\"uid\"];\n\n/* Extract the Event Type and Affiliation. */\nlet eventType = event[\"$\"][\"type\"];\nif (eventType === \"t-x-d-d\") { return; } // ignore pings\nconsole.log(eventType);\nlet et = eventType.split(\"-\");\nlet affil = et[1];\n\n/* There is no '.' notation in SDR, so mark Neutral. */\nif (affil.includes(\".\")) { affil = \"n\"; } \n\n/* Ram the CoT Event Type portions into a SIDR Type */\nlet SIDC = `s${affil}${et[2]}p${et[3] || \"-\" }${et[4] || \"-\" }${et[5] || \"-\" }--------`;\nSIDC = SIDC.substr(0,12).toUpperCase();\n\n/* Now handle all the weird extra CoT ones... */\nif (eventType === \"a-h-X-i-o\") { \n console.log(\"XIO--->\",SIDC)\n SIDC = \"EHIP--------\" }\nelse if (eventType === \"a-h-X-i-m-d\") { \n console.log(\"XIMD--->\",SIDC)\n SIDC = \"EHNPBB------\" }\nelse if (eventType === \"a-h-X-i-g-e\") { \n console.log(\"XIGE--->\",SIDC)\n SIDC = \"EHNPAC------\" }\n\n\nconsole.log(SIDC);\n\n/* \nPoints on the Worldmap can only have one uniquite identifier, which is also\nthat Points display name. If possible, use a Callsign, otherwise use UID.\n*/\nlet callsign;\nlet _contact = detail[\"contact\"];\nif (_contact === undefined) {\n callsign = uid;\n} else {\n callsign = _contact[0][\"$\"][\"callsign\"]; \n}\n\n/* Mouse-over Label */\nlet label = `Callsign: ${callsign} UID: ${uid}
Type: ${eventType} SIDC: ${SIDC}`\n\nlet remarks = detail[\"remarks\"];\nif (remarks) {\n let remark = remarks[0][\"$\"];\n label = `${label}
${remark}`;\n}\n\nlet track = detail[\"track\"]\nlet bearing = null;\nlet speed = null;\n\nif (track) {\n let course = +track[0][\"$\"][\"course\"];\n if (course) {\n if (course !== invalid && course.toString() !== \"0\") {\n bearing = course;\n }\n }\n\n let _speed = track[0][\"$\"][\"speed\"];\n if (_speed) {\n if (_speed.toString() !== invalid) {\n speed = _speed;\n }\n }\n}\n\n/* \nIf CoT Point CE is set and is not invalid, use that as Worldmap Point Accuracy. \n*/\nlet accuracy = null;\nlet ce = event[\"point\"][0][\"$\"][\"ce\"];\nif (ce.toString() != invalid) { accuracy = ce; }\n\n/* Add a helpful weblink to Worldmap Points. */\nlet weblink = null;\nif (uid.includes(\"ICAO\")) {\n weblink = `https://globe.adsbexchange.com/?icao=${uid.replace(\"ICAO-\", \"\")}`;\n} else if (uid.includes(\"APRS\")) {\n weblink = `https://qrz.com/db/${uid.replace(\"APRS.\", \"\").split(\"-\")[0]}`;\n}\n\nlet lat = event[\"point\"][0][\"$\"][\"lat\"];\nlet lon = event[\"point\"][0][\"$\"][\"lon\"];\nlet alt = event[\"point\"][0][\"$\"][\"hae\"];\n\nif (lat == 0 && lon == 0 && alt == 0) {\n console.log(callsign,\"is at null island.\",eventType)\n lat = Math.round(1000000 * lat + Math.random() * 10000) / 1000000;\n lon = Math.round(1000000 * lon + Math.random() * 10000) / 1000000;\n}\n\n/* Serialize as a Worldmap compatible Payload. */\nmsg.payload = {\n name: callsign,\n tooltip: label,\n lat: lat,\n lon: lon,\n alt: alt,\n speed: speed,\n bearing: bearing,\n accuracy: accuracy,\n SIDC: SIDC,\n ttl: 600,\n weblink: weblink,\n layer: eventType,\n options: {scale:0.8}\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":990,"y":40,"wires":[[]]},{"id":"8c35a77a0d0d3f3d","type":"comment","z":"db2b165142e8c36a","name":"Connect to FTS","info":"","x":600,"y":120,"wires":[]},{"id":"586a2364ecb46529","type":"comment","z":"db2b165142e8c36a","name":"Convert to Worldmap Payload","info":"","x":1060,"y":120,"wires":[]},{"id":"885b808dd8a4895d","type":"debug","z":"db2b165142e8c36a","name":"TAK map output","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1260,"y":80,"wires":[]},{"id":"3a1205e793d055fa","type":"worldmap","z":"db2b165142e8c36a","name":"TAK Map","lat":"43","lon":"13","zoom":"4","layer":"EsriDG","cluster":"","maxage":"5000","usermenu":"show","layers":"show","panit":"false","panlock":"false","zoomlock":"false","hiderightclick":"false","coords":"deg","showgrid":"true","allowFileDrop":"true","path":"/tak-map","overlist":"DR,CO,RA,DN,BU,RW,SN,AC,TL,HM","maplist":"OSMG,OSMC,EsriC,EsriS,EsriT,EsriO,EsriDG,NatGeo,OpTop,SW","mapname":"","mapurl":"","mapopt":"","mapwms":false,"x":1240,"y":220,"wires":[]},{"id":"71787ea1acdd3c9a","type":"worldmap in","z":"db2b165142e8c36a","name":"","path":"/tak-map","events":"connect","x":200,"y":280,"wires":[["3f1c45fae45fc2ae"]]},{"id":"361f42b16f36cb0d","type":"worldmap in","z":"db2b165142e8c36a","name":"event","path":"/tak-map","events":"other","x":190,"y":380,"wires":[["9e3dcf9240c85d5a"]]},{"id":"bf877041be384805","type":"debug","z":"db2b165142e8c36a","name":"No form!!","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":560,"y":540,"wires":[]},{"id":"3f1c45fae45fc2ae","type":"function","z":"db2b165142e8c36a","name":"form","func":"msg.payload = { command: {\ncontextmenu: String.raw`\n
\n`\n}}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1010,"y":260,"wires":[["3a1205e793d055fa"]]},{"id":"17650e362d95de70","type":"config","z":"db2b165142e8c36a","name":"FTH Global Config","properties":[{"p":"FTH_FTS_URL","pt":"global","to":"0.0.0.0","tot":"str"},{"p":"FTH_FTS_TCP_Port","pt":"global","to":"8087","tot":"str"},{"p":"FTH_FTS_API_Port","pt":"global","to":"19023","tot":"str"},{"p":"FTH_FTS_API_Auth","pt":"global","to":"token","tot":"str"},{"p":"FTH_FTS_STREAM_Port","pt":"global","to":"8554","tot":"str"},{"p":"FTH_FTS_VIDEO_URL","pt":"global","to":"0.0.0.0","tot":"str"},{"p":"FTH_FTS_VIDEO_API_PORT","pt":"global","to":"9997","tot":"str"}],"active":true,"x":190,"y":80,"wires":[]},{"id":"f9c1c3d2b0dcf4da","type":"http request","z":"db2b165142e8c36a","name":"Post COT to FTS","method":"POST","ret":"txt","paytoqs":"ignore","url":"{{{addr}}}:{{{port}}}/ManageGeoObject/postGeoObject","tls":"","persist":false,"proxy":"","authType":"bearer","senderr":false,"x":890,"y":440,"wires":[["e42b1fdd3dd68980"]]},{"id":"64334cba1273e06c","type":"function","z":"db2b165142e8c36a","name":"","func":"\n msg.addr = global.get('FTH_FTS_URL');\n msg.port = global.get('FTH_FTS_API_Port');\nlet lon = msg.payload.lon;\n let lat = msg.payload.lat;\n let attitude = msg.payload.value.attitude;\n let geoobject = msg.payload.value.geobject;\n let timeOut = msg.payload.name.timeout;\n let aName = msg.payload.value.name;\n let token = global.get('FTH_FTS_API_Auth');\n\n \nmsg.headers = {};\nmsg.headers['Authorization'] = \"Bearer \"+ token;\n \n\nmsg.payload = [];\n\n msg.payload={\n longitude: lon,\n latitude: lat,\nattitude: attitude,\ngeoObject: geoobject,\nhow: \"nonCoT\",\nname: aName,\ntimeout: timeOut \n };\n \nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":680,"y":440,"wires":[["f9c1c3d2b0dcf4da"]]},{"id":"e42b1fdd3dd68980","type":"debug","z":"db2b165142e8c36a","name":"API Response","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1120,"y":440,"wires":[]},{"id":"9e3dcf9240c85d5a","type":"switch","z":"db2b165142e8c36a","name":"check message","property":"payload.value.name","propertyType":"msg","rules":[{"t":"nempty"},{"t":"empty"}],"checkall":"true","repair":false,"outputs":2,"x":440,"y":420,"wires":[["64334cba1273e06c"],["bf877041be384805"]]},{"id":"647af514a3fc61f2","type":"comment","z":"db2b165142e8c36a","name":"Add the menu","info":"","x":210,"y":240,"wires":[]},{"id":"57d4f22db956d69b","type":"comment","z":"db2b165142e8c36a","name":"Get the result from the post","info":"","x":230,"y":340,"wires":[]},{"id":"0565f229206163d0","type":"comment","z":"db2b165142e8c36a","name":"CONFIG ME!!!!","info":"please set the variables to the proper IP","x":170,"y":40,"wires":[]},{"id":"4134eaa89c5c71e0","type":"debug","z":"db2b165142e8c36a","name":"Strem output","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":770,"y":80,"wires":[]},{"id":"4b3df29696d9fc8f","type":"function","z":"db2b165142e8c36a","name":"CoT to Map","func":"/*\nThe following block of code transforms a JSONified CoT Event into a Worldmap \nPoint Payload.\n*/\nlet ttl = 3600;\nlet deleted = false; //= 'undefined';\nlet invalid = 9999999;\nlet event = msg.payload[\"event\"];\n\n/* \nIf the CoT Event contains Detail Elements, extract the first one.\nIf there are no Detail Elements, break.\n*/\nlet _detail = event[\"detail\"];\nif (_detail === undefined) { return null; }\nlet detail = _detail[0];\n\n/*\nIf the CoT Event contains a Point element, use it. If not, break.\n*/\nlet point = event[\"point\"];\nif (point === undefined) { return null; }\n\n/* We'll use UID a couple of times, so lets set it as a variable here. */\nlet uid = event[\"$\"][\"uid\"];\n\n/* Extract the Event Type and Affiliation. */\nlet eventType = event[\"$\"][\"type\"];\nif (eventType === \"t-x-d-d\") { return; } // ignore pings\n\nlet et = eventType.split(\"-\");\nlet affil = et[1];\n\n/* There is no '.' notation in SDR, so mark Neutral. */\nif (affil.includes(\".\")) { affil = \"n\"; }\n\n/* Ram the CoT Event Type portions into a SIDR Type */\nlet SIDC = `s${affil}${et[2]}p${et[3] || \"-\"}${et[4] || \"-\"}${et[5] || \"-\"}--------`;\n\n/* Now handle all the weird extra CoT ones... */\nif ((/a-.-X-i-o/).test(eventType)) { SIDC = `E${affil}IP--------` }\nelse if ((/a-.-X-i-g-a/).test(eventType)) { SIDC = `EFNPAB------` }\nelse if ((/a-.-X-i-g-e/).test(eventType)) { SIDC = `EFNPAC------` }\nelse if ((/a-.-X-i-g-l/).test(eventType)) { SIDC = `EFNPAD------` }\nelse if ((/a-.-X-i-g-s/).test(eventType)) { SIDC = `EFNPAE------` }\nelse if ((/a-.-X-i-g-v-e/).test(eventType)) { SIDC = `WFSPWSVE----` }\nelse if ((/a-.-X-i-g-v/).test(eventType)) { SIDC = `EFNPAG------` }\nelse if ((/a-.-X-i-g/).test(eventType)) { SIDC = `E${affil}NPA-------` }\nelse if ((/a-.-X-i-m-c/).test(eventType)) { SIDC = `WFSPWSTSS---` }\nelse if ((/a-.-X-i-m-d/).test(eventType)) { SIDC = `EFNPBB------` }\nelse if ((/a-.-X-i-m-n/).test(eventType)) { SIDC = `EFNPBM------` }\nelse if ((/a-.-X-i-f/).test(eventType)) { SIDC = `E${affil}IPC-------` }\nelse if ((/a-.-X-i-h/).test(eventType)) { SIDC = `E${affil}OPA-------` }\nelse if ((/a-.-X-i-t-v-a/).test(eventType)) { SIDC = `E${affil}IPHA------` }\n\nelse if ((/a-.-G-I-X-H/).test(eventType)) { SIDC = `S${affil}GPIXH---H-` }\nelse if ((/a-.-G-I-i-l/).test(eventType)) { SIDC = `E${affil}OPDJC-----` }\nelse if ((/a-.-G-I-R-P/).test(eventType)) { SIDC = `S${affil}GPIRP---H-` }\nelse if ((/a-.-G-I-U-E/).test(eventType)) { SIDC = `S${affil}GPIUE---H-` }\nelse if ((/a-.-G-I-U-T/).test(eventType)) { SIDC = `S${affil}GPIUT---H-` }\nelse if ((/a-.-G-I-i-e/).test(eventType)) { SIDC = `E${affil}OPBC------` }\n\nelse if ((/a-.-G-U-C-V-R-E/).test(eventType)) { SIDC = `S${affil}APMFO-----` }\nelse if ((/a-.-A-M-F-Q-H/).test(eventType)) { SIDC = `S${affil}APMFH-----` }\n\nelse if ((/a-.-G-I-U-T/).test(eventType)) { SIDC = `S${affil}GPIUT---H-` }\nelse if ((/b-r-.-O-I-V/).test(eventType)) { SIDC = `S${affil}EFIPAC------` }\n// emergency open send symbol and reduce ttl\nelse if ((/b-a-o-opn/).test(eventType)) {\n SIDC = `S${affil}GDU-------`;\n //{ SIDC = `SFGPUCI-----` ;\n ttl = 36;\n node.warn('emergency Open');\n}\nelse if ((/b-a-o-tbl/).test(eventType)) {\n SIDC = `S${affil}GDU-------`;\n //{ SIDC = `SFGPUCI-----` ;\n ttl = 36;\n node.warn('trouble emergency Open');\n}\nelse if ((/b-a-o-pan/).test(eventType)) {\n SIDC = `S${affil}GXU-------`;\n //{ SIDC = `SFGPUCI-----` ;\n ttl = 36;\n node.warn('ring the bell emergency Open');\n}\n//emergency is closed SFGDU-------\nelse if ((/b-a-o-can/).test(eventType)) {\n //deleted = true\n SIDC = `SFGPUCI-----`; \n node.warn('emergency closed');\n }\n\n\nconsole.log(eventType, \"--->\", SIDC);\nSIDC = SIDC.substr(0, 12).toUpperCase();\n\n/* \nPoints on the Worldmap can only have one uniquite identifier, which is also\nthat Points display name. If possible, use a Callsign, otherwise use UID.\n*/\nlet callsign;\nlet _contact = detail[\"contact\"];\nif (_contact === undefined) {\n callsign = uid;\n} else {\n callsign = _contact[0][\"$\"][\"callsign\"];\n}\n\n/* Mouse-over Label */\nlet label = `Callsign: ${callsign} UID: ${uid}
Type: ${eventType} SIDC: ${SIDC}`\n\nlet remarks = detail[\"remarks\"];\nif (remarks) {\n let remark = remarks[0][\"$\"];\n label = `${label}
${remark}`;\n}\n\nlet track = detail[\"track\"]\nlet bearing = null;\nlet speed = null;\n\nif (track) {\n let course = +track[0][\"$\"][\"course\"];\n if (course) {\n if (course !== invalid) {\n bearing = course;\n }\n }\n\n let _speed = +track[0][\"$\"][\"speed\"];\n if (_speed) {\n if (_speed !== invalid) {\n speed = _speed;\n }\n }\n}\n\n/* \nIf CoT Point CE is set and is not invalid, use that as Worldmap Point Accuracy. \n*/\nlet accuracy = null;\nlet ce = +event[\"point\"][0][\"$\"][\"ce\"];\nif (ce != invalid) { accuracy = ce; }\n\n/* Add a helpful weblink to Worldmap Points. */\nlet weblink = null;\nif (uid.includes(\"ICAO\")) {\n weblink = `https://globe.adsbexchange.com/?icao=${uid.replace(\"ICAO-\", \"\")}`;\n} else if (uid.includes(\"APRS\")) {\n weblink = `https://qrz.com/db/${uid.replace(\"APRS.\", \"\").split(\"-\")[0]}`;\n}\nelse {\n weblink = 'https://spatialillusions.com/unitgenerator/';\n}\n\nlet lat = +event[\"point\"][0][\"$\"][\"lat\"];\nlet lon = +event[\"point\"][0][\"$\"][\"lon\"];\nlet alt = +event[\"point\"][0][\"$\"][\"hae\"];\nif (alt == invalid) { alt = undefined; }\n\nif (lat == 0 && lon == 0 && !alt) {\n console.log(callsign, \"is at null island.\", eventType)\n lat = Math.round(1000000 * lat + Math.random() * 10000) / 1000000;\n lon = Math.round(1000000 * lon + Math.random() * 10000) / 1000000;\n}\n\n/* Serialize as a Worldmap compatible Payload. */\nmsg.payload = {\n name: callsign,\n tooltip: label,\n deleted: deleted,\n lat: lat,\n lon: lon,\n alt: alt,\n speed: speed,\n bearing: bearing,\n accuracy: accuracy,\n type: eventType,\n SIDC: SIDC,\n ttl: ttl,\n weblink: weblink,\n layer: eventType,\n options: { scale: 0.8 }\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1010,"y":160,"wires":[["3a1205e793d055fa","885b808dd8a4895d"]]},{"id":"bbaeafe55e6385e4","type":"comment","z":"db2b165142e8c36a","name":"APi Token here","info":"","x":880,"y":400,"wires":[]}]
\ No newline at end of file
+[
+ {
+ "id": "db2b165142e8c36a",
+ "type": "tab",
+ "label": "web Map 2",
+ "disabled": false,
+ "info": "Gets the flow of COT from FTS and displays on a web map (credit @ampledata#8354).\n The FTH WebMap integration can be installed as part of FTS UI or a stand alone, e.g. for a dashboard. ",
+ "env": []
+ },
+ {
+ "id": "9946c2419fd3c3ae",
+ "type": "inject",
+ "z": "db2b165142e8c36a",
+ "name": "",
+ "props": [
+ {
+ "p": "payload"
+ },
+ {
+ "p": "topic",
+ "vt": "str"
+ }
+ ],
+ "repeat": "120",
+ "crontab": "",
+ "once": true,
+ "onceDelay": 0.1,
+ "topic": "",
+ "payload": "",
+ "payloadType": "date",
+ "x": 170,
+ "y": 160,
+ "wires": [
+ [
+ "33593e8ece650844"
+ ]
+ ]
+ },
+ {
+ "id": "33593e8ece650844",
+ "type": "function",
+ "z": "db2b165142e8c36a",
+ "name": "CoT Ping",
+ "func": "msg.host = env.get(\"FTH_FTS_URL\") || global.get('FTH_FTS_URL');\nmsg.port = env.get(\"FTH_FTS_TCP_Port\") || global.get('FTH_FTS_TCP_Port');\nconst dt = Date.now();\nconst dtD = new Date(dt).toISOString();\nconst dtD5 = new Date(dt + 250000).toISOString();\n\nmsg.payload = {\n event: {\n \"$\": { \n version: \"2.0\", \n type: \"t-x-d-d\", \n uid : \"node-red\",\n time: dtD,\n start: dtD,\n stale: dtD5,\n how: \"m-g\"\n }\n }\n \n}\n\nreturn msg;",
+ "outputs": 1,
+ "timeout": "",
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [],
+ "x": 320,
+ "y": 160,
+ "wires": [
+ [
+ "7a0f7d3e4099e946"
+ ]
+ ]
+ },
+ {
+ "id": "7a0f7d3e4099e946",
+ "type": "xml",
+ "z": "db2b165142e8c36a",
+ "name": "",
+ "property": "payload",
+ "attr": "",
+ "chr": "",
+ "x": 450,
+ "y": 160,
+ "wires": [
+ [
+ "52446a6c25605721"
+ ]
+ ]
+ },
+ {
+ "id": "7c52084b496b83bc",
+ "type": "comment",
+ "z": "db2b165142e8c36a",
+ "name": "Keep Server Connection Alive",
+ "info": "",
+ "x": 200,
+ "y": 120,
+ "wires": []
+ },
+ {
+ "id": "52446a6c25605721",
+ "type": "tcp request",
+ "z": "db2b165142e8c36a",
+ "name": "FTS Server",
+ "server": "",
+ "port": "",
+ "out": "sit",
+ "ret": "buffer",
+ "splitc": " ",
+ "newline": "",
+ "trim": false,
+ "tls": "",
+ "x": 590,
+ "y": 160,
+ "wires": [
+ [
+ "3c62054705949d69",
+ "4134eaa89c5c71e0"
+ ]
+ ]
+ },
+ {
+ "id": "3c62054705949d69",
+ "type": "function",
+ "z": "db2b165142e8c36a",
+ "name": "buf->str",
+ "func": "/*\nlet oldPayload = msg.payload;\nmsg.payload = oldPayload.toString();\nreturn msg;\n*/\n\nvar rest = context.rest || \"\";\nvar p = rest + msg.payload.toString();\nvar b = p.split(\"\");\nwhile (b.length > 1) {\n var m = b.shift();\n node.send({ payload: m + \"\" });\n}\ncontext.rest = b;\nreturn;\n",
+ "outputs": 1,
+ "timeout": "",
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [],
+ "x": 740,
+ "y": 160,
+ "wires": [
+ [
+ "91213e2448bab185"
+ ]
+ ]
+ },
+ {
+ "id": "91213e2448bab185",
+ "type": "xml",
+ "z": "db2b165142e8c36a",
+ "name": "",
+ "property": "payload",
+ "attr": "",
+ "chr": "",
+ "x": 870,
+ "y": 160,
+ "wires": [
+ [
+ "4b3df29696d9fc8f"
+ ]
+ ]
+ },
+ {
+ "id": "05f4656c40958267",
+ "type": "function",
+ "z": "db2b165142e8c36a",
+ "name": "CoT to Map",
+ "func": "/*\nThe following block of code transforms a JSONified CoT Event into a Worldmap \nPoint Payload.\n*/\n\nlet invalid = 9999999;\nlet event = msg.payload[\"event\"];\n\n/* \nIf the CoT Event contains Detail Elements, extract the first one.\nIf there are no Detail Elements, break.\n*/\nlet _detail = event[\"detail\"];\nif (_detail === undefined) { return null; }\nlet detail = _detail[0];\n\n/*\nIf the CoT Event contains a Point element, use it. If not, break.\n*/\nlet point = event[\"point\"];\nif (point === undefined) { return null; }\n\n/* We'll use UID a couple of times, so lets set it as a variable here. */\nlet uid = event[\"$\"][\"uid\"];\n\n/* Extract the Event Type and Affiliation. */\nlet eventType = event[\"$\"][\"type\"];\nif (eventType === \"t-x-d-d\") { return; } // ignore pings\nconsole.log(eventType);\nlet et = eventType.split(\"-\");\nlet affil = et[1];\n\n/* There is no '.' notation in SDR, so mark Neutral. */\nif (affil.includes(\".\")) { affil = \"n\"; } \n\n/* Ram the CoT Event Type portions into a SIDR Type */\nlet SIDC = `s${affil}${et[2]}p${et[3] || \"-\" }${et[4] || \"-\" }${et[5] || \"-\" }--------`;\nSIDC = SIDC.substr(0,12).toUpperCase();\n\n/* Now handle all the weird extra CoT ones... */\nif (eventType === \"a-h-X-i-o\") { \n console.log(\"XIO--->\",SIDC)\n SIDC = \"EHIP--------\" }\nelse if (eventType === \"a-h-X-i-m-d\") { \n console.log(\"XIMD--->\",SIDC)\n SIDC = \"EHNPBB------\" }\nelse if (eventType === \"a-h-X-i-g-e\") { \n console.log(\"XIGE--->\",SIDC)\n SIDC = \"EHNPAC------\" }\n\n\nconsole.log(SIDC);\n\n/* \nPoints on the Worldmap can only have one uniquite identifier, which is also\nthat Points display name. If possible, use a Callsign, otherwise use UID.\n*/\nlet callsign;\nlet _contact = detail[\"contact\"];\nif (_contact === undefined) {\n callsign = uid;\n} else {\n callsign = _contact[0][\"$\"][\"callsign\"]; \n}\n\n/* Mouse-over Label */\nlet label = `Callsign: ${callsign} UID: ${uid}
Type: ${eventType} SIDC: ${SIDC}`\n\nlet remarks = detail[\"remarks\"];\nif (remarks) {\n let remark = remarks[0][\"$\"];\n label = `${label}
${remark}`;\n}\n\nlet track = detail[\"track\"]\nlet bearing = null;\nlet speed = null;\n\nif (track) {\n let course = +track[0][\"$\"][\"course\"];\n if (course) {\n if (course !== invalid && course.toString() !== \"0\") {\n bearing = course;\n }\n }\n\n let _speed = track[0][\"$\"][\"speed\"];\n if (_speed) {\n if (_speed.toString() !== invalid) {\n speed = _speed;\n }\n }\n}\n\n/* \nIf CoT Point CE is set and is not invalid, use that as Worldmap Point Accuracy. \n*/\nlet accuracy = null;\nlet ce = event[\"point\"][0][\"$\"][\"ce\"];\nif (ce.toString() != invalid) { accuracy = ce; }\n\n/* Add a helpful weblink to Worldmap Points. */\nlet weblink = null;\nif (uid.includes(\"ICAO\")) {\n weblink = `https://globe.adsbexchange.com/?icao=${uid.replace(\"ICAO-\", \"\")}`;\n} else if (uid.includes(\"APRS\")) {\n weblink = `https://qrz.com/db/${uid.replace(\"APRS.\", \"\").split(\"-\")[0]}`;\n}\n\nlet lat = event[\"point\"][0][\"$\"][\"lat\"];\nlet lon = event[\"point\"][0][\"$\"][\"lon\"];\nlet alt = event[\"point\"][0][\"$\"][\"hae\"];\n\nif (lat == 0 && lon == 0 && alt == 0) {\n console.log(callsign,\"is at null island.\",eventType)\n lat = Math.round(1000000 * lat + Math.random() * 10000) / 1000000;\n lon = Math.round(1000000 * lon + Math.random() * 10000) / 1000000;\n}\n\n/* Serialize as a Worldmap compatible Payload. */\nmsg.payload = {\n name: callsign,\n tooltip: label,\n lat: lat,\n lon: lon,\n alt: alt,\n speed: speed,\n bearing: bearing,\n accuracy: accuracy,\n SIDC: SIDC,\n ttl: 600,\n weblink: weblink,\n layer: eventType,\n options: {scale:0.8}\n}\n\nreturn msg;",
+ "outputs": 1,
+ "timeout": "",
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [],
+ "x": 990,
+ "y": 40,
+ "wires": [
+ []
+ ]
+ },
+ {
+ "id": "8c35a77a0d0d3f3d",
+ "type": "comment",
+ "z": "db2b165142e8c36a",
+ "name": "Connect to FTS",
+ "info": "",
+ "x": 600,
+ "y": 120,
+ "wires": []
+ },
+ {
+ "id": "586a2364ecb46529",
+ "type": "comment",
+ "z": "db2b165142e8c36a",
+ "name": "Convert to Worldmap Payload",
+ "info": "",
+ "x": 1060,
+ "y": 120,
+ "wires": []
+ },
+ {
+ "id": "885b808dd8a4895d",
+ "type": "debug",
+ "z": "db2b165142e8c36a",
+ "name": "TAK map output",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "payload",
+ "targetType": "msg",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 1260,
+ "y": 80,
+ "wires": []
+ },
+ {
+ "id": "3a1205e793d055fa",
+ "type": "worldmap",
+ "z": "db2b165142e8c36a",
+ "name": "TAK Map",
+ "lat": "43",
+ "lon": "13",
+ "zoom": "4",
+ "layer": "EsriDG",
+ "cluster": "",
+ "maxage": "5000",
+ "usermenu": "show",
+ "layers": "show",
+ "panit": "false",
+ "panlock": "false",
+ "zoomlock": "false",
+ "hiderightclick": "false",
+ "coords": "deg",
+ "showgrid": "true",
+ "allowFileDrop": "true",
+ "path": "/tak-map",
+ "overlist": "DR,CO,RA,DN,BU,RW,SN,AC,TL,HM",
+ "maplist": "OSMG,OSMC,EsriC,EsriS,EsriT,EsriO,EsriDG,NatGeo,OpTop,SW",
+ "mapname": "",
+ "mapurl": "",
+ "mapopt": "",
+ "mapwms": false,
+ "x": 1240,
+ "y": 220,
+ "wires": []
+ },
+ {
+ "id": "71787ea1acdd3c9a",
+ "type": "worldmap in",
+ "z": "db2b165142e8c36a",
+ "name": "",
+ "path": "/tak-map",
+ "events": "connect",
+ "x": 200,
+ "y": 280,
+ "wires": [
+ [
+ "3f1c45fae45fc2ae"
+ ]
+ ]
+ },
+ {
+ "id": "361f42b16f36cb0d",
+ "type": "worldmap in",
+ "z": "db2b165142e8c36a",
+ "name": "event",
+ "path": "/tak-map",
+ "events": "other",
+ "x": 190,
+ "y": 380,
+ "wires": [
+ [
+ "9e3dcf9240c85d5a"
+ ]
+ ]
+ },
+ {
+ "id": "bf877041be384805",
+ "type": "debug",
+ "z": "db2b165142e8c36a",
+ "name": "No form!!",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "payload",
+ "targetType": "msg",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 560,
+ "y": 540,
+ "wires": []
+ },
+ {
+ "id": "3f1c45fae45fc2ae",
+ "type": "function",
+ "z": "db2b165142e8c36a",
+ "name": "form",
+ "func": "msg.payload = { command: {\ncontextmenu: String.raw`\n\n`\n}}\nreturn msg;",
+ "outputs": 1,
+ "timeout": "",
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [],
+ "x": 1010,
+ "y": 260,
+ "wires": [
+ [
+ "3a1205e793d055fa"
+ ]
+ ]
+ },
+ {
+ "id": "17650e362d95de70",
+ "type": "config",
+ "z": "db2b165142e8c36a",
+ "name": "FTH Global Config",
+ "properties": [
+ {
+ "p": "FTH_FTS_URL",
+ "pt": "global",
+ "to": "0.0.0.0",
+ "tot": "str"
+ },
+ {
+ "p": "FTH_FTS_TCP_Port",
+ "pt": "global",
+ "to": "8087",
+ "tot": "str"
+ },
+ {
+ "p": "FTH_FTS_API_Port",
+ "pt": "global",
+ "to": "19023",
+ "tot": "str"
+ },
+ {
+ "p": "FTH_FTS_API_Auth",
+ "pt": "global",
+ "to": "token",
+ "tot": "str"
+ },
+ {
+ "p": "FTH_FTS_STREAM_Port",
+ "pt": "global",
+ "to": "8554",
+ "tot": "str"
+ },
+ {
+ "p": "FTH_FTS_VIDEO_URL",
+ "pt": "global",
+ "to": "0.0.0.0",
+ "tot": "str"
+ },
+ {
+ "p": "FTH_FTS_VIDEO_API_PORT",
+ "pt": "global",
+ "to": "9997",
+ "tot": "str"
+ }
+ ],
+ "active": true,
+ "x": 190,
+ "y": 80,
+ "wires": []
+ },
+ {
+ "id": "f9c1c3d2b0dcf4da",
+ "type": "http request",
+ "z": "db2b165142e8c36a",
+ "name": "Post COT to FTS",
+ "method": "POST",
+ "ret": "txt",
+ "paytoqs": "ignore",
+ "url": "{{{addr}}}:{{{port}}}/ManageGeoObject/postGeoObject",
+ "tls": "",
+ "persist": false,
+ "proxy": "",
+ "authType": "bearer",
+ "senderr": false,
+ "x": 890,
+ "y": 440,
+ "wires": [
+ [
+ "e42b1fdd3dd68980"
+ ]
+ ]
+ },
+ {
+ "id": "64334cba1273e06c",
+ "type": "function",
+ "z": "db2b165142e8c36a",
+ "name": "",
+ "func": "\n msg.addr = env.get(\"FTH_FTS_URL\") || global.get('FTH_FTS_URL');\n msg.port = env.get(\"FTH_FTS_API_Port\") || global.get('FTH_FTS_API_Port');\nlet lon = msg.payload.lon;\n let lat = msg.payload.lat;\n let attitude = msg.payload.value.attitude;\n let geoobject = msg.payload.value.geobject;\n let timeOut = msg.payload.name.timeout;\n let aName = msg.payload.value.name;\nlet token = env.get(\"FTH_FTS_API_Auth\") || global.get('FTH_FTS_API_Auth');\n\n \nmsg.headers = {};\nmsg.headers['Authorization'] = \"Bearer \"+ token;\n \n\nmsg.payload = [];\n\n msg.payload={\n longitude: lon,\n latitude: lat,\nattitude: attitude,\ngeoObject: geoobject,\nhow: \"nonCoT\",\nname: aName,\ntimeout: timeOut \n };\n \nreturn msg;\n",
+ "outputs": 1,
+ "timeout": "",
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [],
+ "x": 680,
+ "y": 440,
+ "wires": [
+ [
+ "f9c1c3d2b0dcf4da"
+ ]
+ ]
+ },
+ {
+ "id": "e42b1fdd3dd68980",
+ "type": "debug",
+ "z": "db2b165142e8c36a",
+ "name": "API Response",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "payload",
+ "targetType": "msg",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 1120,
+ "y": 440,
+ "wires": []
+ },
+ {
+ "id": "9e3dcf9240c85d5a",
+ "type": "switch",
+ "z": "db2b165142e8c36a",
+ "name": "check message",
+ "property": "payload.value.name",
+ "propertyType": "msg",
+ "rules": [
+ {
+ "t": "nempty"
+ },
+ {
+ "t": "empty"
+ }
+ ],
+ "checkall": "true",
+ "repair": false,
+ "outputs": 2,
+ "x": 440,
+ "y": 420,
+ "wires": [
+ [
+ "64334cba1273e06c"
+ ],
+ [
+ "bf877041be384805"
+ ]
+ ]
+ },
+ {
+ "id": "647af514a3fc61f2",
+ "type": "comment",
+ "z": "db2b165142e8c36a",
+ "name": "Add the menu",
+ "info": "",
+ "x": 210,
+ "y": 240,
+ "wires": []
+ },
+ {
+ "id": "57d4f22db956d69b",
+ "type": "comment",
+ "z": "db2b165142e8c36a",
+ "name": "Get the result from the post",
+ "info": "",
+ "x": 230,
+ "y": 340,
+ "wires": []
+ },
+ {
+ "id": "0565f229206163d0",
+ "type": "comment",
+ "z": "db2b165142e8c36a",
+ "name": "CONFIG ME!!!!",
+ "info": "please set the variables to the proper IP",
+ "x": 170,
+ "y": 40,
+ "wires": []
+ },
+ {
+ "id": "4134eaa89c5c71e0",
+ "type": "debug",
+ "z": "db2b165142e8c36a",
+ "name": "Strem output",
+ "active": true,
+ "tosidebar": true,
+ "console": false,
+ "tostatus": false,
+ "complete": "payload",
+ "targetType": "msg",
+ "statusVal": "",
+ "statusType": "auto",
+ "x": 770,
+ "y": 80,
+ "wires": []
+ },
+ {
+ "id": "4b3df29696d9fc8f",
+ "type": "function",
+ "z": "db2b165142e8c36a",
+ "name": "CoT to Map",
+ "func": "/*\nThe following block of code transforms a JSONified CoT Event into a Worldmap \nPoint Payload.\n*/\nlet ttl = 3600;\nlet deleted = false; //= 'undefined';\nlet invalid = 9999999;\nlet event = msg.payload[\"event\"];\n\n/* \nIf the CoT Event contains Detail Elements, extract the first one.\nIf there are no Detail Elements, break.\n*/\nlet _detail = event[\"detail\"];\nif (_detail === undefined) { return null; }\nlet detail = _detail[0];\n\n/*\nIf the CoT Event contains a Point element, use it. If not, break.\n*/\nlet point = event[\"point\"];\nif (point === undefined) { return null; }\n\n/* We'll use UID a couple of times, so lets set it as a variable here. */\nlet uid = event[\"$\"][\"uid\"];\n\n/* Extract the Event Type and Affiliation. */\nlet eventType = event[\"$\"][\"type\"];\nif (eventType === \"t-x-d-d\") { return; } // ignore pings\n\nlet et = eventType.split(\"-\");\nlet affil = et[1];\n\n/* There is no '.' notation in SDR, so mark Neutral. */\nif (affil.includes(\".\")) { affil = \"n\"; }\n\n/* Ram the CoT Event Type portions into a SIDR Type */\nlet SIDC = `s${affil}${et[2]}p${et[3] || \"-\"}${et[4] || \"-\"}${et[5] || \"-\"}--------`;\n\n/* Now handle all the weird extra CoT ones... */\nif ((/a-.-X-i-o/).test(eventType)) { SIDC = `E${affil}IP--------` }\nelse if ((/a-.-X-i-g-a/).test(eventType)) { SIDC = `EFNPAB------` }\nelse if ((/a-.-X-i-g-e/).test(eventType)) { SIDC = `EFNPAC------` }\nelse if ((/a-.-X-i-g-l/).test(eventType)) { SIDC = `EFNPAD------` }\nelse if ((/a-.-X-i-g-s/).test(eventType)) { SIDC = `EFNPAE------` }\nelse if ((/a-.-X-i-g-v-e/).test(eventType)) { SIDC = `WFSPWSVE----` }\nelse if ((/a-.-X-i-g-v/).test(eventType)) { SIDC = `EFNPAG------` }\nelse if ((/a-.-X-i-g/).test(eventType)) { SIDC = `E${affil}NPA-------` }\nelse if ((/a-.-X-i-m-c/).test(eventType)) { SIDC = `WFSPWSTSS---` }\nelse if ((/a-.-X-i-m-d/).test(eventType)) { SIDC = `EFNPBB------` }\nelse if ((/a-.-X-i-m-n/).test(eventType)) { SIDC = `EFNPBM------` }\nelse if ((/a-.-X-i-f/).test(eventType)) { SIDC = `E${affil}IPC-------` }\nelse if ((/a-.-X-i-h/).test(eventType)) { SIDC = `E${affil}OPA-------` }\nelse if ((/a-.-X-i-t-v-a/).test(eventType)) { SIDC = `E${affil}IPHA------` }\n\nelse if ((/a-.-G-I-X-H/).test(eventType)) { SIDC = `S${affil}GPIXH---H-` }\nelse if ((/a-.-G-I-i-l/).test(eventType)) { SIDC = `E${affil}OPDJC-----` }\nelse if ((/a-.-G-I-R-P/).test(eventType)) { SIDC = `S${affil}GPIRP---H-` }\nelse if ((/a-.-G-I-U-E/).test(eventType)) { SIDC = `S${affil}GPIUE---H-` }\nelse if ((/a-.-G-I-U-T/).test(eventType)) { SIDC = `S${affil}GPIUT---H-` }\nelse if ((/a-.-G-I-i-e/).test(eventType)) { SIDC = `E${affil}OPBC------` }\n\nelse if ((/a-.-G-U-C-V-R-E/).test(eventType)) { SIDC = `S${affil}APMFO-----` }\nelse if ((/a-.-A-M-F-Q-H/).test(eventType)) { SIDC = `S${affil}APMFH-----` }\n\nelse if ((/a-.-G-I-U-T/).test(eventType)) { SIDC = `S${affil}GPIUT---H-` }\nelse if ((/b-r-.-O-I-V/).test(eventType)) { SIDC = `S${affil}EFIPAC------` }\n// emergency open send symbol and reduce ttl\nelse if ((/b-a-o-opn/).test(eventType)) {\n SIDC = `S${affil}GDU-------`;\n //{ SIDC = `SFGPUCI-----` ;\n ttl = 36;\n node.warn('emergency Open');\n}\nelse if ((/b-a-o-tbl/).test(eventType)) {\n SIDC = `S${affil}GDU-------`;\n //{ SIDC = `SFGPUCI-----` ;\n ttl = 36;\n node.warn('trouble emergency Open');\n}\nelse if ((/b-a-o-pan/).test(eventType)) {\n SIDC = `S${affil}GXU-------`;\n //{ SIDC = `SFGPUCI-----` ;\n ttl = 36;\n node.warn('ring the bell emergency Open');\n}\n//emergency is closed SFGDU-------\nelse if ((/b-a-o-can/).test(eventType)) {\n //deleted = true\n SIDC = `SFGPUCI-----`; \n node.warn('emergency closed');\n }\n\n\nconsole.log(eventType, \"--->\", SIDC);\nSIDC = SIDC.substr(0, 12).toUpperCase();\n\n/* \nPoints on the Worldmap can only have one uniquite identifier, which is also\nthat Points display name. If possible, use a Callsign, otherwise use UID.\n*/\nlet callsign;\nlet _contact = detail[\"contact\"];\nif (_contact === undefined) {\n callsign = uid;\n} else {\n callsign = _contact[0][\"$\"][\"callsign\"];\n}\n\n/* Mouse-over Label */\nlet label = `Callsign: ${callsign} UID: ${uid}
Type: ${eventType} SIDC: ${SIDC}`\n\nlet remarks = detail[\"remarks\"];\nif (remarks) {\n let remark = remarks[0][\"$\"];\n label = `${label}
${remark}`;\n}\n\nlet track = detail[\"track\"]\nlet bearing = null;\nlet speed = null;\n\nif (track) {\n let course = +track[0][\"$\"][\"course\"];\n if (course) {\n if (course !== invalid) {\n bearing = course;\n }\n }\n\n let _speed = +track[0][\"$\"][\"speed\"];\n if (_speed) {\n if (_speed !== invalid) {\n speed = _speed;\n }\n }\n}\n\n/* \nIf CoT Point CE is set and is not invalid, use that as Worldmap Point Accuracy. \n*/\nlet accuracy = null;\nlet ce = +event[\"point\"][0][\"$\"][\"ce\"];\nif (ce != invalid) { accuracy = ce; }\n\n/* Add a helpful weblink to Worldmap Points. */\nlet weblink = null;\nif (uid.includes(\"ICAO\")) {\n weblink = `https://globe.adsbexchange.com/?icao=${uid.replace(\"ICAO-\", \"\")}`;\n} else if (uid.includes(\"APRS\")) {\n weblink = `https://qrz.com/db/${uid.replace(\"APRS.\", \"\").split(\"-\")[0]}`;\n}\nelse {\n weblink = 'https://spatialillusions.com/unitgenerator/';\n}\n\nlet lat = +event[\"point\"][0][\"$\"][\"lat\"];\nlet lon = +event[\"point\"][0][\"$\"][\"lon\"];\nlet alt = +event[\"point\"][0][\"$\"][\"hae\"];\nif (alt == invalid) { alt = undefined; }\n\nif (lat == 0 && lon == 0 && !alt) {\n console.log(callsign, \"is at null island.\", eventType)\n lat = Math.round(1000000 * lat + Math.random() * 10000) / 1000000;\n lon = Math.round(1000000 * lon + Math.random() * 10000) / 1000000;\n}\n\n/* Serialize as a Worldmap compatible Payload. */\nmsg.payload = {\n name: callsign,\n tooltip: label,\n deleted: deleted,\n lat: lat,\n lon: lon,\n alt: alt,\n speed: speed,\n bearing: bearing,\n accuracy: accuracy,\n type: eventType,\n SIDC: SIDC,\n ttl: ttl,\n weblink: weblink,\n layer: eventType,\n options: { scale: 0.8 }\n}\n\nreturn msg;",
+ "outputs": 1,
+ "timeout": "",
+ "noerr": 0,
+ "initialize": "",
+ "finalize": "",
+ "libs": [],
+ "x": 1010,
+ "y": 160,
+ "wires": [
+ [
+ "3a1205e793d055fa",
+ "885b808dd8a4895d"
+ ]
+ ]
+ },
+ {
+ "id": "bbaeafe55e6385e4",
+ "type": "comment",
+ "z": "db2b165142e8c36a",
+ "name": "APi Token here",
+ "info": "",
+ "x": 880,
+ "y": 400,
+ "wires": []
+ }
+]
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..b5e3e0b
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,15 @@
+version: '3'
+
+services:
+ FreeTAKHub-webmap:
+ image: freetak-webmap:latest
+ pull_policy: build
+ build:
+ context: .
+ dockerfile: Dockerfile
+ ports:
+ - '1880:1880'
+ environment:
+ FTH_FTS_URL: 'free-tak-server'
+ FTH_FTS_TCP_Port: 8087
+ FTH_FTS_API_Auth: ''