@tak-ps/node-cot 13.5.0 → 14.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.
@@ -2,22 +2,16 @@ import protobuf from 'protobufjs';
2
2
  import Err from '@openaddresses/batch-error';
3
3
  import { xml2js, js2xml } from 'xml-js';
4
4
  import { diff } from 'json-diff-ts';
5
- import { v4 as randomUUID } from 'uuid';
5
+ import { from_geojson } from './parser/from_geojson.js';
6
+ import { normalize_geojson } from './parser/normalize_geojson.js';
7
+ import { to_geojson } from './parser/to_geojson.js';
6
8
  import { InputFeature, } from './types/feature.js';
7
- import Ellipse from '@turf/ellipse';
8
- import PointOnFeature from '@turf/point-on-feature';
9
- import Truncate from '@turf/truncate';
10
- import { destination } from '@turf/destination';
11
- import Util from './utils/util.js';
12
- import Color from './utils/color.js';
13
9
  import JSONCoT, { Detail } from './types/types.js';
14
10
  import CoT from './cot.js';
15
11
  import AJV from 'ajv';
16
12
  import fs from 'fs';
17
13
  import path from 'node:path';
18
14
  import { fileURLToPath } from 'node:url';
19
- // GeoJSON Geospatial ops will truncate to the below
20
- const COORDINATE_PRECISION = 6;
21
15
  const protoPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'proto', 'takmessage.proto');
22
16
  const RootMessage = await protobuf.load(protoPath);
23
17
  const pkg = JSON.parse(String(fs.readFileSync(new URL('../package.json', import.meta.url))));
@@ -27,12 +21,6 @@ const checkXML = (new AJV({
27
21
  allowUnionTypes: true
28
22
  }))
29
23
  .compile(JSONCoT);
30
- const checkFeat = (new AJV({
31
- allErrors: true,
32
- coerceTypes: true,
33
- allowUnionTypes: true
34
- }))
35
- .compile(InputFeature);
36
24
  /**
37
25
  * Convert to and from an XML CoT message
38
26
  * @class
@@ -65,14 +53,14 @@ export class CoTParser {
65
53
  * Note: This diffs based on GeoJSON Representation of message
66
54
  * So if unknown properties are present they will be excluded from the diff
67
55
  */
68
- static isDiff(aCoT, bCoT, opts = {
56
+ static async isDiff(aCoT, bCoT, opts = {
69
57
  diffMetadata: false,
70
58
  diffStaleStartTime: false,
71
59
  diffDest: false,
72
60
  diffFlow: false
73
61
  }) {
74
- const a = this.to_geojson(aCoT);
75
- const b = this.to_geojson(bCoT);
62
+ const a = await this.to_geojson(aCoT);
63
+ const b = await this.to_geojson(bCoT);
76
64
  if (!opts.diffDest) {
77
65
  delete a.properties.dest;
78
66
  delete b.properties.dest;
@@ -106,7 +94,7 @@ export class CoTParser {
106
94
  /**
107
95
  * Return an ATAK Compliant Protobuf
108
96
  */
109
- static to_proto(cot, version = 1) {
97
+ static async to_proto(cot, version = 1) {
110
98
  if (version < 1 || version > 1)
111
99
  throw new Err(400, null, `Unsupported Proto Version: ${version}`);
112
100
  const ProtoMessage = RootMessage.lookupType(`atakmap.commoncommo.protobuf.v${version}.TakMessage`);
@@ -140,322 +128,13 @@ export class CoTParser {
140
128
  /**
141
129
  * Return a GeoJSON Feature from an XML CoT message
142
130
  */
143
- static to_geojson(cot) {
144
- const raw = JSON.parse(JSON.stringify(cot.raw));
145
- if (!raw.event.detail)
146
- raw.event.detail = {};
147
- if (!raw.event.detail.contact)
148
- raw.event.detail.contact = { _attributes: { callsign: 'UNKNOWN' } };
149
- if (!raw.event.detail.contact._attributes)
150
- raw.event.detail.contact._attributes = { callsign: 'UNKNOWN' };
151
- const feat = {
152
- id: raw.event._attributes.uid,
153
- type: 'Feature',
154
- properties: {
155
- callsign: raw.event.detail.contact._attributes.callsign || 'UNKNOWN',
156
- center: [Number(raw.event.point._attributes.lon), Number(raw.event.point._attributes.lat), Number(raw.event.point._attributes.hae)],
157
- type: raw.event._attributes.type,
158
- how: raw.event._attributes.how || '',
159
- time: raw.event._attributes.time,
160
- start: raw.event._attributes.start,
161
- stale: raw.event._attributes.stale,
162
- },
163
- geometry: {
164
- type: 'Point',
165
- coordinates: [Number(raw.event.point._attributes.lon), Number(raw.event.point._attributes.lat), Number(raw.event.point._attributes.hae)]
166
- }
167
- };
168
- const contact = JSON.parse(JSON.stringify(raw.event.detail.contact._attributes));
169
- delete contact.callsign;
170
- if (Object.keys(contact).length) {
171
- feat.properties.contact = contact;
172
- }
173
- if (cot.creator()) {
174
- feat.properties.creator = cot.creator();
175
- }
176
- if (raw.event.detail.remarks && raw.event.detail.remarks._text) {
177
- feat.properties.remarks = raw.event.detail.remarks._text;
178
- }
179
- if (raw.event.detail.fileshare) {
180
- feat.properties.fileshare = raw.event.detail.fileshare._attributes;
181
- if (feat.properties.fileshare && typeof feat.properties.fileshare.sizeInBytes === 'string') {
182
- feat.properties.fileshare.sizeInBytes = parseInt(feat.properties.fileshare.sizeInBytes);
183
- }
184
- }
185
- if (raw.event.detail.__milsym) {
186
- feat.properties.milsym = {
187
- id: raw.event.detail.__milsym._attributes.id
188
- };
189
- }
190
- if (raw.event.detail.sensor) {
191
- feat.properties.sensor = raw.event.detail.sensor._attributes;
192
- }
193
- if (raw.event.detail.range) {
194
- feat.properties.range = raw.event.detail.range._attributes.value;
195
- }
196
- if (raw.event.detail.bearing) {
197
- feat.properties.bearing = raw.event.detail.bearing._attributes.value;
198
- }
199
- if (raw.event.detail.labels_on && raw.event.detail.labels_on._attributes && raw.event.detail.labels_on._attributes.value !== undefined) {
200
- feat.properties.labels = raw.event.detail.labels_on._attributes.value;
201
- }
202
- if (raw.event.detail.__video && raw.event.detail.__video._attributes) {
203
- feat.properties.video = raw.event.detail.__video._attributes;
204
- if (raw.event.detail.__video.ConnectionEntry) {
205
- feat.properties.video.connection = raw.event.detail.__video.ConnectionEntry._attributes;
206
- }
207
- }
208
- if (raw.event.detail.__geofence) {
209
- feat.properties.geofence = raw.event.detail.__geofence._attributes;
210
- }
211
- if (raw.event.detail.ackrequest) {
212
- feat.properties.ackrequest = raw.event.detail.ackrequest._attributes;
213
- }
214
- if (raw.event.detail.attachment_list) {
215
- feat.properties.attachments = JSON.parse(raw.event.detail.attachment_list._attributes.hashes);
216
- }
217
- if (raw.event.detail.link) {
218
- if (!Array.isArray(raw.event.detail.link))
219
- raw.event.detail.link = [raw.event.detail.link];
220
- feat.properties.links = raw.event.detail.link.filter((link) => {
221
- return !!link._attributes.url;
222
- }).map((link) => {
223
- return link._attributes;
224
- });
225
- if (!feat.properties.links || !feat.properties.links.length)
226
- delete feat.properties.links;
227
- }
228
- if (raw.event.detail.archive) {
229
- feat.properties.archived = true;
230
- }
231
- if (raw.event.detail.__chat) {
232
- feat.properties.chat = {
233
- ...raw.event.detail.__chat._attributes,
234
- chatgrp: raw.event.detail.__chat.chatgrp
235
- };
236
- }
237
- if (raw.event.detail.track && raw.event.detail.track._attributes) {
238
- if (raw.event.detail.track._attributes.course)
239
- feat.properties.course = Number(raw.event.detail.track._attributes.course);
240
- if (raw.event.detail.track._attributes.slope)
241
- feat.properties.slope = Number(raw.event.detail.track._attributes.slope);
242
- if (raw.event.detail.track._attributes.course)
243
- feat.properties.speed = Number(raw.event.detail.track._attributes.speed);
244
- }
245
- if (raw.event.detail.marti && raw.event.detail.marti.dest) {
246
- if (!Array.isArray(raw.event.detail.marti.dest))
247
- raw.event.detail.marti.dest = [raw.event.detail.marti.dest];
248
- const dest = raw.event.detail.marti.dest.map((d) => {
249
- return { ...d._attributes };
250
- });
251
- feat.properties.dest = dest.length === 1 ? dest[0] : dest;
252
- }
253
- if (raw.event.detail.usericon && raw.event.detail.usericon._attributes && raw.event.detail.usericon._attributes.iconsetpath) {
254
- feat.properties.icon = raw.event.detail.usericon._attributes.iconsetpath;
255
- }
256
- if (raw.event.detail.uid && raw.event.detail.uid._attributes && raw.event.detail.uid._attributes.Droid) {
257
- feat.properties.droid = raw.event.detail.uid._attributes.Droid;
258
- }
259
- if (raw.event.detail.takv && raw.event.detail.takv._attributes) {
260
- feat.properties.takv = raw.event.detail.takv._attributes;
261
- }
262
- if (raw.event.detail.__group && raw.event.detail.__group._attributes) {
263
- feat.properties.group = raw.event.detail.__group._attributes;
264
- }
265
- if (raw.event.detail['_flow-tags_'] && raw.event.detail['_flow-tags_']._attributes) {
266
- feat.properties.flow = raw.event.detail['_flow-tags_']._attributes;
267
- }
268
- if (raw.event.detail.status && raw.event.detail.status._attributes) {
269
- feat.properties.status = raw.event.detail.status._attributes;
270
- }
271
- if (raw.event.detail.mission && raw.event.detail.mission._attributes) {
272
- const mission = {
273
- ...raw.event.detail.mission._attributes
274
- };
275
- if (raw.event.detail.mission && raw.event.detail.mission.MissionChanges) {
276
- const changes = Array.isArray(raw.event.detail.mission.MissionChanges)
277
- ? raw.event.detail.mission.MissionChanges
278
- : [raw.event.detail.mission.MissionChanges];
279
- mission.missionChanges = [];
280
- for (const change of changes) {
281
- mission.missionChanges.push({
282
- contentUid: change.MissionChange.contentUid._text,
283
- creatorUid: change.MissionChange.creatorUid._text,
284
- isFederatedChange: change.MissionChange.isFederatedChange._text,
285
- missionName: change.MissionChange.missionName._text,
286
- timestamp: change.MissionChange.timestamp._text,
287
- type: change.MissionChange.type._text,
288
- details: {
289
- ...change.MissionChange.details._attributes,
290
- ...change.MissionChange.details.location
291
- ? change.MissionChange.details.location._attributes
292
- : {}
293
- }
294
- });
295
- }
296
- }
297
- if (raw.event.detail.mission && raw.event.detail.mission.missionLayer) {
298
- const missionLayer = {};
299
- if (raw.event.detail.mission.missionLayer.name && raw.event.detail.mission.missionLayer.name._text) {
300
- missionLayer.name = raw.event.detail.mission.missionLayer.name._text;
301
- }
302
- if (raw.event.detail.mission.missionLayer.parentUid && raw.event.detail.mission.missionLayer.parentUid._text) {
303
- missionLayer.parentUid = raw.event.detail.mission.missionLayer.parentUid._text;
304
- }
305
- if (raw.event.detail.mission.missionLayer.type && raw.event.detail.mission.missionLayer.type._text) {
306
- missionLayer.type = raw.event.detail.mission.missionLayer.type._text;
307
- }
308
- if (raw.event.detail.mission.missionLayer.uid && raw.event.detail.mission.missionLayer.uid._text) {
309
- missionLayer.uid = raw.event.detail.mission.missionLayer.uid._text;
310
- }
311
- mission.missionLayer = missionLayer;
312
- }
313
- feat.properties.mission = mission;
314
- }
315
- if (raw.event.detail.precisionlocation && raw.event.detail.precisionlocation._attributes) {
316
- feat.properties.precisionlocation = raw.event.detail.precisionlocation._attributes;
317
- }
318
- if (raw.event.detail.strokeColor && raw.event.detail.strokeColor._attributes && raw.event.detail.strokeColor._attributes.value) {
319
- const stroke = new Color(Number(raw.event.detail.strokeColor._attributes.value));
320
- feat.properties.stroke = stroke.as_hex();
321
- feat.properties['stroke-opacity'] = stroke.as_opacity() / 255;
322
- }
323
- if (raw.event.detail.strokeWeight && raw.event.detail.strokeWeight._attributes && raw.event.detail.strokeWeight._attributes.value) {
324
- feat.properties['stroke-width'] = Number(raw.event.detail.strokeWeight._attributes.value);
325
- }
326
- if (raw.event.detail.strokeStyle && raw.event.detail.strokeStyle._attributes && raw.event.detail.strokeStyle._attributes.value) {
327
- feat.properties['stroke-style'] = raw.event.detail.strokeStyle._attributes.value;
328
- }
329
- if (raw.event.detail.color) {
330
- let color = null;
331
- if (Array.isArray(raw.event.detail.color) && raw.event.detail.color.length > 1) {
332
- color = raw.event.detail.color[0];
333
- if (!color._attributes)
334
- color._attributes = {};
335
- for (let i = raw.event.detail.color.length - 1; i >= 1; i--) {
336
- if (raw.event.detail.color[i]._attributes) {
337
- Object.assign(color._attributes, raw.event.detail.color[i]._attributes);
338
- }
339
- }
340
- }
341
- else if (Array.isArray(raw.event.detail.color) && raw.event.detail.color.length === 1) {
342
- color = raw.event.detail.color[0];
343
- }
344
- else if (!Array.isArray(raw.event.detail.color)) {
345
- color = raw.event.detail.color;
346
- }
347
- if (color && color._attributes && color._attributes.argb) {
348
- const parsedColor = new Color(Number(color._attributes.argb));
349
- feat.properties['marker-color'] = parsedColor.as_hex();
350
- feat.properties['marker-opacity'] = parsedColor.as_opacity() / 255;
351
- }
352
- }
353
- // Line, Polygon style types
354
- if (['u-d-f', 'u-d-r', 'b-m-r', 'u-rb-a'].includes(raw.event._attributes.type) && Array.isArray(raw.event.detail.link)) {
355
- const coordinates = [];
356
- for (const l of raw.event.detail.link) {
357
- if (!l._attributes.point)
358
- continue;
359
- coordinates.push(l._attributes.point.split(',').map((p) => { return Number(p.trim()); }).splice(0, 2).reverse());
360
- }
361
- // Range & Bearing Line
362
- if (raw.event._attributes.type === 'u-rb-a') {
363
- const detail = cot.detail();
364
- if (!detail.range)
365
- throw new Error('Range value not provided');
366
- if (!detail.bearing)
367
- throw new Error('Bearing value not provided');
368
- // TODO Support inclination
369
- const dest = destination(cot.position(), detail.range._attributes.value / 1000, detail.bearing._attributes.value).geometry.coordinates;
370
- feat.geometry = {
371
- type: 'LineString',
372
- coordinates: [cot.position(), dest]
373
- };
374
- }
375
- 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])) {
376
- if (raw.event._attributes.type === 'u-d-r') {
377
- // CoT rectangles are only 4 points - GeoJSON needs to be closed
378
- coordinates.push(coordinates[0]);
379
- }
380
- feat.geometry = {
381
- type: 'Polygon',
382
- coordinates: [coordinates]
383
- };
384
- if (raw.event.detail.fillColor && raw.event.detail.fillColor._attributes && raw.event.detail.fillColor._attributes.value) {
385
- const fill = new Color(Number(raw.event.detail.fillColor._attributes.value));
386
- feat.properties['fill-opacity'] = fill.as_opacity() / 255;
387
- feat.properties['fill'] = fill.as_hex();
388
- }
389
- }
390
- else {
391
- feat.geometry = {
392
- type: 'LineString',
393
- coordinates
394
- };
395
- }
396
- }
397
- else if (raw.event._attributes.type.startsWith('u-d-c-c') || raw.event._attributes.type.startsWith('u-r-b-c-c')) {
398
- if (!raw.event.detail.shape)
399
- throw new Err(400, null, `${raw.event._attributes.type} (Circle) must define shape value`);
400
- if (!raw.event.detail.shape.ellipse
401
- || !raw.event.detail.shape.ellipse._attributes)
402
- throw new Err(400, null, `${raw.event._attributes.type} (Circle) must define ellipse shape value`);
403
- const ellipse = {
404
- major: Number(raw.event.detail.shape.ellipse._attributes.major),
405
- minor: Number(raw.event.detail.shape.ellipse._attributes.minor),
406
- angle: Number(raw.event.detail.shape.ellipse._attributes.angle)
407
- };
408
- feat.geometry = Truncate(Ellipse(feat.geometry.coordinates, Number(ellipse.major) / 1000, Number(ellipse.minor) / 1000, {
409
- angle: ellipse.angle
410
- }), {
411
- precision: COORDINATE_PRECISION,
412
- mutate: true
413
- }).geometry;
414
- feat.properties.shape = {};
415
- feat.properties.shape.ellipse = ellipse;
416
- }
417
- else if (raw.event._attributes.type.startsWith('b-m-p-s-p-i')) {
418
- // TODO: Currently the "shape" tag is only parsed here - asking ARA for clarification if it is a general use tag
419
- if (raw.event.detail.shape && raw.event.detail.shape.polyline && raw.event.detail.shape.polyline.vertex) {
420
- const coordinates = [];
421
- const vertices = Array.isArray(raw.event.detail.shape.polyline.vertex) ? raw.event.detail.shape.polyline.vertex : [raw.event.detail.shape.polyline.vertex];
422
- for (const v of vertices) {
423
- coordinates.push([Number(v._attributes.lon), Number(v._attributes.lat)]);
424
- }
425
- if (coordinates.length === 1) {
426
- feat.geometry = { type: 'Point', coordinates: coordinates[0] };
427
- }
428
- else if (raw.event.detail.shape.polyline._attributes && raw.event.detail.shape.polyline._attributes.closed === true) {
429
- coordinates.push(coordinates[0]);
430
- feat.geometry = { type: 'Polygon', coordinates: [coordinates] };
431
- }
432
- else {
433
- feat.geometry = { type: 'LineString', coordinates };
434
- }
435
- }
436
- if (raw.event.detail.shape
437
- && raw.event.detail.shape.polyline
438
- && raw.event.detail.shape.polyline._attributes) {
439
- if (raw.event.detail.shape.polyline._attributes.fillColor) {
440
- const fill = new Color(Number(raw.event.detail.shape.polyline._attributes.fillColor));
441
- feat.properties['fill-opacity'] = fill.as_opacity() / 255;
442
- feat.properties['fill'] = fill.as_hex();
443
- }
444
- if (raw.event.detail.shape.polyline._attributes.color) {
445
- const stroke = new Color(Number(raw.event.detail.shape.polyline._attributes.color));
446
- feat.properties.stroke = stroke.as_hex();
447
- feat.properties['stroke-opacity'] = stroke.as_opacity() / 255;
448
- }
449
- }
450
- }
451
- feat.properties.metadata = cot.metadata;
452
- feat.path = cot.path;
453
- return feat;
131
+ static async to_geojson(cot) {
132
+ return await to_geojson(cot);
454
133
  }
455
134
  /**
456
135
  * Parse an ATAK compliant Protobuf to a JS Object
457
136
  */
458
- static from_proto(raw, version = 1, opts = {}) {
137
+ static async from_proto(raw, version = 1, opts = {}) {
459
138
  const ProtoMessage = RootMessage.lookupType(`atakmap.commoncommo.protobuf.v${version}.TakMessage`);
460
139
  // TODO Type this
461
140
  const msg = ProtoMessage.decode(raw);
@@ -510,6 +189,10 @@ export class CoTParser {
510
189
  cot.metadata = metadata;
511
190
  return this.validate(cot);
512
191
  }
192
+ static async normalize_geojson(feature) {
193
+ const feat = await normalize_geojson(feature);
194
+ return feat;
195
+ }
513
196
  /**
514
197
  * Return an CoT Message given a GeoJSON Feature
515
198
  *
@@ -517,305 +200,9 @@ export class CoTParser {
517
200
  *
518
201
  * @return {CoT}
519
202
  */
520
- static from_geojson(feature, opts = {}) {
521
- checkFeat(feature);
522
- if (checkFeat.errors)
523
- throw new Err(400, null, `${checkFeat.errors[0].message} (${checkFeat.errors[0].instancePath})`);
524
- const cot = {
525
- event: {
526
- _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),
527
- point: Util.cot_point(),
528
- detail: Util.cot_event_detail(feature.properties.callsign)
529
- }
530
- };
531
- if (feature.id)
532
- cot.event._attributes.uid = String(feature.id);
533
- if (feature.properties.callsign && !feature.id)
534
- cot.event._attributes.uid = feature.properties.callsign;
535
- if (!cot.event.detail)
536
- cot.event.detail = {};
537
- if (feature.properties.droid) {
538
- cot.event.detail.uid = { _attributes: { Droid: feature.properties.droid } };
539
- }
540
- if (feature.properties.archived) {
541
- cot.event.detail.archive = { _attributes: {} };
542
- }
543
- if (feature.properties.links) {
544
- if (!cot.event.detail.link)
545
- cot.event.detail.link = [];
546
- else if (!Array.isArray(cot.event.detail.link))
547
- cot.event.detail.link = [cot.event.detail.link];
548
- cot.event.detail.link.push(...feature.properties.links.map((link) => {
549
- return { _attributes: link };
550
- }));
551
- }
552
- if (feature.properties.dest) {
553
- const dest = !Array.isArray(feature.properties.dest) ? [feature.properties.dest] : feature.properties.dest;
554
- cot.event.detail.marti = {
555
- dest: dest.map((dest) => {
556
- return { _attributes: { ...dest } };
557
- })
558
- };
559
- }
560
- if (feature.properties.type === 'b-a-o-tbl') {
561
- cot.event.detail.emergency = {
562
- _attributes: { type: '911 Alert' },
563
- _text: feature.properties.callsign || 'UNKNOWN'
564
- };
565
- }
566
- else if (feature.properties.type === 'b-a-o-can') {
567
- cot.event.detail.emergency = {
568
- _attributes: { cancel: true },
569
- _text: feature.properties.callsign || 'UNKNOWN'
570
- };
571
- }
572
- else if (feature.properties.type === 'b-a-g') {
573
- cot.event.detail.emergency = {
574
- _attributes: { type: 'Geo-fence Breached' },
575
- _text: feature.properties.callsign || 'UNKNOWN'
576
- };
577
- }
578
- else if (feature.properties.type === 'b-a-o-pan') {
579
- cot.event.detail.emergency = {
580
- _attributes: { type: 'Ring The Bell' },
581
- _text: feature.properties.callsign || 'UNKNOWN'
582
- };
583
- }
584
- else if (feature.properties.type === 'b-a-o-opn') {
585
- cot.event.detail.emergency = {
586
- _attributes: { type: 'Troops In Contact' },
587
- _text: feature.properties.callsign || 'UNKNOWN'
588
- };
589
- }
590
- if (feature.properties.takv) {
591
- cot.event.detail.takv = { _attributes: { ...feature.properties.takv } };
592
- }
593
- if (feature.properties.creator) {
594
- cot.event.detail.creator = { _attributes: { ...feature.properties.creator } };
595
- }
596
- if (feature.properties.range !== undefined) {
597
- cot.event.detail.range = { _attributes: { value: feature.properties.range } };
598
- }
599
- if (feature.properties.bearing !== undefined) {
600
- cot.event.detail.bearing = { _attributes: { value: feature.properties.bearing } };
601
- }
602
- if (feature.properties.geofence) {
603
- cot.event.detail.__geofence = { _attributes: { ...feature.properties.geofence } };
604
- }
605
- if (feature.properties.milsym) {
606
- cot.event.detail.__milsym = { _attributes: { id: feature.properties.milsym.id } };
607
- }
608
- if (feature.properties.sensor) {
609
- cot.event.detail.sensor = { _attributes: { ...feature.properties.sensor } };
610
- }
611
- if (feature.properties.ackrequest) {
612
- cot.event.detail.ackrequest = { _attributes: { ...feature.properties.ackrequest } };
613
- }
614
- if (feature.properties.video) {
615
- if (feature.properties.video.connection) {
616
- const video = JSON.parse(JSON.stringify(feature.properties.video));
617
- const connection = video.connection;
618
- delete video.connection;
619
- cot.event.detail.__video = {
620
- _attributes: { ...video },
621
- ConnectionEntry: {
622
- _attributes: connection
623
- }
624
- };
625
- }
626
- else {
627
- cot.event.detail.__video = { _attributes: { ...feature.properties.video } };
628
- }
629
- }
630
- if (feature.properties.attachments) {
631
- cot.event.detail.attachment_list = { _attributes: { hashes: JSON.stringify(feature.properties.attachments) } };
632
- }
633
- if (feature.properties.contact) {
634
- cot.event.detail.contact = {
635
- _attributes: {
636
- ...feature.properties.contact,
637
- callsign: feature.properties.callsign || 'UNKNOWN',
638
- }
639
- };
640
- }
641
- if (feature.properties.fileshare) {
642
- cot.event.detail.fileshare = { _attributes: { ...feature.properties.fileshare } };
643
- }
644
- if (feature.properties.course !== undefined || feature.properties.speed !== undefined || feature.properties.slope !== undefined) {
645
- cot.event.detail.track = {
646
- _attributes: Util.cot_track_attr(feature.properties.course, feature.properties.speed, feature.properties.slope)
647
- };
648
- }
649
- if (feature.properties.group) {
650
- cot.event.detail.__group = { _attributes: { ...feature.properties.group } };
651
- }
652
- if (feature.properties.flow) {
653
- cot.event.detail['_flow-tags_'] = { _attributes: { ...feature.properties.flow } };
654
- }
655
- if (feature.properties.status) {
656
- cot.event.detail.status = { _attributes: { ...feature.properties.status } };
657
- }
658
- if (feature.properties.precisionlocation) {
659
- cot.event.detail.precisionlocation = { _attributes: { ...feature.properties.precisionlocation } };
660
- }
661
- if (feature.properties.icon) {
662
- cot.event.detail.usericon = { _attributes: { iconsetpath: feature.properties.icon } };
663
- }
664
- if (feature.properties.mission) {
665
- cot.event.detail.mission = {
666
- _attributes: {
667
- type: feature.properties.mission.type,
668
- guid: feature.properties.mission.guid,
669
- tool: feature.properties.mission.tool,
670
- name: feature.properties.mission.name,
671
- authorUid: feature.properties.mission.authorUid,
672
- }
673
- };
674
- if (feature.properties.mission.missionLayer) {
675
- cot.event.detail.mission.missionLayer = {};
676
- if (feature.properties.mission.missionLayer.name) {
677
- cot.event.detail.mission.missionLayer.name = { _text: feature.properties.mission.missionLayer.name };
678
- }
679
- if (feature.properties.mission.missionLayer.parentUid) {
680
- cot.event.detail.mission.missionLayer.parentUid = { _text: feature.properties.mission.missionLayer.parentUid };
681
- }
682
- if (feature.properties.mission.missionLayer.type) {
683
- cot.event.detail.mission.missionLayer.type = { _text: feature.properties.mission.missionLayer.type };
684
- }
685
- if (feature.properties.mission.missionLayer.uid) {
686
- cot.event.detail.mission.missionLayer.uid = { _text: feature.properties.mission.missionLayer.uid };
687
- }
688
- }
689
- }
690
- cot.event.detail.remarks = { _attributes: {}, _text: feature.properties.remarks || '' };
691
- if (!feature.geometry) {
692
- throw new Err(400, null, 'Must have Geometry');
693
- }
694
- else if (!['Point', 'Polygon', 'LineString'].includes(feature.geometry.type)) {
695
- throw new Err(400, null, 'Unsupported Geometry Type');
696
- }
697
- // This isn't specific to point as the color can apply to the centroid point
698
- if (feature.properties['marker-color']) {
699
- const color = new Color(feature.properties['marker-color'] || -1761607936);
700
- color.a = feature.properties['marker-opacity'] !== undefined ? feature.properties['marker-opacity'] * 255 : 128;
701
- cot.event.detail.color = {
702
- _attributes: {
703
- argb: color.as_32bit(),
704
- value: color.as_32bit()
705
- }
706
- };
707
- }
708
- if (feature.geometry.type === 'Point') {
709
- cot.event.point._attributes.lon = feature.geometry.coordinates[0];
710
- cot.event.point._attributes.lat = feature.geometry.coordinates[1];
711
- cot.event.point._attributes.hae = feature.geometry.coordinates[2] || 0.0;
712
- }
713
- else if (['Polygon', 'LineString'].includes(feature.geometry.type)) {
714
- const stroke = new Color(feature.properties.stroke || -1761607936);
715
- stroke.a = feature.properties['stroke-opacity'] !== undefined ? feature.properties['stroke-opacity'] * 255 : 128;
716
- cot.event.detail.strokeColor = { _attributes: { value: stroke.as_32bit() } };
717
- if (!feature.properties['stroke-width'])
718
- feature.properties['stroke-width'] = 3;
719
- cot.event.detail.strokeWeight = { _attributes: {
720
- value: feature.properties['stroke-width']
721
- } };
722
- if (!feature.properties['stroke-style'])
723
- feature.properties['stroke-style'] = 'solid';
724
- cot.event.detail.strokeStyle = { _attributes: {
725
- value: feature.properties['stroke-style']
726
- } };
727
- if (feature.geometry.type === 'Polygon' && feature.properties.type && ['u-d-c-c', 'u-r-b-c-c'].includes(feature.properties.type)) {
728
- if (!feature.properties.shape || !feature.properties.shape.ellipse) {
729
- throw new Err(400, null, `${feature.properties.type} (Circle) must define a feature.properties.shape.ellipse property`);
730
- }
731
- cot.event.detail.shape = { ellipse: { _attributes: feature.properties.shape.ellipse } };
732
- }
733
- else if (feature.geometry.type === 'LineString' && feature.properties.type === 'b-m-r') {
734
- cot.event._attributes.type = 'b-m-r';
735
- if (!cot.event.detail.link) {
736
- cot.event.detail.link = [];
737
- }
738
- else if (!Array.isArray(cot.event.detail.link)) {
739
- cot.event.detail.link = [cot.event.detail.link];
740
- }
741
- cot.event.detail.__routeinfo = {
742
- __navcues: {
743
- __navcue: []
744
- }
745
- };
746
- for (const coord of feature.geometry.coordinates) {
747
- cot.event.detail.link.push({
748
- _attributes: {
749
- type: 'b-m-p-c',
750
- uid: randomUUID(),
751
- callsign: "",
752
- point: `${coord[1]},${coord[0]}`
753
- }
754
- });
755
- }
756
- }
757
- else if (feature.geometry.type === 'LineString') {
758
- cot.event._attributes.type = 'u-d-f';
759
- if (!cot.event.detail.link) {
760
- cot.event.detail.link = [];
761
- }
762
- else if (!Array.isArray(cot.event.detail.link)) {
763
- cot.event.detail.link = [cot.event.detail.link];
764
- }
765
- for (const coord of feature.geometry.coordinates) {
766
- cot.event.detail.link.push({
767
- _attributes: { point: `${coord[1]},${coord[0]}` }
768
- });
769
- }
770
- }
771
- else if (feature.geometry.type === 'Polygon') {
772
- cot.event._attributes.type = 'u-d-f';
773
- if (!cot.event.detail.link)
774
- cot.event.detail.link = [];
775
- else if (!Array.isArray(cot.event.detail.link))
776
- cot.event.detail.link = [cot.event.detail.link];
777
- // Inner rings are not yet supported
778
- for (const coord of feature.geometry.coordinates[0]) {
779
- cot.event.detail.link.push({
780
- _attributes: { point: `${coord[1]},${coord[0]}` }
781
- });
782
- }
783
- const fill = new Color(feature.properties.fill || -1761607936);
784
- fill.a = feature.properties['fill-opacity'] !== undefined ? feature.properties['fill-opacity'] * 255 : 128;
785
- cot.event.detail.fillColor = { _attributes: { value: fill.as_32bit() } };
786
- }
787
- if (feature.properties.labels) {
788
- cot.event.detail.labels_on = { _attributes: { value: feature.properties.labels } };
789
- }
790
- else {
791
- cot.event.detail.labels_on = { _attributes: { value: false } };
792
- }
793
- cot.event.detail.tog = { _attributes: { enabled: '0' } };
794
- if (feature.properties.center && Array.isArray(feature.properties.center) && feature.properties.center.length >= 2) {
795
- cot.event.point._attributes.lon = feature.properties.center[0];
796
- cot.event.point._attributes.lat = feature.properties.center[1];
797
- if (feature.properties.center.length >= 3) {
798
- cot.event.point._attributes.hae = feature.properties.center[2] || 0.0;
799
- }
800
- else {
801
- cot.event.point._attributes.hae = 0.0;
802
- }
803
- }
804
- else {
805
- const centre = PointOnFeature(feature);
806
- cot.event.point._attributes.lon = centre.geometry.coordinates[0];
807
- cot.event.point._attributes.lat = centre.geometry.coordinates[1];
808
- cot.event.point._attributes.hae = 0.0;
809
- }
810
- }
811
- const newcot = new CoT(cot, opts);
812
- if (feature.properties.metadata) {
813
- newcot.metadata = feature.properties.metadata;
814
- }
815
- if (feature.path) {
816
- newcot.path = feature.path;
817
- }
818
- return this.validate(newcot);
203
+ static async from_geojson(feature, opts = {}) {
204
+ const cot = await from_geojson(feature, opts);
205
+ return this.validate(cot);
819
206
  }
820
207
  }
821
208
  //# sourceMappingURL=parser.js.map