@tak-ps/node-cot 2.2.1 → 2.3.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,32 @@
1
+ name: Test
2
+
3
+ on:
4
+ pull_request:
5
+ types:
6
+ - opened
7
+ - synchronize
8
+ - reopened
9
+ - ready_for_review
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ if: github.event.pull_request.draft == false
15
+ steps:
16
+ - uses: actions/checkout@v3
17
+ with:
18
+ ref: ${{ github.event.pull_request.head.sha }}
19
+
20
+ - uses: actions/setup-node@v3
21
+ with:
22
+ node-version: 18
23
+ registry-url: https://registry.npmjs.org/
24
+
25
+ - name: Install
26
+ run: npm install
27
+
28
+ - name: Test
29
+ run: npm test
30
+
31
+ - name: Lint
32
+ run: npm run lint
package/CHANGELOG.md CHANGED
@@ -10,6 +10,10 @@
10
10
 
11
11
  ## Version History
12
12
 
13
+ ### v2.3.0
14
+
15
+ - :tada: `from_geojson(Feature.Polygon)` Support
16
+
13
17
  ### v2.2.1
14
18
 
15
19
  - :bug: `event.display` => `event.detail`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tak-ps/node-cot",
3
3
  "type": "module",
4
- "version": "2.2.1",
4
+ "version": "2.3.0",
5
5
  "description": "Lightweight JavaScript library for parsing and manipulating TAK messages",
6
6
  "main": "index.js",
7
7
  "scripts": {
@@ -9,6 +9,7 @@
9
9
  "lint": "eslint *.js src/*.js test/*.js"
10
10
  },
11
11
  "dependencies": {
12
+ "@turf/point-on-feature": "^6.5.0",
12
13
  "protobufjs": "^7.1.2",
13
14
  "uuid": "^9.0.0",
14
15
  "xml-js": "^1.6.11"
package/src/xml.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import xmljs from 'xml-js';
2
2
  import Util from './util.js';
3
+ import PointOnFeature from '@turf/point-on-feature';
3
4
 
4
5
  /**
5
6
  * Convert to and from an XML CoT message
@@ -20,9 +21,9 @@ export default class XMLCot {
20
21
  }
21
22
 
22
23
  // Attempt to cast all point to numerics
23
- for (const key of Object.keys(this.raw.event.point)) {
24
- if (!isNaN(parseFloat(this.raw.event.point[key]))) {
25
- this.raw.event.point[key] = parseFloat(this.raw.event.point[key]);
24
+ for (const key of Object.keys(this.raw.event.point._attributes)) {
25
+ if (!isNaN(parseFloat(this.raw.event.point._attributes[key]))) {
26
+ this.raw.event.point._attributes[key] = parseFloat(this.raw.event.point._attributes[key]);
26
27
  }
27
28
  }
28
29
  }
@@ -36,7 +37,6 @@ export default class XMLCot {
36
37
  */
37
38
  static from_geojson(feature) {
38
39
  if (feature.type !== 'Feature') throw new Error('Must be GeoJSON Feature');
39
- if (!feature.geometry || feature.geometry.type !== 'Point') throw new Error('Must be GeoJSON Point Feature');
40
40
  if (!feature.properties) throw new Error('Feature must have properties');
41
41
 
42
42
  const cot = {
@@ -54,8 +54,36 @@ export default class XMLCot {
54
54
  if (feature.properties[key]) cot.event._attributes[key] = feature.properties[key];
55
55
  }
56
56
 
57
- cot.event.point._attributes.lon = feature.geometry.coordinates[0];
58
- cot.event.point._attributes.lat = feature.geometry.coordinates[1];
57
+ if (!feature.geometry) throw new Error('Must have Geometry');
58
+ if (!['Point', 'Polygon'].includes(feature.geometry.type)) throw new Error('Unsupported Geoemtry Type');
59
+
60
+ if (feature.geometry.type === 'Point') {
61
+ cot.event.point._attributes.lon = feature.geometry.coordinates[0];
62
+ cot.event.point._attributes.lat = feature.geometry.coordinates[1];
63
+ } else if (feature.geometry.type === 'Polygon') {
64
+ // Inner rings are not yet supported
65
+
66
+ cot.event.detail.link = [];
67
+ feature.geometry.coordinates[0].pop(); // Dont' Close Loop in COT
68
+ for (const coord of feature.geometry.coordinates[0]) {
69
+ cot.event.detail.link.push({
70
+ _attributes: {
71
+ point: `${coord[1]},${coord[0]}`
72
+ }
73
+ });
74
+ }
75
+
76
+ cot.event.detail.labels_on = { _attributes: { value: 'false' } };
77
+ cot.event.detail.tog = { _attributes: { enabled: '0' } };
78
+ cot.event.detail.strokeColor = { _attributes: { value: '-256' } };
79
+ cot.event.detail.strokeWeight = { _attributes: { value: '3.0' } };
80
+ cot.event.detail.strokeStyle = { _attributes: { value: 'solid' } };
81
+ cot.event.detail.fillColor = { _attributes: { value: '-1761607936' } };
82
+
83
+ const centre = PointOnFeature(feature);
84
+ cot.event.point._attributes.lon = centre.geometry.coordinates[0];
85
+ cot.event.point._attributes.lat = centre.geometry.coordinates[1];
86
+ }
59
87
 
60
88
  return new XMLCot(cot);
61
89
  }
package/test/cot.test.js CHANGED
@@ -17,11 +17,11 @@ test('Decode COT message', (t) => {
17
17
  },
18
18
  'point': {
19
19
  '_attributes': {
20
- 'lat': '1.234567',
21
- 'lon': '-3.141592',
22
- 'hae': '-25.7',
23
- 'ce': '9.9',
24
- 'le': '9999999.0'
20
+ 'lat': 1.234567,
21
+ 'lon': -3.141592,
22
+ 'hae': -25.7,
23
+ 'ce': 9.9,
24
+ 'le': 9999999.0
25
25
  }
26
26
  },
27
27
  'detail': {
@@ -68,11 +68,11 @@ test('Decode COT message', (t) => {
68
68
  },
69
69
  'point': {
70
70
  '_attributes': {
71
- 'lat': '0.000000',
72
- 'lon': '0.000000',
73
- 'hae': '0.0',
74
- 'ce': '9999999.0',
75
- 'le': '9999999.0'
71
+ 'lat': 0.000000,
72
+ 'lon': 0.000000,
73
+ 'hae': 0.0,
74
+ 'ce': 9999999.0,
75
+ 'le': 9999999.0
76
76
  }
77
77
  },
78
78
  'detail': {
@@ -134,11 +134,11 @@ test('Encode COT message', (t) => {
134
134
  },
135
135
  'point': {
136
136
  '_attributes': {
137
- 'lat': '1.234567',
138
- 'lon': '-3.141592',
139
- 'hae': '-25.7',
140
- 'ce': '9.9',
141
- 'le': '9999999.0'
137
+ 'lat': 1.234567,
138
+ 'lon': -3.141592,
139
+ 'hae': -25.7,
140
+ 'ce': 9.9,
141
+ 'le': 9999999
142
142
  }
143
143
  },
144
144
  'detail': {
@@ -168,7 +168,7 @@ test('Encode COT message', (t) => {
168
168
 
169
169
  t.deepEquals(
170
170
  (new XML(packet)).to_xml(),
171
- '<event version="2.0" uid="ANDROID-deadbeef" type="a-f-G-U-C" how="m-g" time="2021-02-27T20:32:24.771Z" start="2021-02-27T20:32:24.771Z" stale="2021-02-27T20:38:39.771Z"><point lat="1.234567" lon="-3.141592" hae="-25.7" ce="9.9" le="9999999.0"/><detail><takv os="29" version="4.0.0.0 (deadbeef).1234567890-CIV" device="Some Android Device" platform="ATAK-CIV"/><contact xmppUsername="xmpp@host.com" endpoint="*:-1:stcp" callsign="JENNY"/><uid Droid="JENNY"/><precisionlocation altsrc="GPS" geopointsrc="GPS"/><__group role="Team Member" name="Cyan"/><status battery="78"/><track course="80.24833892285461" speed="0.0"/></detail></event>'
171
+ '<event version="2.0" uid="ANDROID-deadbeef" type="a-f-G-U-C" how="m-g" time="2021-02-27T20:32:24.771Z" start="2021-02-27T20:32:24.771Z" stale="2021-02-27T20:38:39.771Z"><point lat="1.234567" lon="-3.141592" hae="-25.7" ce="9.9" le="9999999"/><detail><takv os="29" version="4.0.0.0 (deadbeef).1234567890-CIV" device="Some Android Device" platform="ATAK-CIV"/><contact xmppUsername="xmpp@host.com" endpoint="*:-1:stcp" callsign="JENNY"/><uid Droid="JENNY"/><precisionlocation altsrc="GPS" geopointsrc="GPS"/><__group role="Team Member" name="Cyan"/><status battery="78"/><track course="80.24833892285461" speed="0.0"/></detail></event>'
172
172
  );
173
173
 
174
174
  t.end();
@@ -203,11 +203,11 @@ test('Parse GeoChat message', (t) => {
203
203
  },
204
204
  'point': {
205
205
  '_attributes': {
206
- 'lat': '1.234567',
207
- 'lon': '-3.141592',
208
- 'hae': '-25.8',
209
- 'ce': '9.9',
210
- 'le': '9999999.0'
206
+ 'lat': 1.234567,
207
+ 'lon': -3.141592,
208
+ 'hae': -25.8,
209
+ 'ce': 9.9,
210
+ 'le': 9999999
211
211
  }
212
212
  },
213
213
  'detail': {
@@ -0,0 +1,78 @@
1
+ import test from 'tape';
2
+ import { XML } from '../index.js';
3
+
4
+ test('XML.from_geojson - point', (t) => {
5
+ const geo = XML.from_geojson({
6
+ type: 'Feature',
7
+ properties: {},
8
+ geometry: {
9
+ type: 'Point',
10
+ coordinates: [1.1, 2.2]
11
+ }
12
+ });
13
+
14
+ t.equals(geo.raw.event._attributes.version, '2.0');
15
+ t.equals(geo.raw.event._attributes.type, 'a-f-G');
16
+ t.equals(geo.raw.event._attributes.how, 'm-g');
17
+ t.equals(geo.raw.event._attributes.uid.length, 36);
18
+ t.equals(geo.raw.event._attributes.time.length, 24);
19
+ t.equals(geo.raw.event._attributes.start.length, 24);
20
+ t.equals(geo.raw.event._attributes.stale.length, 24);
21
+
22
+ t.deepEquals(geo.raw.event.point, {
23
+ _attributes: { lat: 2.2, lon: 1.1, hae: 0, ce: 9999999, le: 9999999 }
24
+ });
25
+
26
+ t.deepEquals(geo.raw.event.detail, {
27
+ contact: { _attributes: { callsign: 'UNKNOWN' } }
28
+ });
29
+
30
+ t.end();
31
+ });
32
+
33
+ test('XML.from_geojson - polygon', (t) => {
34
+ const geo = XML.from_geojson({
35
+ type: 'Feature',
36
+ properties: {},
37
+ geometry: {
38
+ type: 'Polygon',
39
+ coordinates: [[
40
+ [-108.587, 39.098],
41
+ [-108.587, 39.032],
42
+ [-108.505, 39.032],
43
+ [-108.505, 39.098],
44
+ [-108.587, 39.098]
45
+ ]]
46
+ }
47
+ });
48
+
49
+ t.equals(geo.raw.event._attributes.version, '2.0');
50
+ t.equals(geo.raw.event._attributes.type, 'a-f-G');
51
+ t.equals(geo.raw.event._attributes.how, 'm-g');
52
+ t.equals(geo.raw.event._attributes.uid.length, 36);
53
+ t.equals(geo.raw.event._attributes.time.length, 24);
54
+ t.equals(geo.raw.event._attributes.start.length, 24);
55
+ t.equals(geo.raw.event._attributes.stale.length, 24);
56
+
57
+ t.deepEquals(geo.raw.event.point, {
58
+ _attributes: { lat: 39.065, lon: -108.54599999999999, hae: 0, ce: 9999999, le: 9999999 }
59
+ });
60
+
61
+ t.deepEquals(geo.raw.event.detail, {
62
+ contact: { _attributes: { callsign: 'UNKNOWN' } },
63
+ link: [
64
+ { _attributes: { point: '39.098,-108.587' } },
65
+ { _attributes: { point: '39.032,-108.587' } },
66
+ { _attributes: { point: '39.032,-108.505' } },
67
+ { _attributes: { point: '39.098,-108.505' } }
68
+ ],
69
+ labels_on: { _attributes: { value: 'false' } },
70
+ tog: { _attributes: { enabled: '0' } },
71
+ strokeColor: { _attributes: { value: '-256' } },
72
+ strokeWeight: { _attributes: { value: '3.0' } },
73
+ strokeStyle: { _attributes: { value: 'solid' } },
74
+ fillColor: { _attributes: { value: '-1761607936' } }
75
+ });
76
+
77
+ t.end();
78
+ });