@tak-ps/node-cot 12.37.0 → 13.0.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,38 +1,8 @@
1
1
  import crypto from 'node:crypto';
2
- import protobuf from 'protobufjs';
3
2
  import Err from '@openaddresses/batch-error';
4
- import { diff } from 'json-diff-ts';
5
- import xmljs from 'xml-js';
6
- import { InputFeature, } from './types/feature.js';
7
3
  import Sensor from './sensor.js';
8
- import PointOnFeature from '@turf/point-on-feature';
9
- import Truncate from '@turf/truncate';
10
- import { destination } from '@turf/destination';
11
- import Ellipse from '@turf/ellipse';
12
4
  import Util from './utils/util.js';
13
- import Color from './utils/color.js';
14
5
  import JSONCoT, { Detail } from './types/types.js';
15
- import AJV from 'ajv';
16
- import fs from 'fs';
17
- import path from 'node:path';
18
- import { fileURLToPath } from 'node:url';
19
- // GeoJSON Geospatial ops will truncate to the below
20
- const COORDINATE_PRECISION = 6;
21
- const protoPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'proto', 'takmessage.proto');
22
- const RootMessage = await protobuf.load(protoPath);
23
- const pkg = JSON.parse(String(fs.readFileSync(new URL('../package.json', import.meta.url))));
24
- const checkXML = (new AJV({
25
- allErrors: true,
26
- coerceTypes: true,
27
- allowUnionTypes: true
28
- }))
29
- .compile(JSONCoT);
30
- const checkFeat = (new AJV({
31
- allErrors: true,
32
- coerceTypes: true,
33
- allowUnionTypes: true
34
- }))
35
- .compile(InputFeature);
36
6
  /**
37
7
  * Convert to and from an XML CoT message
38
8
  * @class
@@ -50,27 +20,14 @@ export default class CoT {
50
20
  // Does the CoT belong to a folder - defaults to "/"
51
21
  path;
52
22
  constructor(cot, opts = {}) {
53
- if (typeof cot === 'string' || cot instanceof Buffer) {
54
- const raw = xmljs.xml2js(String(cot), { compact: true });
55
- this.raw = raw;
56
- }
57
- else {
58
- this.raw = cot;
59
- }
23
+ this.raw = cot;
60
24
  this.metadata = {};
61
25
  this.path = '/';
62
- if (!this.raw.event._attributes.uid)
26
+ if (!this.raw.event._attributes.uid) {
63
27
  this.raw.event._attributes.uid = Util.cot_uuid();
64
- if (process.env.DEBUG_COTS)
65
- console.log(JSON.stringify(this.raw));
66
- checkXML(this.raw);
67
- if (checkXML.errors)
68
- throw new Err(400, null, `${checkXML.errors[0].message} (${checkXML.errors[0].instancePath})`);
28
+ }
69
29
  if (!this.raw.event.detail)
70
30
  this.raw.event.detail = {};
71
- if (!this.raw.event.detail['_flow-tags_'])
72
- this.raw.event.detail['_flow-tags_'] = {};
73
- this.raw.event.detail['_flow-tags_'][`NodeCoT-${pkg.version}`] = new Date().toISOString();
74
31
  if (this.raw.event.detail.archive && Object.keys(this.raw.event.detail.archive).length === 0) {
75
32
  this.raw.event.detail.archive = { _attributes: {} };
76
33
  }
@@ -82,42 +39,8 @@ export default class CoT {
82
39
  time: opts.creator instanceof CoT ? new Date() : opts.creator.time
83
40
  });
84
41
  }
85
- }
86
- /**
87
- * Detect difference between CoT messages
88
- * Note: This diffs based on GeoJSON Representation of message
89
- * So if unknown properties are present they will be excluded from the diff
90
- */
91
- isDiff(cot, opts = {
92
- diffMetadata: false,
93
- diffStaleStartTime: false,
94
- diffDest: false,
95
- diffFlow: false
96
- }) {
97
- const a = this.to_geojson();
98
- const b = cot.to_geojson();
99
- if (!opts.diffDest) {
100
- delete a.properties.dest;
101
- delete b.properties.dest;
102
- }
103
- if (!opts.diffMetadata) {
104
- delete a.properties.metadata;
105
- delete b.properties.metadata;
106
- }
107
- if (!opts.diffFlow) {
108
- delete a.properties.flow;
109
- delete b.properties.flow;
110
- }
111
- if (!opts.diffStaleStartTime) {
112
- delete a.properties.time;
113
- delete a.properties.stale;
114
- delete a.properties.start;
115
- delete b.properties.time;
116
- delete b.properties.stale;
117
- delete b.properties.start;
118
- }
119
- const diffs = diff(a, b);
120
- return diffs.length > 0;
42
+ if (process.env.DEBUG_COTS)
43
+ console.log(JSON.stringify(this.raw));
121
44
  }
122
45
  /**
123
46
  * Returns or sets the UID of the CoT
@@ -241,8 +164,8 @@ export default class CoT {
241
164
  }
242
165
  position(position) {
243
166
  if (position) {
244
- this.raw.event.point._attributes.lon = String(position[0]);
245
- this.raw.event.point._attributes.lat = String(position[1]);
167
+ this.raw.event.point._attributes.lon = position[0];
168
+ this.raw.event.point._attributes.lat = position[1];
246
169
  }
247
170
  return [
248
171
  Number(this.raw.event.point._attributes.lon),
@@ -299,337 +222,6 @@ export default class CoT {
299
222
  detail.link = linkArr;
300
223
  return this;
301
224
  }
302
- /**
303
- * Return an ATAK Compliant Protobuf
304
- */
305
- to_proto(version = 1) {
306
- if (version < 1 || version > 1)
307
- throw new Err(400, null, `Unsupported Proto Version: ${version}`);
308
- const ProtoMessage = RootMessage.lookupType(`atakmap.commoncommo.protobuf.v${version}.TakMessage`);
309
- // The spread operator is important to make sure the delete doesn't modify the underlying detail object
310
- const detail = { ...this.raw.event.detail };
311
- const msg = {
312
- cotEvent: {
313
- ...this.raw.event._attributes,
314
- sendTime: new Date(this.raw.event._attributes.time).getTime(),
315
- startTime: new Date(this.raw.event._attributes.start).getTime(),
316
- staleTime: new Date(this.raw.event._attributes.stale).getTime(),
317
- ...this.raw.event.point._attributes,
318
- detail: {
319
- xmlDetail: ''
320
- }
321
- }
322
- };
323
- let key;
324
- for (key in detail) {
325
- if (['contact', 'group', 'precisionlocation', 'status', 'takv', 'track'].includes(key)) {
326
- msg.cotEvent.detail[key] = detail[key]._attributes;
327
- delete detail[key];
328
- }
329
- }
330
- msg.cotEvent.detail.xmlDetail = xmljs.js2xml({
331
- ...detail,
332
- metadata: this.metadata
333
- }, { compact: true });
334
- return ProtoMessage.encode(msg).finish();
335
- }
336
- /**
337
- * Return a GeoJSON Feature from an XML CoT message
338
- */
339
- to_geojson() {
340
- const raw = JSON.parse(JSON.stringify(this.raw));
341
- if (!raw.event.detail)
342
- raw.event.detail = {};
343
- if (!raw.event.detail.contact)
344
- raw.event.detail.contact = { _attributes: { callsign: 'UNKNOWN' } };
345
- if (!raw.event.detail.contact._attributes)
346
- raw.event.detail.contact._attributes = { callsign: 'UNKNOWN' };
347
- const feat = {
348
- id: raw.event._attributes.uid,
349
- type: 'Feature',
350
- properties: {
351
- callsign: raw.event.detail.contact._attributes.callsign || 'UNKNOWN',
352
- center: [Number(raw.event.point._attributes.lon), Number(raw.event.point._attributes.lat), Number(raw.event.point._attributes.hae)],
353
- type: raw.event._attributes.type,
354
- how: raw.event._attributes.how || '',
355
- time: raw.event._attributes.time,
356
- start: raw.event._attributes.start,
357
- stale: raw.event._attributes.stale,
358
- },
359
- geometry: {
360
- type: 'Point',
361
- coordinates: [Number(raw.event.point._attributes.lon), Number(raw.event.point._attributes.lat), Number(raw.event.point._attributes.hae)]
362
- }
363
- };
364
- const contact = JSON.parse(JSON.stringify(raw.event.detail.contact._attributes));
365
- delete contact.callsign;
366
- if (Object.keys(contact).length) {
367
- feat.properties.contact = contact;
368
- }
369
- if (this.creator()) {
370
- feat.properties.creator = this.creator();
371
- }
372
- if (raw.event.detail.remarks && raw.event.detail.remarks._text) {
373
- feat.properties.remarks = raw.event.detail.remarks._text;
374
- }
375
- if (raw.event.detail.fileshare) {
376
- feat.properties.fileshare = raw.event.detail.fileshare._attributes;
377
- if (feat.properties.fileshare && typeof feat.properties.fileshare.sizeInBytes === 'string') {
378
- feat.properties.fileshare.sizeInBytes = parseInt(feat.properties.fileshare.sizeInBytes);
379
- }
380
- }
381
- if (raw.event.detail.__milsym) {
382
- feat.properties.milsym = {
383
- id: raw.event.detail.__milsym._attributes.id
384
- };
385
- }
386
- if (raw.event.detail.sensor) {
387
- feat.properties.sensor = raw.event.detail.sensor._attributes;
388
- }
389
- if (raw.event.detail.range) {
390
- feat.properties.range = raw.event.detail.range._attributes.value;
391
- }
392
- if (raw.event.detail.bearing) {
393
- feat.properties.bearing = raw.event.detail.bearing._attributes.value;
394
- }
395
- if (raw.event.detail.__video && raw.event.detail.__video._attributes) {
396
- feat.properties.video = raw.event.detail.__video._attributes;
397
- if (raw.event.detail.__video.ConnectionEntry) {
398
- feat.properties.video.connection = raw.event.detail.__video.ConnectionEntry._attributes;
399
- }
400
- }
401
- if (raw.event.detail.__geofence) {
402
- feat.properties.geofence = raw.event.detail.__geofence._attributes;
403
- }
404
- if (raw.event.detail.ackrequest) {
405
- feat.properties.ackrequest = raw.event.detail.ackrequest._attributes;
406
- }
407
- if (raw.event.detail.attachment_list) {
408
- feat.properties.attachments = JSON.parse(raw.event.detail.attachment_list._attributes.hashes);
409
- }
410
- if (raw.event.detail.link) {
411
- if (!Array.isArray(raw.event.detail.link))
412
- raw.event.detail.link = [raw.event.detail.link];
413
- feat.properties.links = raw.event.detail.link.filter((link) => {
414
- return !!link._attributes.url;
415
- }).map((link) => {
416
- return link._attributes;
417
- });
418
- if (!feat.properties.links || !feat.properties.links.length)
419
- delete feat.properties.links;
420
- }
421
- if (raw.event.detail.archive) {
422
- feat.properties.archived = true;
423
- }
424
- if (raw.event.detail.__chat) {
425
- feat.properties.chat = {
426
- ...raw.event.detail.__chat._attributes,
427
- chatgrp: raw.event.detail.__chat.chatgrp
428
- };
429
- }
430
- if (raw.event.detail.track && raw.event.detail.track._attributes) {
431
- if (raw.event.detail.track._attributes.course)
432
- feat.properties.course = Number(raw.event.detail.track._attributes.course);
433
- if (raw.event.detail.track._attributes.slope)
434
- feat.properties.slope = Number(raw.event.detail.track._attributes.slope);
435
- if (raw.event.detail.track._attributes.course)
436
- feat.properties.speed = Number(raw.event.detail.track._attributes.speed);
437
- }
438
- if (raw.event.detail.marti && raw.event.detail.marti.dest) {
439
- if (!Array.isArray(raw.event.detail.marti.dest))
440
- raw.event.detail.marti.dest = [raw.event.detail.marti.dest];
441
- const dest = raw.event.detail.marti.dest.map((d) => {
442
- return { ...d._attributes };
443
- });
444
- feat.properties.dest = dest.length === 1 ? dest[0] : dest;
445
- }
446
- if (raw.event.detail.usericon && raw.event.detail.usericon._attributes && raw.event.detail.usericon._attributes.iconsetpath) {
447
- feat.properties.icon = raw.event.detail.usericon._attributes.iconsetpath;
448
- }
449
- if (raw.event.detail.uid && raw.event.detail.uid._attributes && raw.event.detail.uid._attributes.Droid) {
450
- feat.properties.droid = raw.event.detail.uid._attributes.Droid;
451
- }
452
- if (raw.event.detail.takv && raw.event.detail.takv._attributes) {
453
- feat.properties.takv = raw.event.detail.takv._attributes;
454
- }
455
- if (raw.event.detail.__group && raw.event.detail.__group._attributes) {
456
- feat.properties.group = raw.event.detail.__group._attributes;
457
- }
458
- if (raw.event.detail['_flow-tags_'] && raw.event.detail['_flow-tags_']._attributes) {
459
- feat.properties.flow = raw.event.detail['_flow-tags_']._attributes;
460
- }
461
- if (raw.event.detail.status && raw.event.detail.status._attributes) {
462
- feat.properties.status = raw.event.detail.status._attributes;
463
- }
464
- if (raw.event.detail.mission && raw.event.detail.mission._attributes) {
465
- const mission = {
466
- ...raw.event.detail.mission._attributes
467
- };
468
- if (raw.event.detail.mission && raw.event.detail.mission.MissionChanges) {
469
- const changes = Array.isArray(raw.event.detail.mission.MissionChanges)
470
- ? raw.event.detail.mission.MissionChanges
471
- : [raw.event.detail.mission.MissionChanges];
472
- mission.missionChanges = [];
473
- for (const change of changes) {
474
- mission.missionChanges.push({
475
- contentUid: change.MissionChange.contentUid._text,
476
- creatorUid: change.MissionChange.creatorUid._text,
477
- isFederatedChange: change.MissionChange.isFederatedChange._text,
478
- missionName: change.MissionChange.missionName._text,
479
- timestamp: change.MissionChange.timestamp._text,
480
- type: change.MissionChange.type._text,
481
- details: {
482
- ...change.MissionChange.details._attributes,
483
- ...change.MissionChange.details.location
484
- ? change.MissionChange.details.location._attributes
485
- : {}
486
- }
487
- });
488
- }
489
- }
490
- if (raw.event.detail.mission && raw.event.detail.mission.missionLayer) {
491
- const missionLayer = {};
492
- if (raw.event.detail.mission.missionLayer.name && raw.event.detail.mission.missionLayer.name._text) {
493
- missionLayer.name = raw.event.detail.mission.missionLayer.name._text;
494
- }
495
- if (raw.event.detail.mission.missionLayer.parentUid && raw.event.detail.mission.missionLayer.parentUid._text) {
496
- missionLayer.parentUid = raw.event.detail.mission.missionLayer.parentUid._text;
497
- }
498
- if (raw.event.detail.mission.missionLayer.type && raw.event.detail.mission.missionLayer.type._text) {
499
- missionLayer.type = raw.event.detail.mission.missionLayer.type._text;
500
- }
501
- if (raw.event.detail.mission.missionLayer.uid && raw.event.detail.mission.missionLayer.uid._text) {
502
- missionLayer.uid = raw.event.detail.mission.missionLayer.uid._text;
503
- }
504
- mission.missionLayer = missionLayer;
505
- }
506
- feat.properties.mission = mission;
507
- }
508
- if (raw.event.detail.precisionlocation && raw.event.detail.precisionlocation._attributes) {
509
- feat.properties.precisionlocation = raw.event.detail.precisionlocation._attributes;
510
- }
511
- // Line or Polygon style types
512
- if (['u-d-f', 'u-d-r', 'b-m-r', 'u-rb-a'].includes(raw.event._attributes.type) && Array.isArray(raw.event.detail.link)) {
513
- const coordinates = [];
514
- for (const l of raw.event.detail.link) {
515
- if (!l._attributes.point)
516
- continue;
517
- coordinates.push(l._attributes.point.split(',').map((p) => { return Number(p.trim()); }).splice(0, 2).reverse());
518
- }
519
- if (raw.event.detail.strokeColor && raw.event.detail.strokeColor._attributes && raw.event.detail.strokeColor._attributes.value) {
520
- const stroke = new Color(Number(raw.event.detail.strokeColor._attributes.value));
521
- feat.properties.stroke = stroke.as_hex();
522
- feat.properties['stroke-opacity'] = stroke.as_opacity() / 255;
523
- }
524
- if (raw.event.detail.strokeWeight && raw.event.detail.strokeWeight._attributes && raw.event.detail.strokeWeight._attributes.value) {
525
- feat.properties['stroke-width'] = Number(raw.event.detail.strokeWeight._attributes.value);
526
- }
527
- if (raw.event.detail.strokeStyle && raw.event.detail.strokeStyle._attributes && raw.event.detail.strokeStyle._attributes.value) {
528
- feat.properties['stroke-style'] = raw.event.detail.strokeStyle._attributes.value;
529
- }
530
- // Range & Bearing Line
531
- if (raw.event._attributes.type === 'u-rb-a') {
532
- const detail = this.detail();
533
- if (!detail.range)
534
- throw new Error('Range value not provided');
535
- if (!detail.bearing)
536
- throw new Error('Bearing value not provided');
537
- // TODO Support inclination
538
- const dest = destination(this.position(), detail.range._attributes.value / 1000, detail.bearing._attributes.value).geometry.coordinates;
539
- feat.geometry = {
540
- type: 'LineString',
541
- coordinates: [this.position(), dest]
542
- };
543
- }
544
- else if (raw.event._attributes.type === 'u-d-r' || (coordinates[0][0] === coordinates[coordinates.length - 1][0] && coordinates[0][1] === coordinates[coordinates.length - 1][1])) {
545
- if (raw.event._attributes.type === 'u-d-r') {
546
- // CoT rectangles are only 4 points - GeoJSON needs to be closed
547
- coordinates.push(coordinates[0]);
548
- }
549
- feat.geometry = {
550
- type: 'Polygon',
551
- coordinates: [coordinates]
552
- };
553
- if (raw.event.detail.fillColor && raw.event.detail.fillColor._attributes && raw.event.detail.fillColor._attributes.value) {
554
- const fill = new Color(Number(raw.event.detail.fillColor._attributes.value));
555
- feat.properties['fill-opacity'] = fill.as_opacity() / 255;
556
- feat.properties['fill'] = fill.as_hex();
557
- }
558
- }
559
- else {
560
- feat.geometry = {
561
- type: 'LineString',
562
- coordinates
563
- };
564
- }
565
- }
566
- else if (raw.event._attributes.type.startsWith('u-d-c-c')) {
567
- if (!raw.event.detail.shape)
568
- throw new Err(400, null, 'u-d-c-c (Circle) must define shape value');
569
- if (!raw.event.detail.shape.ellipse
570
- || !raw.event.detail.shape.ellipse._attributes)
571
- throw new Err(400, null, 'u-d-c-c (Circle) must define ellipse shape value');
572
- const ellipse = {
573
- major: Number(raw.event.detail.shape.ellipse._attributes.major),
574
- minor: Number(raw.event.detail.shape.ellipse._attributes.minor),
575
- angle: Number(raw.event.detail.shape.ellipse._attributes.angle)
576
- };
577
- feat.geometry = Truncate(Ellipse(feat.geometry.coordinates, Number(ellipse.major) / 1000, Number(ellipse.minor) / 1000, {
578
- angle: ellipse.angle
579
- }), {
580
- precision: COORDINATE_PRECISION,
581
- mutate: true
582
- }).geometry;
583
- feat.properties.shape = {};
584
- feat.properties.shape.ellipse = ellipse;
585
- }
586
- else if (raw.event._attributes.type.startsWith('b-m-p-s-p-i')) {
587
- // TODO: Currently the "shape" tag is only parsed here - asking ARA for clarification if it is a general use tag
588
- if (raw.event.detail.shape && raw.event.detail.shape.polyline && raw.event.detail.shape.polyline.vertex) {
589
- const coordinates = [];
590
- const vertices = Array.isArray(raw.event.detail.shape.polyline.vertex) ? raw.event.detail.shape.polyline.vertex : [raw.event.detail.shape.polyline.vertex];
591
- for (const v of vertices) {
592
- coordinates.push([Number(v._attributes.lon), Number(v._attributes.lat)]);
593
- }
594
- if (coordinates.length === 1) {
595
- feat.geometry = { type: 'Point', coordinates: coordinates[0] };
596
- }
597
- else if (raw.event.detail.shape.polyline._attributes && raw.event.detail.shape.polyline._attributes.closed === true) {
598
- coordinates.push(coordinates[0]);
599
- feat.geometry = { type: 'Polygon', coordinates: [coordinates] };
600
- }
601
- else {
602
- feat.geometry = { type: 'LineString', coordinates };
603
- }
604
- }
605
- if (raw.event.detail.shape
606
- && raw.event.detail.shape.polyline
607
- && raw.event.detail.shape.polyline._attributes
608
- && raw.event.detail.shape.polyline._attributes) {
609
- if (raw.event.detail.shape.polyline._attributes.fillColor) {
610
- const fill = new Color(Number(raw.event.detail.shape.polyline._attributes.fillColor));
611
- feat.properties['fill-opacity'] = fill.as_opacity() / 255;
612
- feat.properties['fill'] = fill.as_hex();
613
- }
614
- if (raw.event.detail.shape.polyline._attributes.color) {
615
- const stroke = new Color(Number(raw.event.detail.shape.polyline._attributes.color));
616
- feat.properties.stroke = stroke.as_hex();
617
- feat.properties['stroke-opacity'] = stroke.as_opacity() / 255;
618
- }
619
- }
620
- }
621
- if (raw.event.detail.color && raw.event.detail.color._attributes && raw.event.detail.color._attributes.argb) {
622
- const color = new Color(Number(raw.event.detail.color._attributes.argb));
623
- feat.properties['marker-color'] = color.as_hex();
624
- feat.properties['marker-opacity'] = color.as_opacity() / 255;
625
- }
626
- feat.properties.metadata = this.metadata;
627
- feat.path = this.path;
628
- return feat;
629
- }
630
- to_xml() {
631
- return xmljs.js2xml(this.raw, { compact: true });
632
- }
633
225
  is_stale() {
634
226
  return new Date(this.raw.event._attributes.stale) < new Date();
635
227
  }
@@ -793,64 +385,6 @@ export default class CoT {
793
385
  is_uav() {
794
386
  return !!this.raw.event._attributes.type.match(/^a-f-A-M-F-Q-r/);
795
387
  }
796
- /**
797
- * Parse an ATAK compliant Protobuf to a JS Object
798
- */
799
- static from_proto(raw, version = 1) {
800
- const ProtoMessage = RootMessage.lookupType(`atakmap.commoncommo.protobuf.v${version}.TakMessage`);
801
- // TODO Type this
802
- const msg = ProtoMessage.decode(raw);
803
- if (!msg.cotEvent)
804
- throw new Err(400, null, 'No cotEvent Data');
805
- const detail = {};
806
- const metadata = {};
807
- for (const key in msg.cotEvent.detail) {
808
- if (key === 'xmlDetail') {
809
- const parsed = xmljs.xml2js(`<detail>${msg.cotEvent.detail.xmlDetail}</detail>`, { compact: true });
810
- Object.assign(detail, parsed.detail);
811
- if (detail.metadata) {
812
- for (const key in detail.metadata) {
813
- metadata[key] = detail.metadata[key]._text;
814
- }
815
- delete detail.metadata;
816
- }
817
- }
818
- else if (key === 'group') {
819
- if (msg.cotEvent.detail[key]) {
820
- detail.__group = { _attributes: msg.cotEvent.detail[key] };
821
- }
822
- }
823
- else if (['contact', 'precisionlocation', 'status', 'takv', 'track'].includes(key)) {
824
- if (msg.cotEvent.detail[key]) {
825
- detail[key] = { _attributes: msg.cotEvent.detail[key] };
826
- }
827
- }
828
- }
829
- const cot = new CoT({
830
- event: {
831
- _attributes: {
832
- version: '2.0',
833
- uid: msg.cotEvent.uid, type: msg.cotEvent.type, how: msg.cotEvent.how,
834
- qos: msg.cotEvent.qos, opex: msg.cotEvent.opex, access: msg.cotEvent.access,
835
- time: new Date(msg.cotEvent.sendTime.toNumber()).toISOString(),
836
- start: new Date(msg.cotEvent.startTime.toNumber()).toISOString(),
837
- stale: new Date(msg.cotEvent.staleTime.toNumber()).toISOString(),
838
- },
839
- detail,
840
- point: {
841
- _attributes: {
842
- lat: msg.cotEvent.lat,
843
- lon: msg.cotEvent.lon,
844
- hae: msg.cotEvent.hae,
845
- le: msg.cotEvent.le,
846
- ce: msg.cotEvent.ce,
847
- },
848
- }
849
- }
850
- });
851
- cot.metadata = metadata;
852
- return cot;
853
- }
854
388
  /**
855
389
  * Return a CoT Message
856
390
  */
@@ -863,255 +397,5 @@ export default class CoT {
863
397
  }
864
398
  });
865
399
  }
866
- /**
867
- * Return an CoT Message given a GeoJSON Feature
868
- *
869
- * @param {Object} feature GeoJSON Point Feature
870
- *
871
- * @return {CoT}
872
- */
873
- static from_geojson(feature) {
874
- checkFeat(feature);
875
- if (checkFeat.errors)
876
- throw new Err(400, null, `${checkFeat.errors[0].message} (${checkFeat.errors[0].instancePath})`);
877
- const cot = {
878
- event: {
879
- _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),
880
- point: Util.cot_point(),
881
- detail: Util.cot_event_detail(feature.properties.callsign)
882
- }
883
- };
884
- if (feature.id)
885
- cot.event._attributes.uid = String(feature.id);
886
- if (feature.properties.callsign && !feature.id)
887
- cot.event._attributes.uid = feature.properties.callsign;
888
- if (!cot.event.detail)
889
- cot.event.detail = {};
890
- if (feature.properties.droid) {
891
- cot.event.detail.uid = { _attributes: { Droid: feature.properties.droid } };
892
- }
893
- if (feature.properties.archived) {
894
- cot.event.detail.archive = { _attributes: {} };
895
- }
896
- if (feature.properties.links) {
897
- if (!cot.event.detail.link)
898
- cot.event.detail.link = [];
899
- else if (!Array.isArray(cot.event.detail.link))
900
- cot.event.detail.link = [cot.event.detail.link];
901
- cot.event.detail.link.push(...feature.properties.links.map((link) => {
902
- return { _attributes: link };
903
- }));
904
- }
905
- if (feature.properties.dest) {
906
- const dest = !Array.isArray(feature.properties.dest) ? [feature.properties.dest] : feature.properties.dest;
907
- cot.event.detail.marti = {
908
- dest: dest.map((dest) => {
909
- return { _attributes: { ...dest } };
910
- })
911
- };
912
- }
913
- if (feature.properties.takv) {
914
- cot.event.detail.takv = { _attributes: { ...feature.properties.takv } };
915
- }
916
- if (feature.properties.creator) {
917
- cot.event.detail.creator = { _attributes: { ...feature.properties.creator } };
918
- }
919
- if (feature.properties.range !== undefined) {
920
- cot.event.detail.range = { _attributes: { value: feature.properties.range } };
921
- }
922
- if (feature.properties.bearing !== undefined) {
923
- cot.event.detail.bearing = { _attributes: { value: feature.properties.bearing } };
924
- }
925
- if (feature.properties.geofence) {
926
- cot.event.detail.__geofence = { _attributes: { ...feature.properties.geofence } };
927
- }
928
- if (feature.properties.milsym) {
929
- cot.event.detail.__milsym = { _attributes: { id: feature.properties.milsym.id } };
930
- }
931
- if (feature.properties.sensor) {
932
- cot.event.detail.sensor = { _attributes: { ...feature.properties.sensor } };
933
- }
934
- if (feature.properties.ackrequest) {
935
- cot.event.detail.ackrequest = { _attributes: { ...feature.properties.ackrequest } };
936
- }
937
- if (feature.properties.video) {
938
- if (feature.properties.video.connection) {
939
- const video = JSON.parse(JSON.stringify(feature.properties.video));
940
- const connection = video.connection;
941
- delete video.connection;
942
- cot.event.detail.__video = {
943
- _attributes: { ...video },
944
- ConnectionEntry: {
945
- _attributes: connection
946
- }
947
- };
948
- }
949
- else {
950
- cot.event.detail.__video = { _attributes: { ...feature.properties.video } };
951
- }
952
- }
953
- if (feature.properties.attachments) {
954
- cot.event.detail.attachment_list = { _attributes: { hashes: JSON.stringify(feature.properties.attachments) } };
955
- }
956
- if (feature.properties.contact) {
957
- cot.event.detail.contact = {
958
- _attributes: {
959
- ...feature.properties.contact,
960
- callsign: feature.properties.callsign || 'UNKNOWN',
961
- }
962
- };
963
- }
964
- if (feature.properties.fileshare) {
965
- cot.event.detail.fileshare = { _attributes: { ...feature.properties.fileshare } };
966
- }
967
- if (feature.properties.course !== undefined || feature.properties.speed !== undefined || feature.properties.slope !== undefined) {
968
- cot.event.detail.track = {
969
- _attributes: Util.cot_track_attr(feature.properties.course, feature.properties.speed, feature.properties.slope)
970
- };
971
- }
972
- if (feature.properties.group) {
973
- cot.event.detail.__group = { _attributes: { ...feature.properties.group } };
974
- }
975
- if (feature.properties.flow) {
976
- cot.event.detail['_flow-tags_'] = { _attributes: { ...feature.properties.flow } };
977
- }
978
- if (feature.properties.status) {
979
- cot.event.detail.status = { _attributes: { ...feature.properties.status } };
980
- }
981
- if (feature.properties.precisionlocation) {
982
- cot.event.detail.precisionlocation = { _attributes: { ...feature.properties.precisionlocation } };
983
- }
984
- if (feature.properties.icon) {
985
- cot.event.detail.usericon = { _attributes: { iconsetpath: feature.properties.icon } };
986
- }
987
- if (feature.properties.mission) {
988
- cot.event.detail.mission = {
989
- _attributes: {
990
- type: feature.properties.mission.type,
991
- guid: feature.properties.mission.guid,
992
- tool: feature.properties.mission.tool,
993
- name: feature.properties.mission.name,
994
- authorUid: feature.properties.mission.authorUid,
995
- }
996
- };
997
- if (feature.properties.mission.missionLayer) {
998
- cot.event.detail.mission.missionLayer = {};
999
- if (feature.properties.mission.missionLayer.name) {
1000
- cot.event.detail.mission.missionLayer.name = { _text: feature.properties.mission.missionLayer.name };
1001
- }
1002
- if (feature.properties.mission.missionLayer.parentUid) {
1003
- cot.event.detail.mission.missionLayer.parentUid = { _text: feature.properties.mission.missionLayer.parentUid };
1004
- }
1005
- if (feature.properties.mission.missionLayer.type) {
1006
- cot.event.detail.mission.missionLayer.type = { _text: feature.properties.mission.missionLayer.type };
1007
- }
1008
- if (feature.properties.mission.missionLayer.uid) {
1009
- cot.event.detail.mission.missionLayer.uid = { _text: feature.properties.mission.missionLayer.uid };
1010
- }
1011
- }
1012
- }
1013
- cot.event.detail.remarks = { _attributes: {}, _text: feature.properties.remarks || '' };
1014
- if (!feature.geometry) {
1015
- throw new Err(400, null, 'Must have Geometry');
1016
- }
1017
- else if (!['Point', 'Polygon', 'LineString'].includes(feature.geometry.type)) {
1018
- throw new Err(400, null, 'Unsupported Geometry Type');
1019
- }
1020
- if (feature.geometry.type === 'Point') {
1021
- cot.event.point._attributes.lon = String(feature.geometry.coordinates[0]);
1022
- cot.event.point._attributes.lat = String(feature.geometry.coordinates[1]);
1023
- cot.event.point._attributes.hae = String(feature.geometry.coordinates[2] || '0.0');
1024
- if (feature.properties['marker-color']) {
1025
- const color = new Color(feature.properties['marker-color'] || -1761607936);
1026
- color.a = feature.properties['marker-opacity'] !== undefined ? feature.properties['marker-opacity'] * 255 : 128;
1027
- cot.event.detail.color = { _attributes: { argb: String(color.as_32bit()) } };
1028
- }
1029
- }
1030
- else if (feature.geometry.type === 'Polygon' && feature.properties.type === 'u-d-c-c') {
1031
- if (!feature.properties.shape || !feature.properties.shape.ellipse) {
1032
- throw new Err(400, null, 'u-d-c-c (Circle) must define a feature.properties.shape.ellipse property');
1033
- }
1034
- cot.event.detail.shape = { ellipse: { _attributes: feature.properties.shape.ellipse } };
1035
- if (feature.properties.center) {
1036
- cot.event.point._attributes.lon = String(feature.properties.center[0]);
1037
- cot.event.point._attributes.lat = String(feature.properties.center[1]);
1038
- }
1039
- else {
1040
- const centre = PointOnFeature(feature);
1041
- cot.event.point._attributes.lon = String(centre.geometry.coordinates[0]);
1042
- cot.event.point._attributes.lat = String(centre.geometry.coordinates[1]);
1043
- cot.event.point._attributes.hae = '0.0';
1044
- }
1045
- }
1046
- else if (['Polygon', 'LineString'].includes(feature.geometry.type)) {
1047
- const stroke = new Color(feature.properties.stroke || -1761607936);
1048
- stroke.a = feature.properties['stroke-opacity'] !== undefined ? feature.properties['stroke-opacity'] * 255 : 128;
1049
- cot.event.detail.strokeColor = { _attributes: { value: String(stroke.as_32bit()) } };
1050
- if (!feature.properties['stroke-width'])
1051
- feature.properties['stroke-width'] = 3;
1052
- cot.event.detail.strokeWeight = { _attributes: {
1053
- value: String(feature.properties['stroke-width'])
1054
- } };
1055
- if (!feature.properties['stroke-style'])
1056
- feature.properties['stroke-style'] = 'solid';
1057
- cot.event.detail.strokeStyle = { _attributes: {
1058
- value: feature.properties['stroke-style']
1059
- } };
1060
- if (feature.geometry.type === 'LineString') {
1061
- cot.event._attributes.type = 'u-d-f';
1062
- if (!cot.event.detail.link)
1063
- cot.event.detail.link = [];
1064
- else if (!Array.isArray(cot.event.detail.link))
1065
- cot.event.detail.link = [cot.event.detail.link];
1066
- for (const coord of feature.geometry.coordinates) {
1067
- cot.event.detail.link.push({
1068
- _attributes: { point: `${coord[1]},${coord[0]}` }
1069
- });
1070
- }
1071
- }
1072
- else if (feature.geometry.type === 'Polygon') {
1073
- cot.event._attributes.type = 'u-d-f';
1074
- if (!cot.event.detail.link)
1075
- cot.event.detail.link = [];
1076
- else if (!Array.isArray(cot.event.detail.link))
1077
- cot.event.detail.link = [cot.event.detail.link];
1078
- // Inner rings are not yet supported
1079
- for (const coord of feature.geometry.coordinates[0]) {
1080
- cot.event.detail.link.push({
1081
- _attributes: { point: `${coord[1]},${coord[0]}` }
1082
- });
1083
- }
1084
- const fill = new Color(feature.properties.fill || -1761607936);
1085
- fill.a = feature.properties['fill-opacity'] !== undefined ? feature.properties['fill-opacity'] * 255 : 128;
1086
- cot.event.detail.fillColor = { _attributes: { value: String(fill.as_32bit()) } };
1087
- }
1088
- cot.event.detail.labels_on = { _attributes: { value: 'false' } };
1089
- cot.event.detail.tog = { _attributes: { enabled: '0' } };
1090
- if (feature.properties.center && Array.isArray(feature.properties.center) && feature.properties.center.length >= 2) {
1091
- cot.event.point._attributes.lon = String(feature.properties.center[0]);
1092
- cot.event.point._attributes.lat = String(feature.properties.center[1]);
1093
- if (feature.properties.center.length >= 3) {
1094
- cot.event.point._attributes.hae = String(feature.properties.center[2] || '0.0');
1095
- }
1096
- else {
1097
- cot.event.point._attributes.hae = '0.0';
1098
- }
1099
- }
1100
- else {
1101
- const centre = PointOnFeature(feature);
1102
- cot.event.point._attributes.lon = String(centre.geometry.coordinates[0]);
1103
- cot.event.point._attributes.lat = String(centre.geometry.coordinates[1]);
1104
- cot.event.point._attributes.hae = '0.0';
1105
- }
1106
- }
1107
- const newcot = new CoT(cot);
1108
- if (feature.properties.metadata) {
1109
- newcot.metadata = feature.properties.metadata;
1110
- }
1111
- if (feature.path) {
1112
- newcot.path = feature.path;
1113
- }
1114
- return newcot;
1115
- }
1116
400
  }
1117
401
  //# sourceMappingURL=cot.js.map