@tak-ps/node-cot 2.5.0 → 2.6.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.
- package/CHANGELOG.md +5 -0
- package/lib/color.js +46 -0
- package/lib/xml.js +26 -13
- package/package.json +2 -1
- package/test/from_geojson.test.js +5 -6
- package/test/styles.test.js +33 -0
- package/scripts/convert.js +0 -29
- package/scripts/parse.js +0 -34
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
## Version History
|
|
12
12
|
|
|
13
|
+
### v2.6.0
|
|
14
|
+
|
|
15
|
+
- :tada: Add support for passing style properties via GeoJSON Properties
|
|
16
|
+
- :rocket: Add support for encoding/decoding 32bit signed ARGB values
|
|
17
|
+
|
|
13
18
|
### v2.5.0
|
|
14
19
|
|
|
15
20
|
- :tada: Automatically perform basic schema validation on CoT Creation
|
package/lib/color.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import _color from 'color';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper functions for working with CoT Colours
|
|
5
|
+
*
|
|
6
|
+
* @param {Number|Number[]} color 32bit packged ARGB or [A, R, G, B]
|
|
7
|
+
* @class
|
|
8
|
+
*/
|
|
9
|
+
export default class Color {
|
|
10
|
+
constructor(color) {
|
|
11
|
+
if (!isNaN(Number(color))) {
|
|
12
|
+
this.r = (color >> 16) & 255;
|
|
13
|
+
this.g = (color >> 8) & 255;
|
|
14
|
+
this.b = (color >> 0) & 255;
|
|
15
|
+
this.a = ((color >> 24) & 255) / 255;
|
|
16
|
+
} else if (Array.isArray(color)) {
|
|
17
|
+
this.a = color[0];
|
|
18
|
+
this.r = color[1];
|
|
19
|
+
this.g = color[2];
|
|
20
|
+
this.b = color[3];
|
|
21
|
+
} else {
|
|
22
|
+
const c = _color(color);
|
|
23
|
+
|
|
24
|
+
this.a = c.valpha;
|
|
25
|
+
this.r = c.color[0];
|
|
26
|
+
this.g = c.color[1];
|
|
27
|
+
this.b = c.color[2];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
as_32bit() {
|
|
32
|
+
return (this.a << 24) | (this.r << 16) | (this.g << 8) | this.b;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
as_opacity() {
|
|
36
|
+
return this.a;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
as_argb() {
|
|
40
|
+
return [this.a, this.r, this.b, this.g];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
as_rgb() {
|
|
44
|
+
return [this.r, this.b, this.g];
|
|
45
|
+
}
|
|
46
|
+
}
|
package/lib/xml.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import xmljs from 'xml-js';
|
|
2
2
|
import Util from './util.js';
|
|
3
|
+
import Color from './color.js';
|
|
3
4
|
import PointOnFeature from '@turf/point-on-feature';
|
|
4
5
|
import AJV from 'ajv';
|
|
5
6
|
import fs from 'fs';
|
|
@@ -71,36 +72,48 @@ export default class XMLCot {
|
|
|
71
72
|
cot.event.point._attributes.lon = feature.geometry.coordinates[0];
|
|
72
73
|
cot.event.point._attributes.lat = feature.geometry.coordinates[1];
|
|
73
74
|
} else if (['Polygon', 'LineString'].includes(feature.geometry.type)) {
|
|
74
|
-
|
|
75
|
+
const stroke = new Color(feature.properties.stroke || -1761607936);
|
|
76
|
+
if (feature.properties['stroke-opacity']) stroke.a = feature.properties['stroke-opacity'];
|
|
77
|
+
cot.event.detail.strokeColor = { _attributes: { value: stroke.as_32bit() } };
|
|
75
78
|
|
|
76
|
-
if (feature.
|
|
77
|
-
|
|
79
|
+
if (!feature.properties['stroke-width']) feature.properties['stroke-width'] = 3;
|
|
80
|
+
cot.event.detail.strokeWeight = { _attributes: {
|
|
81
|
+
value: feature.properties['stroke-width']
|
|
82
|
+
} };
|
|
83
|
+
|
|
84
|
+
if (!feature.properties['stroke-style']) feature.properties['stroke-style'] = 'solid';
|
|
85
|
+
cot.event.detail.strokeStyle = { _attributes: {
|
|
86
|
+
value: feature.properties['stroke-style']
|
|
87
|
+
} };
|
|
88
|
+
|
|
89
|
+
if (feature.geometry.type === 'LineString') {
|
|
90
|
+
cot.event._attributes.type = 'u-d-f';
|
|
78
91
|
|
|
79
|
-
// Inner rings are not yet supported
|
|
80
92
|
cot.event.detail.link = [];
|
|
81
|
-
feature.geometry.coordinates
|
|
82
|
-
for (const coord of feature.geometry.coordinates[0]) {
|
|
93
|
+
for (const coord of feature.geometry.coordinates) {
|
|
83
94
|
cot.event.detail.link.push({
|
|
84
95
|
_attributes: { point: `${coord[1]},${coord[0]}` }
|
|
85
96
|
});
|
|
86
97
|
}
|
|
87
|
-
} else if (feature.geometry.type === '
|
|
88
|
-
cot.event._attributes.type = 'u-d-
|
|
98
|
+
} else if (feature.geometry.type === 'Polygon') {
|
|
99
|
+
cot.event._attributes.type = 'u-d-r';
|
|
89
100
|
|
|
101
|
+
// Inner rings are not yet supported
|
|
90
102
|
cot.event.detail.link = [];
|
|
91
|
-
|
|
103
|
+
feature.geometry.coordinates[0].pop(); // Dont' Close Loop in COT
|
|
104
|
+
for (const coord of feature.geometry.coordinates[0]) {
|
|
92
105
|
cot.event.detail.link.push({
|
|
93
106
|
_attributes: { point: `${coord[1]},${coord[0]}` }
|
|
94
107
|
});
|
|
95
108
|
}
|
|
109
|
+
|
|
110
|
+
const fill = new Color(feature.properties.fill || -1761607936);
|
|
111
|
+
if (feature.properties['fill-opacity']) fill.a = feature.properties['fill-opacity'];
|
|
112
|
+
cot.event.detail.fillColor = { _attributes: { value: fill.as_32bit() } };
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
cot.event.detail.labels_on = { _attributes: { value: 'false' } };
|
|
99
116
|
cot.event.detail.tog = { _attributes: { enabled: '0' } };
|
|
100
|
-
cot.event.detail.strokeColor = { _attributes: { value: '-256' } };
|
|
101
|
-
cot.event.detail.strokeWeight = { _attributes: { value: '3.0' } };
|
|
102
|
-
cot.event.detail.strokeStyle = { _attributes: { value: 'solid' } };
|
|
103
|
-
cot.event.detail.fillColor = { _attributes: { value: '-1761607936' } };
|
|
104
117
|
|
|
105
118
|
const centre = PointOnFeature(feature);
|
|
106
119
|
cot.event.point._attributes.lon = centre.geometry.coordinates[0];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tak-ps/node-cot",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.6.0",
|
|
5
5
|
"description": "Lightweight JavaScript library for parsing and manipulating TAK messages",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"scripts": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@turf/point-on-feature": "^6.5.0",
|
|
13
13
|
"ajv": "^8.11.2",
|
|
14
|
+
"color": "^4.2.3",
|
|
14
15
|
"protobufjs": "^7.1.2",
|
|
15
16
|
"uuid": "^9.0.0",
|
|
16
17
|
"xml-js": "^1.6.11"
|
|
@@ -68,10 +68,10 @@ test('XML.from_geojson - Polygon', (t) => {
|
|
|
68
68
|
],
|
|
69
69
|
labels_on: { _attributes: { value: 'false' } },
|
|
70
70
|
tog: { _attributes: { enabled: '0' } },
|
|
71
|
-
strokeColor: { _attributes: { value:
|
|
72
|
-
strokeWeight: { _attributes: { value:
|
|
71
|
+
strokeColor: { _attributes: { value: 16776960 } },
|
|
72
|
+
strokeWeight: { _attributes: { value: 3 } },
|
|
73
73
|
strokeStyle: { _attributes: { value: 'solid' } },
|
|
74
|
-
fillColor: { _attributes: { value:
|
|
74
|
+
fillColor: { _attributes: { value: 16776960 } }
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
t.end();
|
|
@@ -116,10 +116,9 @@ test('XML.from_geojson - LineString', (t) => {
|
|
|
116
116
|
],
|
|
117
117
|
labels_on: { _attributes: { value: 'false' } },
|
|
118
118
|
tog: { _attributes: { enabled: '0' } },
|
|
119
|
-
strokeColor: { _attributes: { value:
|
|
120
|
-
strokeWeight: { _attributes: { value:
|
|
119
|
+
strokeColor: { _attributes: { value: 16776960 } },
|
|
120
|
+
strokeWeight: { _attributes: { value: 3 } },
|
|
121
121
|
strokeStyle: { _attributes: { value: 'solid' } },
|
|
122
|
-
fillColor: { _attributes: { value: '-1761607936' } }
|
|
123
122
|
});
|
|
124
123
|
|
|
125
124
|
t.end();
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import test from 'tape';
|
|
2
|
+
import { XML } from '../index.js';
|
|
3
|
+
|
|
4
|
+
test('XML.from_geojson - Polygon Style', (t) => {
|
|
5
|
+
const geo = XML.from_geojson({
|
|
6
|
+
type: 'Feature',
|
|
7
|
+
properties: {
|
|
8
|
+
|
|
9
|
+
},
|
|
10
|
+
geometry: {
|
|
11
|
+
type: 'Point',
|
|
12
|
+
coordinates: [1.1, 2.2]
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
t.equals(geo.raw.event._attributes.version, '2.0');
|
|
17
|
+
t.equals(geo.raw.event._attributes.type, 'a-f-G');
|
|
18
|
+
t.equals(geo.raw.event._attributes.how, 'm-g');
|
|
19
|
+
t.equals(geo.raw.event._attributes.uid.length, 36);
|
|
20
|
+
t.equals(geo.raw.event._attributes.time.length, 24);
|
|
21
|
+
t.equals(geo.raw.event._attributes.start.length, 24);
|
|
22
|
+
t.equals(geo.raw.event._attributes.stale.length, 24);
|
|
23
|
+
|
|
24
|
+
t.deepEquals(geo.raw.event.point, {
|
|
25
|
+
_attributes: { lat: 2.2, lon: 1.1, hae: 0, ce: 9999999, le: 9999999 }
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
t.deepEquals(geo.raw.event.detail, {
|
|
29
|
+
contact: { _attributes: { callsign: 'UNKNOWN' } }
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
t.end();
|
|
33
|
+
});
|
package/scripts/convert.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
|
-
const {cot, proto} = require(path.join(__dirname, '../index.js'))
|
|
3
|
-
|
|
4
|
-
const run = (message) => {
|
|
5
|
-
if (!message) {
|
|
6
|
-
console.error('Enter a TAK message')
|
|
7
|
-
return
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const bufferMessage = typeof message !== Buffer ? Buffer.from(message, 'hex') : message
|
|
11
|
-
|
|
12
|
-
if (bufferMessage[0] === 191) { // TAK message format 0xbf
|
|
13
|
-
console.log('TAK message received')
|
|
14
|
-
const trimmedBuffer = bufferMessage.slice(3, bufferMessage.length) // remove tak message header from content
|
|
15
|
-
if (bufferMessage[1] === 0) { // is COT XML
|
|
16
|
-
console.error('Enter a TAK proto message')
|
|
17
|
-
} else if (bufferMessage[1] === 1) { // is Protobuf
|
|
18
|
-
console.log('TAK protobuf format')
|
|
19
|
-
const protoMessage = proto.proto2js(trimmedBuffer)
|
|
20
|
-
const cotMessage = proto.protojs2cotjs(protoMessage)
|
|
21
|
-
console.log(cotMessage)
|
|
22
|
-
console.log(cot.js2xml(cotMessage))
|
|
23
|
-
}
|
|
24
|
-
} else { // not TAK message format
|
|
25
|
-
console.error('Enter a TAK proto message')
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
run(process.argv[2])
|
package/scripts/parse.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
|
-
const {cot, proto} = require(path.join(__dirname, '../index.js'))
|
|
3
|
-
|
|
4
|
-
// https://github.com/deptofdefense/AndroidTacticalAssaultKit-CIV/blob/master/commoncommo/core/impl/protobuf/protocol.txt
|
|
5
|
-
|
|
6
|
-
const run = (message) => {
|
|
7
|
-
if (!message) {
|
|
8
|
-
console.error('Enter a TAK message')
|
|
9
|
-
return
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const bufferMessage = typeof message !== Buffer ? Buffer.from(message, 'hex') : message
|
|
13
|
-
|
|
14
|
-
if (bufferMessage[0] === 191) { // TAK message format 0xbf
|
|
15
|
-
console.log('TAK message received')
|
|
16
|
-
const trimmedBuffer = bufferMessage.slice(3, bufferMessage.length) // remove tak message header from content
|
|
17
|
-
if (bufferMessage[1] === 0) { // is COT XML
|
|
18
|
-
console.log('COT XML format')
|
|
19
|
-
console.log(cot.xml2js(trimmedBuffer)) // try parsing raw XML
|
|
20
|
-
} else if (bufferMessage[1] === 1) { // is Protobuf
|
|
21
|
-
console.log('TAK protobuf format')
|
|
22
|
-
console.log(proto.proto2js(trimmedBuffer))
|
|
23
|
-
}
|
|
24
|
-
} else { // not TAK message format
|
|
25
|
-
try {
|
|
26
|
-
console.log('COT XML received')
|
|
27
|
-
console.log(cot.xml2js(message)) // try parsing raw XML
|
|
28
|
-
} catch (e) {
|
|
29
|
-
console.error('Failed to parse message', e)
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
run(process.argv[2])
|