@tak-ps/node-cot 2.4.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.
@@ -0,0 +1,29 @@
1
+ name: NPM Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - '*'
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v3
13
+
14
+ - name: Get tag
15
+ id: tag
16
+ uses: dawidd6/action-get-tag@v1
17
+
18
+ - uses: actions/setup-node@v3
19
+ with:
20
+ node-version: 18
21
+ registry-url: https://registry.npmjs.org/
22
+
23
+ - name: npm install
24
+ run: npm install
25
+
26
+ - name: npm publish
27
+ run: npm publish
28
+ env:
29
+ NPM_TOKEN: ${{ secrets.NPM_SECRET }}
@@ -1,6 +1,9 @@
1
1
  name: Test
2
2
 
3
3
  on:
4
+ push:
5
+ branches:
6
+ - main
4
7
  pull_request:
5
8
  types:
6
9
  - opened
@@ -25,8 +28,8 @@ jobs:
25
28
  - name: Install
26
29
  run: npm install
27
30
 
28
- - name: Test
29
- run: npm test
30
-
31
31
  - name: Lint
32
32
  run: npm run lint
33
+
34
+ - name: Test
35
+ run: npm test
package/CHANGELOG.md CHANGED
@@ -10,6 +10,15 @@
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
+
18
+ ### v2.5.0
19
+
20
+ - :tada: Automatically perform basic schema validation on CoT Creation
21
+
13
22
  ### v2.4.0
14
23
 
15
24
  - :tada: `from_geojson(Feature.LineString)` Support
package/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import XML from './src/xml.js';
1
+ import XML from './lib/xml.js';
2
2
 
3
3
  export { XML };
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
+ }
@@ -0,0 +1,83 @@
1
+ {
2
+ "type": "object",
3
+ "required": [
4
+ "event"
5
+ ],
6
+ "properties": {
7
+ "event": {
8
+ "type": "object",
9
+ "required": [
10
+ "_attributes",
11
+ "point"
12
+ ],
13
+ "properties": {
14
+ "_attributes": {
15
+ "type": "object",
16
+ "required": [
17
+ "version",
18
+ "uid",
19
+ "type",
20
+ "time",
21
+ "start",
22
+ "stale"
23
+ ],
24
+ "properties": {
25
+ "version": {
26
+ "type": "string"
27
+ },
28
+ "uid": {
29
+ "type": "string"
30
+ },
31
+ "type": {
32
+ "type": "string"
33
+ },
34
+ "time": {
35
+ "type": "string"
36
+ },
37
+ "start": {
38
+ "type": "string"
39
+ },
40
+ "stale": {
41
+ "type": "string"
42
+ }
43
+ }
44
+ },
45
+ "point": {
46
+ "type": "object",
47
+ "required": [
48
+ "_attributes"
49
+ ],
50
+ "properties": {
51
+ "_attributes": {
52
+ "type": "object",
53
+ "required": [
54
+ "lat",
55
+ "lon",
56
+ "hae",
57
+ "ce",
58
+ "le"
59
+ ],
60
+ "properties": {
61
+ "lat": {
62
+ "type": "number"
63
+ },
64
+ "lon": {
65
+ "type": "number"
66
+ },
67
+ "hae": {
68
+ "type": "number"
69
+ },
70
+ "ce": {
71
+ "type": "number"
72
+ },
73
+ "le": {
74
+ "type": "number"
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
File without changes
@@ -1,6 +1,12 @@
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';
5
+ import AJV from 'ajv';
6
+ import fs from 'fs';
7
+
8
+ const ajv = (new AJV({ allErrors: true })).compile(JSON.parse(fs.readFileSync(new URL('./schema.json', import.meta.url))));
9
+
4
10
 
5
11
  /**
6
12
  * Convert to and from an XML CoT message
@@ -26,6 +32,11 @@ export default class XMLCot {
26
32
  this.raw.event.point._attributes[key] = parseFloat(this.raw.event.point._attributes[key]);
27
33
  }
28
34
  }
35
+
36
+ if (!this.raw.event._attributes.uid) this.raw.event._attributes.uuid = Util.cot_uuid().uid;
37
+
38
+ ajv(this.raw);
39
+ if (ajv.errors) throw new Error(ajv.errors[0].message);
29
40
  }
30
41
 
31
42
  /**
@@ -61,36 +72,48 @@ export default class XMLCot {
61
72
  cot.event.point._attributes.lon = feature.geometry.coordinates[0];
62
73
  cot.event.point._attributes.lat = feature.geometry.coordinates[1];
63
74
  } else if (['Polygon', 'LineString'].includes(feature.geometry.type)) {
64
- cot.event._attributes.type = 'u-d-f';
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() } };
65
78
 
66
- if (feature.geometry.type === 'Polygon') {
67
- cot.event._attributes.type = 'u-d-r';
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';
68
91
 
69
- // Inner rings are not yet supported
70
92
  cot.event.detail.link = [];
71
- feature.geometry.coordinates[0].pop(); // Dont' Close Loop in COT
72
- for (const coord of feature.geometry.coordinates[0]) {
93
+ for (const coord of feature.geometry.coordinates) {
73
94
  cot.event.detail.link.push({
74
95
  _attributes: { point: `${coord[1]},${coord[0]}` }
75
96
  });
76
97
  }
77
- } else if (feature.geometry.type === 'LineString') {
78
- cot.event._attributes.type = 'u-d-f';
98
+ } else if (feature.geometry.type === 'Polygon') {
99
+ cot.event._attributes.type = 'u-d-r';
79
100
 
101
+ // Inner rings are not yet supported
80
102
  cot.event.detail.link = [];
81
- for (const coord of feature.geometry.coordinates) {
103
+ feature.geometry.coordinates[0].pop(); // Dont' Close Loop in COT
104
+ for (const coord of feature.geometry.coordinates[0]) {
82
105
  cot.event.detail.link.push({
83
106
  _attributes: { point: `${coord[1]},${coord[0]}` }
84
107
  });
85
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() } };
86
113
  }
87
114
 
88
115
  cot.event.detail.labels_on = { _attributes: { value: 'false' } };
89
116
  cot.event.detail.tog = { _attributes: { enabled: '0' } };
90
- cot.event.detail.strokeColor = { _attributes: { value: '-256' } };
91
- cot.event.detail.strokeWeight = { _attributes: { value: '3.0' } };
92
- cot.event.detail.strokeStyle = { _attributes: { value: 'solid' } };
93
- cot.event.detail.fillColor = { _attributes: { value: '-1761607936' } };
94
117
 
95
118
  const centre = PointOnFeature(feature);
96
119
  cot.event.point._attributes.lon = centre.geometry.coordinates[0];
package/package.json CHANGED
@@ -1,15 +1,17 @@
1
1
  {
2
2
  "name": "@tak-ps/node-cot",
3
3
  "type": "module",
4
- "version": "2.4.0",
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": {
8
8
  "test": "tape test/**.test.js",
9
- "lint": "eslint *.js src/*.js test/*.js"
9
+ "lint": "eslint *.js lib/*.js test/*.js"
10
10
  },
11
11
  "dependencies": {
12
12
  "@turf/point-on-feature": "^6.5.0",
13
+ "ajv": "^8.11.2",
14
+ "color": "^4.2.3",
13
15
  "protobufjs": "^7.1.2",
14
16
  "uuid": "^9.0.0",
15
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: '-256' } },
72
- strokeWeight: { _attributes: { value: '3.0' } },
71
+ strokeColor: { _attributes: { value: 16776960 } },
72
+ strokeWeight: { _attributes: { value: 3 } },
73
73
  strokeStyle: { _attributes: { value: 'solid' } },
74
- fillColor: { _attributes: { value: '-1761607936' } }
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: '-256' } },
120
- strokeWeight: { _attributes: { value: '3.0' } },
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
+ });
@@ -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])