@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.
- package/CHANGELOG.md +10 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/parser/from_geojson.js +344 -0
- package/dist/lib/parser/from_geojson.js.map +1 -0
- package/dist/lib/parser/normalize_geojson.js +85 -0
- package/dist/lib/parser/normalize_geojson.js.map +1 -0
- package/dist/lib/parser/to_geojson.js +325 -0
- package/dist/lib/parser/to_geojson.js.map +1 -0
- package/dist/lib/parser.js +17 -630
- package/dist/lib/parser.js.map +1 -1
- package/dist/lib/type.js +36 -0
- package/dist/lib/type.js.map +1 -0
- package/dist/lib/types/geojson.js +50 -0
- package/dist/lib/types/geojson.js.map +1 -0
- package/dist/lib/utils/color.js +13 -1
- package/dist/lib/utils/color.js.map +1 -1
- package/dist/package.json +5 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/index.ts +4 -0
- package/lib/parser/from_geojson.ts +401 -0
- package/lib/parser/normalize_geojson.ts +104 -0
- package/lib/parser/to_geojson.ts +395 -0
- package/lib/parser.ts +25 -741
- package/lib/type.ts +49 -0
- package/lib/types/geojson.ts +59 -0
- package/lib/utils/color.ts +14 -1
- package/package.json +5 -2
package/lib/parser.ts
CHANGED
|
@@ -2,31 +2,19 @@ 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';
|
|
6
5
|
import type { Static } from '@sinclair/typebox';
|
|
6
|
+
import { from_geojson } from './parser/from_geojson.js';
|
|
7
|
+
import { normalize_geojson } from './parser/normalize_geojson.js';
|
|
8
|
+
import { to_geojson } from './parser/to_geojson.js';
|
|
7
9
|
import type {
|
|
8
10
|
Feature,
|
|
9
|
-
Polygon,
|
|
10
|
-
FeaturePropertyMission,
|
|
11
|
-
FeaturePropertyMissionLayer,
|
|
12
11
|
} from './types/feature.js';
|
|
13
12
|
import type {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Link,
|
|
17
|
-
LinkAttributes,
|
|
18
|
-
ColorAttributes,
|
|
19
|
-
} from './types/types.js'
|
|
13
|
+
GeoJSONFeature,
|
|
14
|
+
} from './types/geojson.js';
|
|
20
15
|
import {
|
|
21
16
|
InputFeature,
|
|
22
17
|
} from './types/feature.js';
|
|
23
|
-
import type { AllGeoJSON } from "@turf/helpers";
|
|
24
|
-
import Ellipse from '@turf/ellipse';
|
|
25
|
-
import PointOnFeature from '@turf/point-on-feature';
|
|
26
|
-
import Truncate from '@turf/truncate';
|
|
27
|
-
import { destination } from '@turf/destination';
|
|
28
|
-
import Util from './utils/util.js';
|
|
29
|
-
import Color from './utils/color.js';
|
|
30
18
|
import JSONCoT, { Detail } from './types/types.js'
|
|
31
19
|
import CoT from './cot.js';
|
|
32
20
|
import type { CoTOptions } from './cot.js';
|
|
@@ -35,9 +23,6 @@ import fs from 'fs';
|
|
|
35
23
|
import path from 'node:path';
|
|
36
24
|
import { fileURLToPath } from 'node:url';
|
|
37
25
|
|
|
38
|
-
// GeoJSON Geospatial ops will truncate to the below
|
|
39
|
-
const COORDINATE_PRECISION = 6;
|
|
40
|
-
|
|
41
26
|
const protoPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'proto', 'takmessage.proto');
|
|
42
27
|
const RootMessage = await protobuf.load(protoPath);
|
|
43
28
|
|
|
@@ -50,13 +35,6 @@ const checkXML = (new AJV({
|
|
|
50
35
|
}))
|
|
51
36
|
.compile(JSONCoT);
|
|
52
37
|
|
|
53
|
-
const checkFeat = (new AJV({
|
|
54
|
-
allErrors: true,
|
|
55
|
-
coerceTypes: true,
|
|
56
|
-
allowUnionTypes: true
|
|
57
|
-
}))
|
|
58
|
-
.compile(InputFeature);
|
|
59
|
-
|
|
60
38
|
/**
|
|
61
39
|
* Convert to and from an XML CoT message
|
|
62
40
|
* @class
|
|
@@ -97,7 +75,7 @@ export class CoTParser {
|
|
|
97
75
|
* Note: This diffs based on GeoJSON Representation of message
|
|
98
76
|
* So if unknown properties are present they will be excluded from the diff
|
|
99
77
|
*/
|
|
100
|
-
static isDiff(
|
|
78
|
+
static async isDiff(
|
|
101
79
|
aCoT: CoT,
|
|
102
80
|
bCoT: CoT,
|
|
103
81
|
opts = {
|
|
@@ -106,9 +84,9 @@ export class CoTParser {
|
|
|
106
84
|
diffDest: false,
|
|
107
85
|
diffFlow: false
|
|
108
86
|
}
|
|
109
|
-
): boolean {
|
|
110
|
-
const a = this.to_geojson(aCoT) as Static<typeof InputFeature>;
|
|
111
|
-
const b = this.to_geojson(bCoT) as Static<typeof InputFeature>;
|
|
87
|
+
): Promise<boolean> {
|
|
88
|
+
const a = await this.to_geojson(aCoT) as Static<typeof InputFeature>;
|
|
89
|
+
const b = await this.to_geojson(bCoT) as Static<typeof InputFeature>;
|
|
112
90
|
|
|
113
91
|
if (!opts.diffDest) {
|
|
114
92
|
delete a.properties.dest;
|
|
@@ -159,7 +137,7 @@ export class CoTParser {
|
|
|
159
137
|
/**
|
|
160
138
|
* Return an ATAK Compliant Protobuf
|
|
161
139
|
*/
|
|
162
|
-
static to_proto(cot: CoT, version = 1): Uint8Array {
|
|
140
|
+
static async to_proto(cot: CoT, version = 1): Promise<Uint8Array> {
|
|
163
141
|
if (version < 1 || version > 1) throw new Err(400, null, `Unsupported Proto Version: ${version}`);
|
|
164
142
|
const ProtoMessage = RootMessage.lookupType(`atakmap.commoncommo.protobuf.v${version}.TakMessage`)
|
|
165
143
|
|
|
@@ -198,382 +176,18 @@ export class CoTParser {
|
|
|
198
176
|
/**
|
|
199
177
|
* Return a GeoJSON Feature from an XML CoT message
|
|
200
178
|
*/
|
|
201
|
-
static to_geojson(cot: CoT): Static<typeof Feature
|
|
202
|
-
|
|
203
|
-
if (!raw.event.detail) raw.event.detail = {};
|
|
204
|
-
if (!raw.event.detail.contact) raw.event.detail.contact = { _attributes: { callsign: 'UNKNOWN' } };
|
|
205
|
-
if (!raw.event.detail.contact._attributes) raw.event.detail.contact._attributes = { callsign: 'UNKNOWN' };
|
|
206
|
-
|
|
207
|
-
const feat: Static<typeof Feature> = {
|
|
208
|
-
id: raw.event._attributes.uid,
|
|
209
|
-
type: 'Feature',
|
|
210
|
-
properties: {
|
|
211
|
-
callsign: raw.event.detail.contact._attributes.callsign || 'UNKNOWN',
|
|
212
|
-
center: [ Number(raw.event.point._attributes.lon), Number(raw.event.point._attributes.lat), Number(raw.event.point._attributes.hae) ],
|
|
213
|
-
type: raw.event._attributes.type,
|
|
214
|
-
how: raw.event._attributes.how || '',
|
|
215
|
-
time: raw.event._attributes.time,
|
|
216
|
-
start: raw.event._attributes.start,
|
|
217
|
-
stale: raw.event._attributes.stale,
|
|
218
|
-
},
|
|
219
|
-
geometry: {
|
|
220
|
-
type: 'Point',
|
|
221
|
-
coordinates: [ Number(raw.event.point._attributes.lon), Number(raw.event.point._attributes.lat), Number(raw.event.point._attributes.hae) ]
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const contact = JSON.parse(JSON.stringify(raw.event.detail.contact._attributes));
|
|
226
|
-
delete contact.callsign;
|
|
227
|
-
if (Object.keys(contact).length) {
|
|
228
|
-
feat.properties.contact = contact;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (cot.creator()) {
|
|
232
|
-
feat.properties.creator = cot.creator();
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (raw.event.detail.remarks && raw.event.detail.remarks._text) {
|
|
236
|
-
feat.properties.remarks = raw.event.detail.remarks._text;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (raw.event.detail.fileshare) {
|
|
240
|
-
feat.properties.fileshare = raw.event.detail.fileshare._attributes;
|
|
241
|
-
if (feat.properties.fileshare && typeof feat.properties.fileshare.sizeInBytes === 'string') {
|
|
242
|
-
feat.properties.fileshare.sizeInBytes = parseInt(feat.properties.fileshare.sizeInBytes)
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (raw.event.detail.__milsym) {
|
|
247
|
-
feat.properties.milsym = {
|
|
248
|
-
id: raw.event.detail.__milsym._attributes.id
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (raw.event.detail.sensor) {
|
|
253
|
-
feat.properties.sensor = raw.event.detail.sensor._attributes;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (raw.event.detail.range) {
|
|
257
|
-
feat.properties.range = raw.event.detail.range._attributes.value;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (raw.event.detail.bearing) {
|
|
261
|
-
feat.properties.bearing = raw.event.detail.bearing._attributes.value;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (raw.event.detail.labels_on && raw.event.detail.labels_on._attributes && raw.event.detail.labels_on._attributes.value !== undefined) {
|
|
265
|
-
feat.properties.labels = raw.event.detail.labels_on._attributes.value;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (raw.event.detail.__video && raw.event.detail.__video._attributes) {
|
|
269
|
-
feat.properties.video = raw.event.detail.__video._attributes;
|
|
270
|
-
|
|
271
|
-
if (raw.event.detail.__video.ConnectionEntry) {
|
|
272
|
-
feat.properties.video.connection = raw.event.detail.__video.ConnectionEntry._attributes;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (raw.event.detail.__geofence) {
|
|
277
|
-
feat.properties.geofence = raw.event.detail.__geofence._attributes;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (raw.event.detail.ackrequest) {
|
|
281
|
-
feat.properties.ackrequest = raw.event.detail.ackrequest._attributes;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (raw.event.detail.attachment_list) {
|
|
285
|
-
feat.properties.attachments = JSON.parse(raw.event.detail.attachment_list._attributes.hashes);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (raw.event.detail.link) {
|
|
289
|
-
if (!Array.isArray(raw.event.detail.link)) raw.event.detail.link = [raw.event.detail.link];
|
|
290
|
-
|
|
291
|
-
feat.properties.links = raw.event.detail.link.filter((link: Static<typeof Link>) => {
|
|
292
|
-
return !!link._attributes.url
|
|
293
|
-
}).map((link: Static<typeof Link>): Static<typeof LinkAttributes> => {
|
|
294
|
-
return link._attributes;
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
if (!feat.properties.links || !feat.properties.links.length) delete feat.properties.links;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (raw.event.detail.archive) {
|
|
301
|
-
feat.properties.archived = true;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (raw.event.detail.__chat) {
|
|
305
|
-
feat.properties.chat = {
|
|
306
|
-
...raw.event.detail.__chat._attributes,
|
|
307
|
-
chatgrp: raw.event.detail.__chat.chatgrp
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (raw.event.detail.track && raw.event.detail.track._attributes) {
|
|
312
|
-
if (raw.event.detail.track._attributes.course) feat.properties.course = Number(raw.event.detail.track._attributes.course);
|
|
313
|
-
if (raw.event.detail.track._attributes.slope) feat.properties.slope = Number(raw.event.detail.track._attributes.slope);
|
|
314
|
-
if (raw.event.detail.track._attributes.course) feat.properties.speed = Number(raw.event.detail.track._attributes.speed);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (raw.event.detail.marti && raw.event.detail.marti.dest) {
|
|
318
|
-
if (!Array.isArray(raw.event.detail.marti.dest)) raw.event.detail.marti.dest = [raw.event.detail.marti.dest];
|
|
319
|
-
|
|
320
|
-
const dest: Array<Static<typeof MartiDestAttributes>> = raw.event.detail.marti.dest.map((d: Static<typeof MartiDest>) => {
|
|
321
|
-
return { ...d._attributes };
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
feat.properties.dest = dest.length === 1 ? dest[0] : dest
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (raw.event.detail.usericon && raw.event.detail.usericon._attributes && raw.event.detail.usericon._attributes.iconsetpath) {
|
|
328
|
-
feat.properties.icon = raw.event.detail.usericon._attributes.iconsetpath;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (raw.event.detail.uid && raw.event.detail.uid._attributes && raw.event.detail.uid._attributes.Droid) {
|
|
333
|
-
feat.properties.droid = raw.event.detail.uid._attributes.Droid;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (raw.event.detail.takv && raw.event.detail.takv._attributes) {
|
|
337
|
-
feat.properties.takv = raw.event.detail.takv._attributes;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (raw.event.detail.__group && raw.event.detail.__group._attributes) {
|
|
341
|
-
feat.properties.group = raw.event.detail.__group._attributes;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (raw.event.detail['_flow-tags_'] && raw.event.detail['_flow-tags_']._attributes) {
|
|
345
|
-
feat.properties.flow = raw.event.detail['_flow-tags_']._attributes;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (raw.event.detail.status && raw.event.detail.status._attributes) {
|
|
349
|
-
feat.properties.status = raw.event.detail.status._attributes;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (raw.event.detail.mission && raw.event.detail.mission._attributes) {
|
|
353
|
-
const mission: Static<typeof FeaturePropertyMission> = {
|
|
354
|
-
...raw.event.detail.mission._attributes
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
if (raw.event.detail.mission && raw.event.detail.mission.MissionChanges) {
|
|
358
|
-
const changes =
|
|
359
|
-
Array.isArray(raw.event.detail.mission.MissionChanges)
|
|
360
|
-
? raw.event.detail.mission.MissionChanges
|
|
361
|
-
: [ raw.event.detail.mission.MissionChanges ]
|
|
362
|
-
|
|
363
|
-
mission.missionChanges = []
|
|
364
|
-
for (const change of changes) {
|
|
365
|
-
mission.missionChanges.push({
|
|
366
|
-
contentUid: change.MissionChange.contentUid._text,
|
|
367
|
-
creatorUid: change.MissionChange.creatorUid._text,
|
|
368
|
-
isFederatedChange: change.MissionChange.isFederatedChange._text,
|
|
369
|
-
missionName: change.MissionChange.missionName._text,
|
|
370
|
-
timestamp: change.MissionChange.timestamp._text,
|
|
371
|
-
type: change.MissionChange.type._text,
|
|
372
|
-
details: {
|
|
373
|
-
...change.MissionChange.details._attributes,
|
|
374
|
-
...change.MissionChange.details.location
|
|
375
|
-
? change.MissionChange.details.location._attributes
|
|
376
|
-
: {}
|
|
377
|
-
}
|
|
378
|
-
})
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if (raw.event.detail.mission && raw.event.detail.mission.missionLayer) {
|
|
384
|
-
const missionLayer: Static<typeof FeaturePropertyMissionLayer> = {};
|
|
385
|
-
|
|
386
|
-
if (raw.event.detail.mission.missionLayer.name && raw.event.detail.mission.missionLayer.name._text) {
|
|
387
|
-
missionLayer.name = raw.event.detail.mission.missionLayer.name._text;
|
|
388
|
-
}
|
|
389
|
-
if (raw.event.detail.mission.missionLayer.parentUid && raw.event.detail.mission.missionLayer.parentUid._text) {
|
|
390
|
-
missionLayer.parentUid = raw.event.detail.mission.missionLayer.parentUid._text;
|
|
391
|
-
}
|
|
392
|
-
if (raw.event.detail.mission.missionLayer.type && raw.event.detail.mission.missionLayer.type._text) {
|
|
393
|
-
missionLayer.type = raw.event.detail.mission.missionLayer.type._text;
|
|
394
|
-
}
|
|
395
|
-
if (raw.event.detail.mission.missionLayer.uid && raw.event.detail.mission.missionLayer.uid._text) {
|
|
396
|
-
missionLayer.uid = raw.event.detail.mission.missionLayer.uid._text;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
mission.missionLayer = missionLayer;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
feat.properties.mission = mission;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (raw.event.detail.precisionlocation && raw.event.detail.precisionlocation._attributes) {
|
|
406
|
-
feat.properties.precisionlocation = raw.event.detail.precisionlocation._attributes;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (raw.event.detail.strokeColor && raw.event.detail.strokeColor._attributes && raw.event.detail.strokeColor._attributes.value) {
|
|
410
|
-
const stroke = new Color(Number(raw.event.detail.strokeColor._attributes.value));
|
|
411
|
-
feat.properties.stroke = stroke.as_hex();
|
|
412
|
-
feat.properties['stroke-opacity'] = stroke.as_opacity() / 255;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (raw.event.detail.strokeWeight && raw.event.detail.strokeWeight._attributes && raw.event.detail.strokeWeight._attributes.value) {
|
|
416
|
-
feat.properties['stroke-width'] = Number(raw.event.detail.strokeWeight._attributes.value);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (raw.event.detail.strokeStyle && raw.event.detail.strokeStyle._attributes && raw.event.detail.strokeStyle._attributes.value) {
|
|
420
|
-
feat.properties['stroke-style'] = raw.event.detail.strokeStyle._attributes.value;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (raw.event.detail.color) {
|
|
424
|
-
let color: Static<typeof ColorAttributes> | null = null;
|
|
425
|
-
|
|
426
|
-
if (Array.isArray(raw.event.detail.color) && raw.event.detail.color.length > 1) {
|
|
427
|
-
color = raw.event.detail.color[0];
|
|
428
|
-
if (!color._attributes) color._attributes = {};
|
|
429
|
-
|
|
430
|
-
for (let i = raw.event.detail.color.length - 1; i >= 1; i--) {
|
|
431
|
-
if (raw.event.detail.color[i]._attributes) {
|
|
432
|
-
Object.assign(color._attributes, raw.event.detail.color[i]._attributes);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
} else if (Array.isArray(raw.event.detail.color) && raw.event.detail.color.length === 1) {
|
|
436
|
-
color = raw.event.detail.color[0];
|
|
437
|
-
} else if (!Array.isArray(raw.event.detail.color)) {
|
|
438
|
-
color = raw.event.detail.color;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
if (color && color._attributes && color._attributes.argb) {
|
|
442
|
-
const parsedColor = new Color(Number(color._attributes.argb));
|
|
443
|
-
feat.properties['marker-color'] = parsedColor.as_hex();
|
|
444
|
-
feat.properties['marker-opacity'] = parsedColor.as_opacity() / 255;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Line, Polygon style types
|
|
449
|
-
if (['u-d-f', 'u-d-r', 'b-m-r', 'u-rb-a'].includes(raw.event._attributes.type) && Array.isArray(raw.event.detail.link)) {
|
|
450
|
-
const coordinates = [];
|
|
451
|
-
|
|
452
|
-
for (const l of raw.event.detail.link) {
|
|
453
|
-
if (!l._attributes.point) continue;
|
|
454
|
-
coordinates.push(l._attributes.point.split(',').map((p: string) => { return Number(p.trim()) }).splice(0, 2).reverse());
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// Range & Bearing Line
|
|
458
|
-
if (raw.event._attributes.type === 'u-rb-a') {
|
|
459
|
-
const detail = cot.detail();
|
|
460
|
-
|
|
461
|
-
if (!detail.range) throw new Error('Range value not provided')
|
|
462
|
-
if (!detail.bearing) throw new Error('Bearing value not provided')
|
|
463
|
-
|
|
464
|
-
// TODO Support inclination
|
|
465
|
-
const dest = destination(
|
|
466
|
-
cot.position(),
|
|
467
|
-
detail.range._attributes.value / 1000,
|
|
468
|
-
detail.bearing._attributes.value
|
|
469
|
-
).geometry.coordinates;
|
|
470
|
-
|
|
471
|
-
feat.geometry = {
|
|
472
|
-
type: 'LineString',
|
|
473
|
-
coordinates: [cot.position(), dest]
|
|
474
|
-
};
|
|
475
|
-
} 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])) {
|
|
476
|
-
if (raw.event._attributes.type === 'u-d-r') {
|
|
477
|
-
// CoT rectangles are only 4 points - GeoJSON needs to be closed
|
|
478
|
-
coordinates.push(coordinates[0])
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
feat.geometry = {
|
|
482
|
-
type: 'Polygon',
|
|
483
|
-
coordinates: [coordinates]
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
if (raw.event.detail.fillColor && raw.event.detail.fillColor._attributes && raw.event.detail.fillColor._attributes.value) {
|
|
487
|
-
const fill = new Color(Number(raw.event.detail.fillColor._attributes.value));
|
|
488
|
-
feat.properties['fill-opacity'] = fill.as_opacity() / 255;
|
|
489
|
-
feat.properties['fill'] = fill.as_hex();
|
|
490
|
-
}
|
|
491
|
-
} else {
|
|
492
|
-
feat.geometry = {
|
|
493
|
-
type: 'LineString',
|
|
494
|
-
coordinates
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
} else if (raw.event._attributes.type.startsWith('u-d-c-c') || raw.event._attributes.type.startsWith('u-r-b-c-c')) {
|
|
498
|
-
if (!raw.event.detail.shape) throw new Err(400, null, `${raw.event._attributes.type} (Circle) must define shape value`)
|
|
499
|
-
if (
|
|
500
|
-
!raw.event.detail.shape.ellipse
|
|
501
|
-
|| !raw.event.detail.shape.ellipse._attributes
|
|
502
|
-
) throw new Err(400, null, `${raw.event._attributes.type} (Circle) must define ellipse shape value`)
|
|
503
|
-
|
|
504
|
-
const ellipse = {
|
|
505
|
-
major: Number(raw.event.detail.shape.ellipse._attributes.major),
|
|
506
|
-
minor: Number(raw.event.detail.shape.ellipse._attributes.minor),
|
|
507
|
-
angle: Number(raw.event.detail.shape.ellipse._attributes.angle)
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
feat.geometry = Truncate(Ellipse(
|
|
511
|
-
feat.geometry.coordinates as number[],
|
|
512
|
-
Number(ellipse.major) / 1000,
|
|
513
|
-
Number(ellipse.minor) / 1000,
|
|
514
|
-
{
|
|
515
|
-
angle: ellipse.angle
|
|
516
|
-
}
|
|
517
|
-
), {
|
|
518
|
-
precision: COORDINATE_PRECISION,
|
|
519
|
-
mutate: true
|
|
520
|
-
}).geometry as Static<typeof Polygon>;
|
|
521
|
-
|
|
522
|
-
feat.properties.shape = {};
|
|
523
|
-
feat.properties.shape.ellipse = ellipse;
|
|
524
|
-
} else if (raw.event._attributes.type.startsWith('b-m-p-s-p-i')) {
|
|
525
|
-
// TODO: Currently the "shape" tag is only parsed here - asking ARA for clarification if it is a general use tag
|
|
526
|
-
if (raw.event.detail.shape && raw.event.detail.shape.polyline && raw.event.detail.shape.polyline.vertex) {
|
|
527
|
-
const coordinates = [];
|
|
528
|
-
|
|
529
|
-
const vertices = Array.isArray(raw.event.detail.shape.polyline.vertex) ? raw.event.detail.shape.polyline.vertex : [raw.event.detail.shape.polyline.vertex];
|
|
530
|
-
for (const v of vertices) {
|
|
531
|
-
coordinates.push([Number(v._attributes.lon), Number(v._attributes.lat)]);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
if (coordinates.length === 1) {
|
|
535
|
-
feat.geometry = { type: 'Point', coordinates: coordinates[0] }
|
|
536
|
-
} else if (raw.event.detail.shape.polyline._attributes && raw.event.detail.shape.polyline._attributes.closed === true) {
|
|
537
|
-
coordinates.push(coordinates[0]);
|
|
538
|
-
feat.geometry = { type: 'Polygon', coordinates: [coordinates] }
|
|
539
|
-
} else {
|
|
540
|
-
feat.geometry = { type: 'LineString', coordinates }
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if (
|
|
545
|
-
raw.event.detail.shape
|
|
546
|
-
&& raw.event.detail.shape.polyline
|
|
547
|
-
&& raw.event.detail.shape.polyline._attributes
|
|
548
|
-
) {
|
|
549
|
-
if (raw.event.detail.shape.polyline._attributes.fillColor) {
|
|
550
|
-
const fill = new Color(Number(raw.event.detail.shape.polyline._attributes.fillColor));
|
|
551
|
-
feat.properties['fill-opacity'] = fill.as_opacity() / 255;
|
|
552
|
-
feat.properties['fill'] = fill.as_hex();
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
if (raw.event.detail.shape.polyline._attributes.color) {
|
|
556
|
-
const stroke = new Color(Number(raw.event.detail.shape.polyline._attributes.color));
|
|
557
|
-
feat.properties.stroke = stroke.as_hex();
|
|
558
|
-
feat.properties['stroke-opacity'] = stroke.as_opacity() / 255;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
feat.properties.metadata = cot.metadata;
|
|
564
|
-
feat.path = cot.path;
|
|
565
|
-
|
|
566
|
-
return feat;
|
|
179
|
+
static async to_geojson(cot: CoT): Promise<Static<typeof Feature>> {
|
|
180
|
+
return await to_geojson(cot);
|
|
567
181
|
}
|
|
568
182
|
|
|
569
183
|
/**
|
|
570
184
|
* Parse an ATAK compliant Protobuf to a JS Object
|
|
571
185
|
*/
|
|
572
|
-
static from_proto(
|
|
186
|
+
static async from_proto(
|
|
573
187
|
raw: Uint8Array,
|
|
574
188
|
version = 1,
|
|
575
189
|
opts: CoTOptions = {}
|
|
576
|
-
): CoT {
|
|
190
|
+
): Promise<CoT> {
|
|
577
191
|
const ProtoMessage = RootMessage.lookupType(`atakmap.commoncommo.protobuf.v${version}.TakMessage`)
|
|
578
192
|
|
|
579
193
|
// TODO Type this
|
|
@@ -633,6 +247,13 @@ export class CoTParser {
|
|
|
633
247
|
return this.validate(cot);
|
|
634
248
|
}
|
|
635
249
|
|
|
250
|
+
static async normalize_geojson(
|
|
251
|
+
feature: Static<typeof GeoJSONFeature>
|
|
252
|
+
): Promise<Static<typeof Feature>> {
|
|
253
|
+
const feat = await normalize_geojson(feature);
|
|
254
|
+
return feat;
|
|
255
|
+
}
|
|
256
|
+
|
|
636
257
|
/**
|
|
637
258
|
* Return an CoT Message given a GeoJSON Feature
|
|
638
259
|
*
|
|
@@ -640,349 +261,12 @@ export class CoTParser {
|
|
|
640
261
|
*
|
|
641
262
|
* @return {CoT}
|
|
642
263
|
*/
|
|
643
|
-
static from_geojson(
|
|
264
|
+
static async from_geojson(
|
|
644
265
|
feature: Static<typeof InputFeature>,
|
|
645
266
|
opts: CoTOptions = {}
|
|
646
|
-
): CoT {
|
|
647
|
-
|
|
648
|
-
if (checkFeat.errors) throw new Err(400, null, `${checkFeat.errors[0].message} (${checkFeat.errors[0].instancePath})`);
|
|
649
|
-
|
|
650
|
-
const cot: Static<typeof JSONCoT> = {
|
|
651
|
-
event: {
|
|
652
|
-
_attributes: Util.cot_event_attr(
|
|
653
|
-
feature.properties.type || 'a-f-G',
|
|
654
|
-
feature.properties.how || 'm-g',
|
|
655
|
-
feature.properties.time,
|
|
656
|
-
feature.properties.start,
|
|
657
|
-
feature.properties.stale
|
|
658
|
-
),
|
|
659
|
-
point: Util.cot_point(),
|
|
660
|
-
detail: Util.cot_event_detail(feature.properties.callsign)
|
|
661
|
-
}
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
if (feature.id) cot.event._attributes.uid = String(feature.id);
|
|
665
|
-
if (feature.properties.callsign && !feature.id) cot.event._attributes.uid = feature.properties.callsign;
|
|
666
|
-
if (!cot.event.detail) cot.event.detail = {};
|
|
667
|
-
|
|
668
|
-
if (feature.properties.droid) {
|
|
669
|
-
cot.event.detail.uid = { _attributes: { Droid: feature.properties.droid } };
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
if (feature.properties.archived) {
|
|
673
|
-
cot.event.detail.archive = { _attributes: { } };
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
if (feature.properties.links) {
|
|
677
|
-
if (!cot.event.detail.link) cot.event.detail.link = [];
|
|
678
|
-
else if (!Array.isArray(cot.event.detail.link)) cot.event.detail.link = [cot.event.detail.link];
|
|
679
|
-
|
|
680
|
-
cot.event.detail.link.push(...feature.properties.links.map((link: Static<typeof LinkAttributes>) => {
|
|
681
|
-
return { _attributes: link };
|
|
682
|
-
}))
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
if (feature.properties.dest) {
|
|
686
|
-
const dest = !Array.isArray(feature.properties.dest) ? [ feature.properties.dest ] : feature.properties.dest;
|
|
687
|
-
|
|
688
|
-
cot.event.detail.marti = {
|
|
689
|
-
dest: dest.map((dest) => {
|
|
690
|
-
return { _attributes: { ...dest } };
|
|
691
|
-
})
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
if (feature.properties.type === 'b-a-o-tbl') {
|
|
696
|
-
cot.event.detail.emergency = {
|
|
697
|
-
_attributes: { type: '911 Alert' },
|
|
698
|
-
_text: feature.properties.callsign || 'UNKNOWN'
|
|
699
|
-
}
|
|
700
|
-
} else if (feature.properties.type === 'b-a-o-can') {
|
|
701
|
-
cot.event.detail.emergency = {
|
|
702
|
-
_attributes: { cancel: true },
|
|
703
|
-
_text: feature.properties.callsign || 'UNKNOWN'
|
|
704
|
-
}
|
|
705
|
-
} else if (feature.properties.type === 'b-a-g') {
|
|
706
|
-
cot.event.detail.emergency = {
|
|
707
|
-
_attributes: { type: 'Geo-fence Breached' },
|
|
708
|
-
_text: feature.properties.callsign || 'UNKNOWN'
|
|
709
|
-
}
|
|
710
|
-
} else if (feature.properties.type === 'b-a-o-pan') {
|
|
711
|
-
cot.event.detail.emergency = {
|
|
712
|
-
_attributes: { type: 'Ring The Bell' },
|
|
713
|
-
_text: feature.properties.callsign || 'UNKNOWN'
|
|
714
|
-
}
|
|
715
|
-
} else if (feature.properties.type === 'b-a-o-opn') {
|
|
716
|
-
cot.event.detail.emergency = {
|
|
717
|
-
_attributes: { type: 'Troops In Contact' },
|
|
718
|
-
_text: feature.properties.callsign || 'UNKNOWN'
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
if (feature.properties.takv) {
|
|
723
|
-
cot.event.detail.takv = { _attributes: { ...feature.properties.takv } };
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
if (feature.properties.creator) {
|
|
727
|
-
cot.event.detail.creator = { _attributes: { ...feature.properties.creator } };
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
if (feature.properties.range !== undefined) {
|
|
731
|
-
cot.event.detail.range = { _attributes: { value: feature.properties.range } }
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
if (feature.properties.bearing !== undefined) {
|
|
735
|
-
cot.event.detail.bearing = { _attributes: { value: feature.properties.bearing } }
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
if (feature.properties.geofence) {
|
|
739
|
-
cot.event.detail.__geofence = { _attributes: { ...feature.properties.geofence } };
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
if (feature.properties.milsym) {
|
|
743
|
-
cot.event.detail.__milsym = { _attributes: { id: feature.properties.milsym.id} };
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
if (feature.properties.sensor) {
|
|
747
|
-
cot.event.detail.sensor = { _attributes: { ...feature.properties.sensor } };
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
if (feature.properties.ackrequest) {
|
|
751
|
-
cot.event.detail.ackrequest = { _attributes: { ...feature.properties.ackrequest } };
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
if (feature.properties.video) {
|
|
755
|
-
if (feature.properties.video.connection) {
|
|
756
|
-
const video = JSON.parse(JSON.stringify(feature.properties.video));
|
|
757
|
-
|
|
758
|
-
const connection = video.connection;
|
|
759
|
-
delete video.connection;
|
|
760
|
-
|
|
761
|
-
cot.event.detail.__video = {
|
|
762
|
-
_attributes: { ...video },
|
|
763
|
-
ConnectionEntry: {
|
|
764
|
-
_attributes: connection
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
} else {
|
|
768
|
-
cot.event.detail.__video = { _attributes: { ...feature.properties.video } };
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
if (feature.properties.attachments) {
|
|
773
|
-
cot.event.detail.attachment_list = { _attributes: { hashes: JSON.stringify(feature.properties.attachments) } };
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
if (feature.properties.contact) {
|
|
777
|
-
cot.event.detail.contact = {
|
|
778
|
-
_attributes: {
|
|
779
|
-
...feature.properties.contact,
|
|
780
|
-
callsign: feature.properties.callsign || 'UNKNOWN',
|
|
781
|
-
}
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
if (feature.properties.fileshare) {
|
|
786
|
-
cot.event.detail.fileshare = { _attributes: { ...feature.properties.fileshare } };
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
if (feature.properties.course !== undefined || feature.properties.speed !== undefined || feature.properties.slope !== undefined) {
|
|
790
|
-
cot.event.detail.track = {
|
|
791
|
-
_attributes: Util.cot_track_attr(feature.properties.course, feature.properties.speed, feature.properties.slope)
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
if (feature.properties.group) {
|
|
796
|
-
cot.event.detail.__group = { _attributes: { ...feature.properties.group } }
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
if (feature.properties.flow) {
|
|
800
|
-
cot.event.detail['_flow-tags_'] = { _attributes: { ...feature.properties.flow } }
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
if (feature.properties.status) {
|
|
804
|
-
cot.event.detail.status = { _attributes: { ...feature.properties.status } }
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
if (feature.properties.precisionlocation) {
|
|
808
|
-
cot.event.detail.precisionlocation = { _attributes: { ...feature.properties.precisionlocation } }
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
if (feature.properties.icon) {
|
|
812
|
-
cot.event.detail.usericon = { _attributes: { iconsetpath: feature.properties.icon } }
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
if (feature.properties.mission) {
|
|
816
|
-
cot.event.detail.mission = {
|
|
817
|
-
_attributes: {
|
|
818
|
-
type: feature.properties.mission.type,
|
|
819
|
-
guid: feature.properties.mission.guid,
|
|
820
|
-
tool: feature.properties.mission.tool,
|
|
821
|
-
name: feature.properties.mission.name,
|
|
822
|
-
authorUid: feature.properties.mission.authorUid,
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
if (feature.properties.mission.missionLayer) {
|
|
827
|
-
cot.event.detail.mission.missionLayer = {};
|
|
828
|
-
|
|
829
|
-
if (feature.properties.mission.missionLayer.name) {
|
|
830
|
-
cot.event.detail.mission.missionLayer.name = { _text: feature.properties.mission.missionLayer.name };
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
if (feature.properties.mission.missionLayer.parentUid) {
|
|
834
|
-
cot.event.detail.mission.missionLayer.parentUid = { _text: feature.properties.mission.missionLayer.parentUid };
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
if (feature.properties.mission.missionLayer.type) {
|
|
838
|
-
cot.event.detail.mission.missionLayer.type = { _text: feature.properties.mission.missionLayer.type };
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
if (feature.properties.mission.missionLayer.uid) {
|
|
842
|
-
cot.event.detail.mission.missionLayer.uid = { _text: feature.properties.mission.missionLayer.uid };
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
cot.event.detail.remarks = { _attributes: { }, _text: feature.properties.remarks || '' };
|
|
848
|
-
|
|
849
|
-
if (!feature.geometry) {
|
|
850
|
-
throw new Err(400, null, 'Must have Geometry');
|
|
851
|
-
} else if (!['Point', 'Polygon', 'LineString'].includes(feature.geometry.type)) {
|
|
852
|
-
throw new Err(400, null, 'Unsupported Geometry Type');
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
// This isn't specific to point as the color can apply to the centroid point
|
|
856
|
-
if (feature.properties['marker-color']) {
|
|
857
|
-
const color = new Color(feature.properties['marker-color'] || -1761607936);
|
|
858
|
-
color.a = feature.properties['marker-opacity'] !== undefined ? feature.properties['marker-opacity'] * 255 : 128;
|
|
859
|
-
|
|
860
|
-
cot.event.detail.color = {
|
|
861
|
-
_attributes: {
|
|
862
|
-
argb: color.as_32bit(),
|
|
863
|
-
value: color.as_32bit()
|
|
864
|
-
}
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
if (feature.geometry.type === 'Point') {
|
|
869
|
-
cot.event.point._attributes.lon = feature.geometry.coordinates[0];
|
|
870
|
-
cot.event.point._attributes.lat = feature.geometry.coordinates[1];
|
|
871
|
-
cot.event.point._attributes.hae = feature.geometry.coordinates[2] || 0.0;
|
|
872
|
-
} else if (['Polygon', 'LineString'].includes(feature.geometry.type)) {
|
|
873
|
-
const stroke = new Color(feature.properties.stroke || -1761607936);
|
|
874
|
-
stroke.a = feature.properties['stroke-opacity'] !== undefined ? feature.properties['stroke-opacity'] * 255 : 128;
|
|
875
|
-
cot.event.detail.strokeColor = { _attributes: { value: stroke.as_32bit() } };
|
|
876
|
-
|
|
877
|
-
if (!feature.properties['stroke-width']) feature.properties['stroke-width'] = 3;
|
|
878
|
-
cot.event.detail.strokeWeight = { _attributes: {
|
|
879
|
-
value: feature.properties['stroke-width']
|
|
880
|
-
} };
|
|
881
|
-
|
|
882
|
-
if (!feature.properties['stroke-style']) feature.properties['stroke-style'] = 'solid';
|
|
883
|
-
cot.event.detail.strokeStyle = { _attributes: {
|
|
884
|
-
value: feature.properties['stroke-style']
|
|
885
|
-
} };
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
if (feature.geometry.type === 'Polygon' && feature.properties.type && ['u-d-c-c', 'u-r-b-c-c'].includes(feature.properties.type)) {
|
|
889
|
-
if (!feature.properties.shape || !feature.properties.shape.ellipse) {
|
|
890
|
-
throw new Err(400, null, `${feature.properties.type} (Circle) must define a feature.properties.shape.ellipse property`)
|
|
891
|
-
}
|
|
267
|
+
): Promise<CoT> {
|
|
268
|
+
const cot = await from_geojson(feature, opts);
|
|
892
269
|
|
|
893
|
-
|
|
894
|
-
} else if (feature.geometry.type === 'LineString' && feature.properties.type === 'b-m-r') {
|
|
895
|
-
cot.event._attributes.type = 'b-m-r';
|
|
896
|
-
|
|
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
|
-
}
|
|
902
|
-
|
|
903
|
-
cot.event.detail.__routeinfo = {
|
|
904
|
-
__navcues: {
|
|
905
|
-
__navcue: []
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
for (const coord of feature.geometry.coordinates) {
|
|
910
|
-
cot.event.detail.link.push({
|
|
911
|
-
_attributes: {
|
|
912
|
-
type: 'b-m-p-c',
|
|
913
|
-
uid: randomUUID(),
|
|
914
|
-
callsign: "",
|
|
915
|
-
point: `${coord[1]},${coord[0]}`
|
|
916
|
-
}
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
} else if (feature.geometry.type === 'LineString') {
|
|
920
|
-
cot.event._attributes.type = 'u-d-f';
|
|
921
|
-
|
|
922
|
-
if (!cot.event.detail.link) {
|
|
923
|
-
cot.event.detail.link = [];
|
|
924
|
-
} else if (!Array.isArray(cot.event.detail.link)) {
|
|
925
|
-
cot.event.detail.link = [cot.event.detail.link]
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
for (const coord of feature.geometry.coordinates) {
|
|
929
|
-
cot.event.detail.link.push({
|
|
930
|
-
_attributes: { point: `${coord[1]},${coord[0]}` }
|
|
931
|
-
});
|
|
932
|
-
}
|
|
933
|
-
} else if (feature.geometry.type === 'Polygon') {
|
|
934
|
-
cot.event._attributes.type = 'u-d-f';
|
|
935
|
-
|
|
936
|
-
if (!cot.event.detail.link) cot.event.detail.link = [];
|
|
937
|
-
else if (!Array.isArray(cot.event.detail.link)) cot.event.detail.link = [cot.event.detail.link]
|
|
938
|
-
|
|
939
|
-
// Inner rings are not yet supported
|
|
940
|
-
for (const coord of feature.geometry.coordinates[0]) {
|
|
941
|
-
cot.event.detail.link.push({
|
|
942
|
-
_attributes: { point: `${coord[1]},${coord[0]}` }
|
|
943
|
-
});
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
const fill = new Color(feature.properties.fill || -1761607936);
|
|
947
|
-
fill.a = feature.properties['fill-opacity'] !== undefined ? feature.properties['fill-opacity'] * 255 : 128;
|
|
948
|
-
cot.event.detail.fillColor = { _attributes: { value: fill.as_32bit() } };
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
if (feature.properties.labels) {
|
|
952
|
-
cot.event.detail.labels_on = { _attributes: { value: feature.properties.labels } };
|
|
953
|
-
} else {
|
|
954
|
-
cot.event.detail.labels_on = { _attributes: { value: false } };
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
cot.event.detail.tog = { _attributes: { enabled: '0' } };
|
|
958
|
-
|
|
959
|
-
if (feature.properties.center && Array.isArray(feature.properties.center) && feature.properties.center.length >= 2) {
|
|
960
|
-
cot.event.point._attributes.lon = feature.properties.center[0];
|
|
961
|
-
cot.event.point._attributes.lat = feature.properties.center[1];
|
|
962
|
-
|
|
963
|
-
if (feature.properties.center.length >= 3) {
|
|
964
|
-
cot.event.point._attributes.hae = feature.properties.center[2] || 0.0;
|
|
965
|
-
} else {
|
|
966
|
-
cot.event.point._attributes.hae = 0.0;
|
|
967
|
-
}
|
|
968
|
-
} else {
|
|
969
|
-
const centre = PointOnFeature(feature as AllGeoJSON);
|
|
970
|
-
cot.event.point._attributes.lon = centre.geometry.coordinates[0];
|
|
971
|
-
cot.event.point._attributes.lat = centre.geometry.coordinates[1];
|
|
972
|
-
cot.event.point._attributes.hae = 0.0;
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
const newcot = new CoT(cot, opts);
|
|
977
|
-
|
|
978
|
-
if (feature.properties.metadata) {
|
|
979
|
-
newcot.metadata = feature.properties.metadata
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
if (feature.path) {
|
|
983
|
-
newcot.path = feature.path
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
return this.validate(newcot);
|
|
270
|
+
return this.validate(cot);
|
|
987
271
|
}
|
|
988
272
|
}
|