@tak-ps/node-cot 12.11.0 → 12.12.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/dist/lib/cot.js CHANGED
@@ -1,3 +1,4 @@
1
+ import crypto from 'node:crypto';
1
2
  import protobuf from 'protobufjs';
2
3
  import Err from '@openaddresses/batch-error';
3
4
  import { diff } from 'json-diff-ts';
@@ -104,6 +105,20 @@ export default class CoT {
104
105
  const diffs = diff(a, b);
105
106
  return diffs.length > 0;
106
107
  }
108
+ /**
109
+ * Returns or sets the Callsign of the CoT
110
+ */
111
+ callsign(callsign) {
112
+ if (!this.raw.event.detail)
113
+ this.raw.event.detail = {};
114
+ if (callsign && !this.raw.event.detail.contact) {
115
+ this.raw.event.detail.contact = { _attributes: { callsign } };
116
+ }
117
+ else if (callsign && this.raw.event.detail.contact) {
118
+ this.raw.event.detail.contact._attributes.callsign = callsign;
119
+ }
120
+ return this.raw.event.detail.contact ? this.raw.event.detail.contact._attributes.callsign : 'UNKNOWN';
121
+ }
107
122
  /**
108
123
  * Returns or sets the UID of the CoT
109
124
  */
@@ -129,241 +144,64 @@ export default class CoT {
129
144
  }
130
145
  destArr.push({ _attributes: dest });
131
146
  this.raw.event.detail.marti.dest = destArr;
147
+ return this;
132
148
  }
133
- addLink(link) {
149
+ addVideo(video, connection) {
134
150
  if (!this.raw.event.detail)
135
151
  this.raw.event.detail = {};
136
- let linkArr = [];
137
- if (this.raw.event.detail.link && !Array.isArray(this.raw.event.detail.link)) {
138
- linkArr = [this.raw.event.detail.link];
139
- }
140
- else if (this.raw.event.detail.link && Array.isArray(this.raw.event.detail.link)) {
141
- linkArr = this.raw.event.detail.link;
142
- }
143
- linkArr.push({ _attributes: link });
144
- this.raw.event.detail.link = linkArr;
145
- }
146
- /**
147
- * Return an CoT Message given a GeoJSON Feature
148
- *
149
- * @param {Object} feature GeoJSON Point Feature
150
- *
151
- * @return {CoT}
152
- */
153
- static from_geojson(feature) {
154
- checkFeat(feature);
155
- if (checkFeat.errors)
156
- throw new Err(400, null, `${checkFeat.errors[0].message} (${checkFeat.errors[0].instancePath})`);
157
- const cot = {
158
- event: {
159
- _attributes: Util.cot_event_attr(feature.properties.type || 'a-f-G', feature.properties.how || 'm-g', feature.properties.time, feature.properties.start, feature.properties.stale),
160
- point: Util.cot_point(),
161
- detail: Util.cot_event_detail(feature.properties.callsign)
162
- }
163
- };
164
- if (feature.id)
165
- cot.event._attributes.uid = String(feature.id);
166
- if (feature.properties.callsign && !feature.id)
167
- cot.event._attributes.uid = feature.properties.callsign;
168
- if (!cot.event.detail)
169
- cot.event.detail = {};
170
- if (feature.properties.droid) {
171
- cot.event.detail.uid = { _attributes: { Droid: feature.properties.droid } };
172
- }
173
- if (feature.properties.archived) {
174
- cot.event.detail.archive = { _attributes: {} };
175
- }
176
- if (feature.properties.links) {
177
- if (!cot.event.detail.link)
178
- cot.event.detail.link = [];
179
- else if (!Array.isArray(cot.event.detail.link))
180
- cot.event.detail.link = [cot.event.detail.link];
181
- cot.event.detail.link.push(...feature.properties.links.map((link) => {
182
- return { _attributes: link };
183
- }));
184
- }
185
- if (feature.properties.dest) {
186
- const dest = !Array.isArray(feature.properties.dest) ? [feature.properties.dest] : feature.properties.dest;
187
- cot.event.detail.marti = {
188
- dest: dest.map((dest) => {
189
- return { _attributes: { ...dest } };
190
- })
191
- };
152
+ if (this.raw.event.detail.__video)
153
+ throw new Err(400, null, 'A video stream already exists on this CoT');
154
+ if (!video.url)
155
+ throw new Err(400, null, 'A Video URL must be provided');
156
+ if (!video.uid && connection && connection.uid) {
157
+ video.uid = connection.uid;
192
158
  }
193
- if (feature.properties.takv) {
194
- cot.event.detail.takv = { _attributes: { ...feature.properties.takv } };
195
- }
196
- if (feature.properties.geofence) {
197
- cot.event.detail.__geofence = { _attributes: { ...feature.properties.geofence } };
198
- }
199
- if (feature.properties.sensor) {
200
- cot.event.detail.sensor = { _attributes: { ...feature.properties.sensor } };
201
- }
202
- if (feature.properties.ackrequest) {
203
- cot.event.detail.ackrequest = { _attributes: { ...feature.properties.ackrequest } };
204
- }
205
- if (feature.properties.video) {
206
- cot.event.detail.__video = { _attributes: { ...feature.properties.video } };
207
- }
208
- if (feature.properties.attachments) {
209
- cot.event.detail.attachment_list = { _attributes: { hashes: JSON.stringify(feature.properties.attachments) } };
159
+ else if (video.uid && connection && !connection.uid) {
160
+ connection.uid = video.uid;
210
161
  }
211
- if (feature.properties.contact) {
212
- cot.event.detail.contact = {
213
- _attributes: {
214
- callsign: feature.properties.callsign || 'UNKNOWN',
215
- ...feature.properties.contact
216
- }
217
- };
218
- }
219
- if (feature.properties.fileshare) {
220
- cot.event.detail.fileshare = { _attributes: { ...feature.properties.fileshare } };
162
+ else if (!video.uid) {
163
+ video.uid = crypto.randomUUID();
221
164
  }
222
- if (feature.properties.course !== undefined || feature.properties.speed !== undefined || feature.properties.slope !== undefined) {
223
- cot.event.detail.track = {
224
- _attributes: Util.cot_track_attr(feature.properties.course, feature.properties.speed, feature.properties.slope)
165
+ this.raw.event.detail.__video = {
166
+ _attributes: video
167
+ };
168
+ if (connection) {
169
+ this.raw.event.detail.__video.ConnectionEntry = {
170
+ _attributes: connection
225
171
  };
226
172
  }
227
- if (feature.properties.group) {
228
- cot.event.detail.__group = { _attributes: { ...feature.properties.group } };
229
- }
230
- if (feature.properties.flow) {
231
- cot.event.detail['_flow-tags_'] = { _attributes: { ...feature.properties.flow } };
232
- }
233
- if (feature.properties.status) {
234
- cot.event.detail.status = { _attributes: { ...feature.properties.status } };
235
- }
236
- if (feature.properties.precisionlocation) {
237
- cot.event.detail.precisionlocation = { _attributes: { ...feature.properties.precisionlocation } };
238
- }
239
- if (feature.properties.icon) {
240
- cot.event.detail.usericon = { _attributes: { iconsetpath: feature.properties.icon } };
241
- }
242
- if (feature.properties.mission) {
243
- cot.event.detail.mission = {
173
+ else {
174
+ this.raw.event.detail.__video.ConnectionEntry = {
244
175
  _attributes: {
245
- type: feature.properties.mission.type,
246
- guid: feature.properties.mission.guid,
247
- tool: feature.properties.mission.tool,
248
- name: feature.properties.mission.name,
249
- authorUid: feature.properties.mission.authorUid,
176
+ uid: video.uid,
177
+ networkTimeout: 12000,
178
+ path: '',
179
+ protocol: 'raw',
180
+ bufferTime: -1,
181
+ address: video.url,
182
+ port: -1,
183
+ roverPort: -1,
184
+ rtspReliable: 0,
185
+ ignoreEmbeddedKLV: false,
186
+ alias: this.callsign()
250
187
  }
251
188
  };
252
- if (feature.properties.mission.missionLayer) {
253
- cot.event.detail.mission.missionLayer = {};
254
- if (feature.properties.mission.missionLayer.name) {
255
- cot.event.detail.mission.missionLayer.name = { _text: feature.properties.mission.missionLayer.name };
256
- }
257
- if (feature.properties.mission.missionLayer.parentUid) {
258
- cot.event.detail.mission.missionLayer.parentUid = { _text: feature.properties.mission.missionLayer.parentUid };
259
- }
260
- if (feature.properties.mission.missionLayer.type) {
261
- cot.event.detail.mission.missionLayer.type = { _text: feature.properties.mission.missionLayer.type };
262
- }
263
- if (feature.properties.mission.missionLayer.uid) {
264
- cot.event.detail.mission.missionLayer.uid = { _text: feature.properties.mission.missionLayer.uid };
265
- }
266
- }
267
- }
268
- cot.event.detail.remarks = { _attributes: {}, _text: feature.properties.remarks || '' };
269
- if (!feature.geometry) {
270
- throw new Err(400, null, 'Must have Geometry');
271
- }
272
- else if (!['Point', 'Polygon', 'LineString'].includes(feature.geometry.type)) {
273
- throw new Err(400, null, 'Unsupported Geometry Type');
274
- }
275
- if (feature.geometry.type === 'Point') {
276
- cot.event.point._attributes.lon = String(feature.geometry.coordinates[0]);
277
- cot.event.point._attributes.lat = String(feature.geometry.coordinates[1]);
278
- cot.event.point._attributes.hae = String(feature.geometry.coordinates[2] || '0.0');
279
- if (feature.properties['marker-color']) {
280
- const color = new Color(feature.properties['marker-color'] || -1761607936);
281
- color.a = feature.properties['marker-opacity'] !== undefined ? feature.properties['marker-opacity'] * 255 : 128;
282
- cot.event.detail.color = { _attributes: { argb: String(color.as_32bit()) } };
283
- }
284
- }
285
- else if (feature.geometry.type === 'Polygon' && feature.properties.type === 'u-d-c-c') {
286
- if (!feature.properties.shape || !feature.properties.shape.ellipse) {
287
- throw new Err(400, null, 'u-d-c-c (Circle) must define a feature.properties.shape.ellipse property');
288
- }
289
- cot.event.detail.shape = { ellipse: { _attributes: feature.properties.shape.ellipse } };
290
- if (feature.properties.center) {
291
- cot.event.point._attributes.lon = String(feature.properties.center[0]);
292
- cot.event.point._attributes.lat = String(feature.properties.center[1]);
293
- }
294
- else {
295
- const centre = PointOnFeature(feature);
296
- cot.event.point._attributes.lon = String(centre.geometry.coordinates[0]);
297
- cot.event.point._attributes.lat = String(centre.geometry.coordinates[1]);
298
- cot.event.point._attributes.hae = '0.0';
299
- }
300
189
  }
301
- else if (['Polygon', 'LineString'].includes(feature.geometry.type)) {
302
- const stroke = new Color(feature.properties.stroke || -1761607936);
303
- stroke.a = feature.properties['stroke-opacity'] !== undefined ? feature.properties['stroke-opacity'] * 255 : 128;
304
- cot.event.detail.strokeColor = { _attributes: { value: String(stroke.as_32bit()) } };
305
- if (!feature.properties['stroke-width'])
306
- feature.properties['stroke-width'] = 3;
307
- cot.event.detail.strokeWeight = { _attributes: {
308
- value: String(feature.properties['stroke-width'])
309
- } };
310
- if (!feature.properties['stroke-style'])
311
- feature.properties['stroke-style'] = 'solid';
312
- cot.event.detail.strokeStyle = { _attributes: {
313
- value: feature.properties['stroke-style']
314
- } };
315
- if (feature.geometry.type === 'LineString') {
316
- cot.event._attributes.type = 'u-d-f';
317
- if (!cot.event.detail.link)
318
- cot.event.detail.link = [];
319
- else if (!Array.isArray(cot.event.detail.link))
320
- cot.event.detail.link = [cot.event.detail.link];
321
- for (const coord of feature.geometry.coordinates) {
322
- cot.event.detail.link.push({
323
- _attributes: { point: `${coord[1]},${coord[0]}` }
324
- });
325
- }
326
- }
327
- else if (feature.geometry.type === 'Polygon') {
328
- cot.event._attributes.type = 'u-d-f';
329
- if (!cot.event.detail.link)
330
- cot.event.detail.link = [];
331
- else if (!Array.isArray(cot.event.detail.link))
332
- cot.event.detail.link = [cot.event.detail.link];
333
- // Inner rings are not yet supported
334
- for (const coord of feature.geometry.coordinates[0]) {
335
- cot.event.detail.link.push({
336
- _attributes: { point: `${coord[1]},${coord[0]}` }
337
- });
338
- }
339
- const fill = new Color(feature.properties.fill || -1761607936);
340
- fill.a = feature.properties['fill-opacity'] !== undefined ? feature.properties['fill-opacity'] * 255 : 128;
341
- cot.event.detail.fillColor = { _attributes: { value: String(fill.as_32bit()) } };
342
- }
343
- cot.event.detail.labels_on = { _attributes: { value: 'false' } };
344
- cot.event.detail.tog = { _attributes: { enabled: '0' } };
345
- if (feature.properties.center && Array.isArray(feature.properties.center) && feature.properties.center.length >= 2) {
346
- cot.event.point._attributes.lon = String(feature.properties.center[0]);
347
- cot.event.point._attributes.lat = String(feature.properties.center[1]);
348
- if (feature.properties.center.length >= 3) {
349
- cot.event.point._attributes.hae = String(feature.properties.center[2] || '0.0');
350
- }
351
- else {
352
- cot.event.point._attributes.hae = '0.0';
353
- }
354
- }
355
- else {
356
- const centre = PointOnFeature(feature);
357
- cot.event.point._attributes.lon = String(centre.geometry.coordinates[0]);
358
- cot.event.point._attributes.lat = String(centre.geometry.coordinates[1]);
359
- cot.event.point._attributes.hae = '0.0';
360
- }
190
+ return this;
191
+ }
192
+ addLink(link) {
193
+ if (!this.raw.event.detail)
194
+ this.raw.event.detail = {};
195
+ let linkArr = [];
196
+ if (this.raw.event.detail.link && !Array.isArray(this.raw.event.detail.link)) {
197
+ linkArr = [this.raw.event.detail.link];
361
198
  }
362
- const newcot = new CoT(cot);
363
- if (feature.properties.metadata) {
364
- newcot.metadata = feature.properties.metadata;
199
+ else if (this.raw.event.detail.link && Array.isArray(this.raw.event.detail.link)) {
200
+ linkArr = this.raw.event.detail.link;
365
201
  }
366
- return newcot;
202
+ linkArr.push({ _attributes: link });
203
+ this.raw.event.detail.link = linkArr;
204
+ return this;
367
205
  }
368
206
  /**
369
207
  * Return an ATAK Compliant Protobuf
@@ -399,65 +237,7 @@ export default class CoT {
399
237
  return ProtoMessage.encode(msg).finish();
400
238
  }
401
239
  /**
402
- * Parse an ATAK compliant Protobuf to a JS Object
403
- */
404
- static from_proto(raw, version = 1) {
405
- const ProtoMessage = RootMessage.lookupType(`atakmap.commoncommo.protobuf.v${version}.TakMessage`);
406
- // TODO Type this
407
- const msg = ProtoMessage.decode(raw);
408
- if (!msg.cotEvent)
409
- throw new Err(400, null, 'No cotEvent Data');
410
- const detail = {};
411
- const metadata = {};
412
- for (const key in msg.cotEvent.detail) {
413
- if (key === 'xmlDetail') {
414
- const parsed = xmljs.xml2js(`<detail>${msg.cotEvent.detail.xmlDetail}</detail>`, { compact: true });
415
- Object.assign(detail, parsed.detail);
416
- if (detail.metadata) {
417
- for (const key in detail.metadata) {
418
- metadata[key] = detail.metadata[key]._text;
419
- }
420
- delete detail.metadata;
421
- }
422
- }
423
- else if (key === 'group') {
424
- if (msg.cotEvent.detail[key]) {
425
- detail.__group = { _attributes: msg.cotEvent.detail[key] };
426
- }
427
- }
428
- else if (['contact', 'precisionlocation', 'status', 'takv', 'track'].includes(key)) {
429
- if (msg.cotEvent.detail[key]) {
430
- detail[key] = { _attributes: msg.cotEvent.detail[key] };
431
- }
432
- }
433
- }
434
- const cot = new CoT({
435
- event: {
436
- _attributes: {
437
- version: '2.0',
438
- uid: msg.cotEvent.uid, type: msg.cotEvent.type, how: msg.cotEvent.how,
439
- qos: msg.cotEvent.qos, opex: msg.cotEvent.opex, access: msg.cotEvent.access,
440
- time: new Date(msg.cotEvent.sendTime.toNumber()).toISOString(),
441
- start: new Date(msg.cotEvent.startTime.toNumber()).toISOString(),
442
- stale: new Date(msg.cotEvent.staleTime.toNumber()).toISOString(),
443
- },
444
- detail,
445
- point: {
446
- _attributes: {
447
- lat: msg.cotEvent.lat,
448
- lon: msg.cotEvent.lon,
449
- hae: msg.cotEvent.hae,
450
- le: msg.cotEvent.le,
451
- ce: msg.cotEvent.ce,
452
- },
453
- }
454
- }
455
- });
456
- cot.metadata = metadata;
457
- return cot;
458
- }
459
- /**
460
- * Return a GeoJSON Feature from an XML CoT message
240
+ * Return a GeoJSON Feature from an XML CoT message
461
241
  */
462
242
  to_geojson() {
463
243
  const raw = JSON.parse(JSON.stringify(this.raw));
@@ -503,6 +283,9 @@ export default class CoT {
503
283
  }
504
284
  if (raw.event.detail.__video && raw.event.detail.__video._attributes) {
505
285
  feat.properties.video = raw.event.detail.__video._attributes;
286
+ if (raw.event.detail.__video.ConnectionEntry) {
287
+ feat.properties.video.connection = raw.event.detail.__video.ConnectionEntry._attributes;
288
+ }
506
289
  }
507
290
  if (raw.event.detail.__geofence) {
508
291
  feat.properties.geofence = raw.event.detail.__geofence._attributes;
@@ -720,18 +503,6 @@ export default class CoT {
720
503
  to_xml() {
721
504
  return xmljs.js2xml(this.raw, { compact: true });
722
505
  }
723
- /**
724
- * Return a CoT Message
725
- */
726
- static ping() {
727
- return new CoT({
728
- event: {
729
- _attributes: Util.cot_event_attr('t-x-c-t', 'h-g-i-g-o'),
730
- detail: {},
731
- point: Util.cot_point()
732
- }
733
- });
734
- }
735
506
  /**
736
507
  * Determines if the CoT message represents a Tasking Message
737
508
  *
@@ -892,5 +663,310 @@ export default class CoT {
892
663
  is_uav() {
893
664
  return !!this.raw.event._attributes.type.match(/^a-f-A-M-F-Q-r/);
894
665
  }
666
+ /**
667
+ * Parse an ATAK compliant Protobuf to a JS Object
668
+ */
669
+ static from_proto(raw, version = 1) {
670
+ const ProtoMessage = RootMessage.lookupType(`atakmap.commoncommo.protobuf.v${version}.TakMessage`);
671
+ // TODO Type this
672
+ const msg = ProtoMessage.decode(raw);
673
+ if (!msg.cotEvent)
674
+ throw new Err(400, null, 'No cotEvent Data');
675
+ const detail = {};
676
+ const metadata = {};
677
+ for (const key in msg.cotEvent.detail) {
678
+ if (key === 'xmlDetail') {
679
+ const parsed = xmljs.xml2js(`<detail>${msg.cotEvent.detail.xmlDetail}</detail>`, { compact: true });
680
+ Object.assign(detail, parsed.detail);
681
+ if (detail.metadata) {
682
+ for (const key in detail.metadata) {
683
+ metadata[key] = detail.metadata[key]._text;
684
+ }
685
+ delete detail.metadata;
686
+ }
687
+ }
688
+ else if (key === 'group') {
689
+ if (msg.cotEvent.detail[key]) {
690
+ detail.__group = { _attributes: msg.cotEvent.detail[key] };
691
+ }
692
+ }
693
+ else if (['contact', 'precisionlocation', 'status', 'takv', 'track'].includes(key)) {
694
+ if (msg.cotEvent.detail[key]) {
695
+ detail[key] = { _attributes: msg.cotEvent.detail[key] };
696
+ }
697
+ }
698
+ }
699
+ const cot = new CoT({
700
+ event: {
701
+ _attributes: {
702
+ version: '2.0',
703
+ uid: msg.cotEvent.uid, type: msg.cotEvent.type, how: msg.cotEvent.how,
704
+ qos: msg.cotEvent.qos, opex: msg.cotEvent.opex, access: msg.cotEvent.access,
705
+ time: new Date(msg.cotEvent.sendTime.toNumber()).toISOString(),
706
+ start: new Date(msg.cotEvent.startTime.toNumber()).toISOString(),
707
+ stale: new Date(msg.cotEvent.staleTime.toNumber()).toISOString(),
708
+ },
709
+ detail,
710
+ point: {
711
+ _attributes: {
712
+ lat: msg.cotEvent.lat,
713
+ lon: msg.cotEvent.lon,
714
+ hae: msg.cotEvent.hae,
715
+ le: msg.cotEvent.le,
716
+ ce: msg.cotEvent.ce,
717
+ },
718
+ }
719
+ }
720
+ });
721
+ cot.metadata = metadata;
722
+ return cot;
723
+ }
724
+ /**
725
+ * Return a CoT Message
726
+ */
727
+ static ping() {
728
+ return new CoT({
729
+ event: {
730
+ _attributes: Util.cot_event_attr('t-x-c-t', 'h-g-i-g-o'),
731
+ detail: {},
732
+ point: Util.cot_point()
733
+ }
734
+ });
735
+ }
736
+ /**
737
+ * Return an CoT Message given a GeoJSON Feature
738
+ *
739
+ * @param {Object} feature GeoJSON Point Feature
740
+ *
741
+ * @return {CoT}
742
+ */
743
+ static from_geojson(feature) {
744
+ checkFeat(feature);
745
+ if (checkFeat.errors)
746
+ throw new Err(400, null, `${checkFeat.errors[0].message} (${checkFeat.errors[0].instancePath})`);
747
+ const cot = {
748
+ event: {
749
+ _attributes: Util.cot_event_attr(feature.properties.type || 'a-f-G', feature.properties.how || 'm-g', feature.properties.time, feature.properties.start, feature.properties.stale),
750
+ point: Util.cot_point(),
751
+ detail: Util.cot_event_detail(feature.properties.callsign)
752
+ }
753
+ };
754
+ if (feature.id)
755
+ cot.event._attributes.uid = String(feature.id);
756
+ if (feature.properties.callsign && !feature.id)
757
+ cot.event._attributes.uid = feature.properties.callsign;
758
+ if (!cot.event.detail)
759
+ cot.event.detail = {};
760
+ if (feature.properties.droid) {
761
+ cot.event.detail.uid = { _attributes: { Droid: feature.properties.droid } };
762
+ }
763
+ if (feature.properties.archived) {
764
+ cot.event.detail.archive = { _attributes: {} };
765
+ }
766
+ if (feature.properties.links) {
767
+ if (!cot.event.detail.link)
768
+ cot.event.detail.link = [];
769
+ else if (!Array.isArray(cot.event.detail.link))
770
+ cot.event.detail.link = [cot.event.detail.link];
771
+ cot.event.detail.link.push(...feature.properties.links.map((link) => {
772
+ return { _attributes: link };
773
+ }));
774
+ }
775
+ if (feature.properties.dest) {
776
+ const dest = !Array.isArray(feature.properties.dest) ? [feature.properties.dest] : feature.properties.dest;
777
+ cot.event.detail.marti = {
778
+ dest: dest.map((dest) => {
779
+ return { _attributes: { ...dest } };
780
+ })
781
+ };
782
+ }
783
+ if (feature.properties.takv) {
784
+ cot.event.detail.takv = { _attributes: { ...feature.properties.takv } };
785
+ }
786
+ if (feature.properties.geofence) {
787
+ cot.event.detail.__geofence = { _attributes: { ...feature.properties.geofence } };
788
+ }
789
+ if (feature.properties.sensor) {
790
+ cot.event.detail.sensor = { _attributes: { ...feature.properties.sensor } };
791
+ }
792
+ if (feature.properties.ackrequest) {
793
+ cot.event.detail.ackrequest = { _attributes: { ...feature.properties.ackrequest } };
794
+ }
795
+ if (feature.properties.video) {
796
+ if (feature.properties.video.connection) {
797
+ const video = JSON.parse(JSON.stringify(feature.properties.video));
798
+ const connection = video.connection;
799
+ delete video.connection;
800
+ cot.event.detail.__video = {
801
+ _attributes: { ...video },
802
+ ConnectionEntry: {
803
+ _attributes: connection
804
+ }
805
+ };
806
+ }
807
+ else {
808
+ cot.event.detail.__video = { _attributes: { ...feature.properties.video } };
809
+ }
810
+ }
811
+ if (feature.properties.attachments) {
812
+ cot.event.detail.attachment_list = { _attributes: { hashes: JSON.stringify(feature.properties.attachments) } };
813
+ }
814
+ if (feature.properties.contact) {
815
+ cot.event.detail.contact = {
816
+ _attributes: {
817
+ callsign: feature.properties.callsign || 'UNKNOWN',
818
+ ...feature.properties.contact
819
+ }
820
+ };
821
+ }
822
+ if (feature.properties.fileshare) {
823
+ cot.event.detail.fileshare = { _attributes: { ...feature.properties.fileshare } };
824
+ }
825
+ if (feature.properties.course !== undefined || feature.properties.speed !== undefined || feature.properties.slope !== undefined) {
826
+ cot.event.detail.track = {
827
+ _attributes: Util.cot_track_attr(feature.properties.course, feature.properties.speed, feature.properties.slope)
828
+ };
829
+ }
830
+ if (feature.properties.group) {
831
+ cot.event.detail.__group = { _attributes: { ...feature.properties.group } };
832
+ }
833
+ if (feature.properties.flow) {
834
+ cot.event.detail['_flow-tags_'] = { _attributes: { ...feature.properties.flow } };
835
+ }
836
+ if (feature.properties.status) {
837
+ cot.event.detail.status = { _attributes: { ...feature.properties.status } };
838
+ }
839
+ if (feature.properties.precisionlocation) {
840
+ cot.event.detail.precisionlocation = { _attributes: { ...feature.properties.precisionlocation } };
841
+ }
842
+ if (feature.properties.icon) {
843
+ cot.event.detail.usericon = { _attributes: { iconsetpath: feature.properties.icon } };
844
+ }
845
+ if (feature.properties.mission) {
846
+ cot.event.detail.mission = {
847
+ _attributes: {
848
+ type: feature.properties.mission.type,
849
+ guid: feature.properties.mission.guid,
850
+ tool: feature.properties.mission.tool,
851
+ name: feature.properties.mission.name,
852
+ authorUid: feature.properties.mission.authorUid,
853
+ }
854
+ };
855
+ if (feature.properties.mission.missionLayer) {
856
+ cot.event.detail.mission.missionLayer = {};
857
+ if (feature.properties.mission.missionLayer.name) {
858
+ cot.event.detail.mission.missionLayer.name = { _text: feature.properties.mission.missionLayer.name };
859
+ }
860
+ if (feature.properties.mission.missionLayer.parentUid) {
861
+ cot.event.detail.mission.missionLayer.parentUid = { _text: feature.properties.mission.missionLayer.parentUid };
862
+ }
863
+ if (feature.properties.mission.missionLayer.type) {
864
+ cot.event.detail.mission.missionLayer.type = { _text: feature.properties.mission.missionLayer.type };
865
+ }
866
+ if (feature.properties.mission.missionLayer.uid) {
867
+ cot.event.detail.mission.missionLayer.uid = { _text: feature.properties.mission.missionLayer.uid };
868
+ }
869
+ }
870
+ }
871
+ cot.event.detail.remarks = { _attributes: {}, _text: feature.properties.remarks || '' };
872
+ if (!feature.geometry) {
873
+ throw new Err(400, null, 'Must have Geometry');
874
+ }
875
+ else if (!['Point', 'Polygon', 'LineString'].includes(feature.geometry.type)) {
876
+ throw new Err(400, null, 'Unsupported Geometry Type');
877
+ }
878
+ if (feature.geometry.type === 'Point') {
879
+ cot.event.point._attributes.lon = String(feature.geometry.coordinates[0]);
880
+ cot.event.point._attributes.lat = String(feature.geometry.coordinates[1]);
881
+ cot.event.point._attributes.hae = String(feature.geometry.coordinates[2] || '0.0');
882
+ if (feature.properties['marker-color']) {
883
+ const color = new Color(feature.properties['marker-color'] || -1761607936);
884
+ color.a = feature.properties['marker-opacity'] !== undefined ? feature.properties['marker-opacity'] * 255 : 128;
885
+ cot.event.detail.color = { _attributes: { argb: String(color.as_32bit()) } };
886
+ }
887
+ }
888
+ else if (feature.geometry.type === 'Polygon' && feature.properties.type === 'u-d-c-c') {
889
+ if (!feature.properties.shape || !feature.properties.shape.ellipse) {
890
+ throw new Err(400, null, 'u-d-c-c (Circle) must define a feature.properties.shape.ellipse property');
891
+ }
892
+ cot.event.detail.shape = { ellipse: { _attributes: feature.properties.shape.ellipse } };
893
+ if (feature.properties.center) {
894
+ cot.event.point._attributes.lon = String(feature.properties.center[0]);
895
+ cot.event.point._attributes.lat = String(feature.properties.center[1]);
896
+ }
897
+ else {
898
+ const centre = PointOnFeature(feature);
899
+ cot.event.point._attributes.lon = String(centre.geometry.coordinates[0]);
900
+ cot.event.point._attributes.lat = String(centre.geometry.coordinates[1]);
901
+ cot.event.point._attributes.hae = '0.0';
902
+ }
903
+ }
904
+ else if (['Polygon', 'LineString'].includes(feature.geometry.type)) {
905
+ const stroke = new Color(feature.properties.stroke || -1761607936);
906
+ stroke.a = feature.properties['stroke-opacity'] !== undefined ? feature.properties['stroke-opacity'] * 255 : 128;
907
+ cot.event.detail.strokeColor = { _attributes: { value: String(stroke.as_32bit()) } };
908
+ if (!feature.properties['stroke-width'])
909
+ feature.properties['stroke-width'] = 3;
910
+ cot.event.detail.strokeWeight = { _attributes: {
911
+ value: String(feature.properties['stroke-width'])
912
+ } };
913
+ if (!feature.properties['stroke-style'])
914
+ feature.properties['stroke-style'] = 'solid';
915
+ cot.event.detail.strokeStyle = { _attributes: {
916
+ value: feature.properties['stroke-style']
917
+ } };
918
+ if (feature.geometry.type === 'LineString') {
919
+ cot.event._attributes.type = 'u-d-f';
920
+ if (!cot.event.detail.link)
921
+ cot.event.detail.link = [];
922
+ else if (!Array.isArray(cot.event.detail.link))
923
+ cot.event.detail.link = [cot.event.detail.link];
924
+ for (const coord of feature.geometry.coordinates) {
925
+ cot.event.detail.link.push({
926
+ _attributes: { point: `${coord[1]},${coord[0]}` }
927
+ });
928
+ }
929
+ }
930
+ else if (feature.geometry.type === 'Polygon') {
931
+ cot.event._attributes.type = 'u-d-f';
932
+ if (!cot.event.detail.link)
933
+ cot.event.detail.link = [];
934
+ else if (!Array.isArray(cot.event.detail.link))
935
+ cot.event.detail.link = [cot.event.detail.link];
936
+ // Inner rings are not yet supported
937
+ for (const coord of feature.geometry.coordinates[0]) {
938
+ cot.event.detail.link.push({
939
+ _attributes: { point: `${coord[1]},${coord[0]}` }
940
+ });
941
+ }
942
+ const fill = new Color(feature.properties.fill || -1761607936);
943
+ fill.a = feature.properties['fill-opacity'] !== undefined ? feature.properties['fill-opacity'] * 255 : 128;
944
+ cot.event.detail.fillColor = { _attributes: { value: String(fill.as_32bit()) } };
945
+ }
946
+ cot.event.detail.labels_on = { _attributes: { value: 'false' } };
947
+ cot.event.detail.tog = { _attributes: { enabled: '0' } };
948
+ if (feature.properties.center && Array.isArray(feature.properties.center) && feature.properties.center.length >= 2) {
949
+ cot.event.point._attributes.lon = String(feature.properties.center[0]);
950
+ cot.event.point._attributes.lat = String(feature.properties.center[1]);
951
+ if (feature.properties.center.length >= 3) {
952
+ cot.event.point._attributes.hae = String(feature.properties.center[2] || '0.0');
953
+ }
954
+ else {
955
+ cot.event.point._attributes.hae = '0.0';
956
+ }
957
+ }
958
+ else {
959
+ const centre = PointOnFeature(feature);
960
+ cot.event.point._attributes.lon = String(centre.geometry.coordinates[0]);
961
+ cot.event.point._attributes.lat = String(centre.geometry.coordinates[1]);
962
+ cot.event.point._attributes.hae = '0.0';
963
+ }
964
+ }
965
+ const newcot = new CoT(cot);
966
+ if (feature.properties.metadata) {
967
+ newcot.metadata = feature.properties.metadata;
968
+ }
969
+ return newcot;
970
+ }
895
971
  }
896
972
  //# sourceMappingURL=cot.js.map