Skip to content

Commit ee21b9b

Browse files
authored
Merge pull request #70 from dfpc-coe/creator-milsym
Creator & MilSym Support
2 parents 751bbf1 + 169a471 commit ee21b9b

7 files changed

Lines changed: 428 additions & 210 deletions

File tree

lib/cot.ts

Lines changed: 107 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
MartiDestAttributes,
1717
Link,
1818
LinkAttributes,
19+
CreatorAttributes,
1920
VideoAttributes,
2021
SensorAttributes,
2122
VideoConnectionEntryAttributes,
@@ -77,7 +78,17 @@ export default class CoT {
7778
// Does the CoT belong to a folder - defaults to "/"
7879
path: string;
7980

80-
constructor(cot: Buffer | Static<typeof JSONCoT> | object | string) {
81+
constructor(
82+
cot: Buffer | Static<typeof JSONCoT> | object | string,
83+
opts: {
84+
creator?: CoT | {
85+
uid: string,
86+
type: string,
87+
callsign: string,
88+
time?: Date | string,
89+
}
90+
} = {}
91+
) {
8192
if (typeof cot === 'string' || cot instanceof Buffer) {
8293
const raw = xmljs.xml2js(String(cot), { compact: true });
8394
this.raw = raw as Static<typeof JSONCoT>;
@@ -102,6 +113,15 @@ export default class CoT {
102113
if (this.raw.event.detail.archive && Object.keys(this.raw.event.detail.archive).length === 0) {
103114
this.raw.event.detail.archive = { _attributes: {} };
104115
}
116+
117+
if (opts.creator) {
118+
this.creator({
119+
uid: opts.creator instanceof CoT ? opts.creator.uid() : opts.creator.uid,
120+
type: opts.creator instanceof CoT ? opts.creator.type() : opts.creator.type,
121+
callsign: opts.creator instanceof CoT ? opts.creator.callsign() : opts.creator.callsign,
122+
time: opts.creator instanceof CoT ? new Date() : opts.creator.time
123+
});
124+
}
105125
}
106126

107127
/**
@@ -150,6 +170,14 @@ export default class CoT {
150170
return diffs.length > 0;
151171
}
152172

173+
/**
174+
* Returns or sets the UID of the CoT
175+
*/
176+
uid(uid?: string): string {
177+
if (uid) this.raw.event._attributes.uid = uid;
178+
return this.raw.event._attributes.uid;
179+
}
180+
153181
/**
154182
* Returns or sets the Callsign of the CoT
155183
*/
@@ -163,48 +191,51 @@ export default class CoT {
163191

164192
/**
165193
* Returns or sets the Callsign of the CoT
194+
*
195+
* @param callsign - Optional Callsign to set
166196
*/
167197
callsign(callsign?: string): string {
168-
if (!this.raw.event.detail) this.raw.event.detail = {};
198+
const detail = this.detail();
169199

170-
if (callsign && !this.raw.event.detail.contact) {
171-
this.raw.event.detail.contact = { _attributes: { callsign } };
172-
} else if (callsign && this.raw.event.detail.contact) {
173-
this.raw.event.detail.contact._attributes.callsign = callsign;
200+
if (callsign && !detail.contact) {
201+
detail.contact = { _attributes: { callsign } };
202+
} else if (callsign && detail.contact) {
203+
detail.contact._attributes.callsign = callsign;
174204
}
175205

176-
if (this.raw.event.detail.contact && this.raw.event.detail.contact._attributes && typeof this.raw.event.detail.contact._attributes.callsign === 'string') {
177-
return this.raw.event.detail.contact._attributes.callsign;
206+
if (detail.contact && detail.contact._attributes && typeof detail.contact._attributes.callsign === 'string') {
207+
return detail.contact._attributes.callsign;
178208
} else {
179209
return 'UNKNOWN'
180210
}
181211
}
182212

183213
/**
184-
* Returns or sets the UID of the CoT
214+
* Return Detail Object of CoT or create one if it doesn't yet exist and pass a reference
185215
*/
186-
uid(uid?: string): string {
187-
if (uid) this.raw.event._attributes.uid = uid;
188-
return this.raw.event._attributes.uid;
216+
detail(): Static<typeof Detail> {
217+
if (!this.raw.event.detail) this.raw.event.detail = {};
218+
return this.raw.event.detail;
189219
}
190220

191221
/**
192222
* Add a given Dest tag to a CoT
193223
*/
194224
addDest(dest: Static<typeof MartiDestAttributes>): CoT {
195-
if (!this.raw.event.detail) this.raw.event.detail = {};
196-
if (!this.raw.event.detail.marti) this.raw.event.detail.marti = {};
225+
const detail = this.detail();
226+
227+
if (!detail.marti) detail.marti = {};
197228

198229
let destArr: Array<Static<typeof MartiDest>> = [];
199-
if (this.raw.event.detail.marti.dest && !Array.isArray(this.raw.event.detail.marti.dest)) {
200-
destArr = [this.raw.event.detail.marti.dest]
201-
} else if (this.raw.event.detail.marti.dest && Array.isArray(this.raw.event.detail.marti.dest)) {
202-
destArr = this.raw.event.detail.marti.dest;
230+
if (detail.marti.dest && !Array.isArray(detail.marti.dest)) {
231+
destArr = [detail.marti.dest]
232+
} else if (detail.marti.dest && Array.isArray(detail.marti.dest)) {
233+
destArr = detail.marti.dest;
203234
}
204235

205236
destArr.push({ _attributes: dest });
206237

207-
this.raw.event.detail.marti.dest = destArr;
238+
detail.marti.dest = destArr;
208239

209240
return this;
210241
}
@@ -213,8 +244,8 @@ export default class CoT {
213244
video: Static<typeof VideoAttributes>,
214245
connection?: Static<typeof VideoConnectionEntryAttributes>
215246
): CoT {
216-
if (!this.raw.event.detail) this.raw.event.detail = {};
217-
if (this.raw.event.detail.__video) throw new Err(400, null, 'A video stream already exists on this CoT');
247+
const detail = this.detail();
248+
if (detail.__video) throw new Err(400, null, 'A video stream already exists on this CoT');
218249

219250
if (!video.url) throw new Err(400, null, 'A Video URL must be provided');
220251

@@ -226,16 +257,16 @@ export default class CoT {
226257
video.uid = crypto.randomUUID();
227258
}
228259

229-
this.raw.event.detail.__video = {
260+
detail.__video = {
230261
_attributes: video
231262
};
232263

233264
if (connection) {
234-
this.raw.event.detail.__video.ConnectionEntry = {
265+
detail.__video.ConnectionEntry = {
235266
_attributes: connection
236267
}
237268
} else {
238-
this.raw.event.detail.__video.ConnectionEntry = {
269+
detail.__video.ConnectionEntry = {
239270
_attributes: {
240271
uid: video.uid,
241272
networkTimeout: 12000,
@@ -268,37 +299,71 @@ export default class CoT {
268299
}
269300

270301
sensor(sensor?: Static<typeof SensorAttributes>): Static<typeof Polygon> | null {
271-
if (!this.raw.event.detail) this.raw.event.detail = {};
302+
const detail = this.detail();
272303

273304
if (sensor) {
274-
this.raw.event.detail.sensor = {
305+
detail.sensor = {
275306
_attributes: sensor
276307
}
277308
}
278309

279-
if (!this.raw.event.detail.sensor || !this.raw.event.detail.sensor._attributes) {
310+
if (!detail.sensor || !detail.sensor._attributes) {
280311
return null;
281312
}
282313

283314
return new Sensor(
284315
this.position(),
285-
this.raw.event.detail.sensor._attributes
316+
detail.sensor._attributes
286317
).to_geojson();
287318
};
288319

320+
creator(
321+
creator?: {
322+
uid: string,
323+
type: string,
324+
callsign: string,
325+
time: Date | string | undefined,
326+
}
327+
): Static<typeof CreatorAttributes> | undefined {
328+
const detail = this.detail();
329+
330+
if (creator) {
331+
this.addLink({
332+
uid: creator.uid,
333+
production_time: creator.time ? new Date(creator.time).toISOString() : new Date().toISOString(),
334+
type: creator.type,
335+
parent_callsign: creator.callsign,
336+
relation: 'p-p'
337+
});
338+
339+
detail.creator = {
340+
_attributes: {
341+
...creator,
342+
time: creator.time ? new Date(creator.time).toISOString() : new Date().toISOString(),
343+
}
344+
};
345+
}
346+
347+
if (detail.creator) {
348+
return detail.creator._attributes;
349+
} else {
350+
return;
351+
}
352+
}
353+
289354
addLink(link: Static<typeof LinkAttributes>): CoT {
290-
if (!this.raw.event.detail) this.raw.event.detail = {};
355+
const detail = this.detail();
291356

292357
let linkArr: Array<Static<typeof Link>> = [];
293-
if (this.raw.event.detail.link && !Array.isArray(this.raw.event.detail.link)) {
294-
linkArr = [this.raw.event.detail.link]
295-
} else if (this.raw.event.detail.link && Array.isArray(this.raw.event.detail.link)) {
296-
linkArr = this.raw.event.detail.link;
358+
if (detail.link && !Array.isArray(detail.link)) {
359+
linkArr = [detail.link]
360+
} else if (detail.link && Array.isArray(detail.link)) {
361+
linkArr = detail.link;
297362
}
298363

299364
linkArr.push({ _attributes: link });
300365

301-
this.raw.event.detail.link = linkArr;
366+
detail.link = linkArr;
302367

303368
return this;
304369
}
@@ -375,6 +440,10 @@ export default class CoT {
375440
feat.properties.contact = contact;
376441
}
377442

443+
if (this.creator()) {
444+
feat.properties.creator = this.creator();
445+
}
446+
378447
if (raw.event.detail.remarks && raw.event.detail.remarks._text) {
379448
feat.properties.remarks = raw.event.detail.remarks._text;
380449
}
@@ -978,6 +1047,10 @@ export default class CoT {
9781047
cot.event.detail.takv = { _attributes: { ...feature.properties.takv } };
9791048
}
9801049

1050+
if (feature.properties.creator) {
1051+
cot.event.detail.creator = { _attributes: { ...feature.properties.creator } };
1052+
}
1053+
9811054
if (feature.properties.geofence) {
9821055
cot.event.detail.__geofence = { _attributes: { ...feature.properties.geofence } };
9831056
}

lib/types/feature.ts

Lines changed: 9 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Type } from '@sinclair/typebox';
22
import {
33
GroupAttributes,
4+
CreatorAttributes,
45
ACKRequestAttributes,
56
ShapeEllipseAttributes,
67
GeofenceAttributes,
@@ -55,65 +56,6 @@ export const FeaturePropertyMission = Type.Object({
5556
missionChanges: Type.Optional(Type.Array(FeaturePropertyMissionChange))
5657
});
5758

58-
export const InputProperties = Type.Object({
59-
callsign: Type.Optional(Type.String({ default: 'UNKNOWN' })),
60-
type: Type.Optional(Type.String({ default: 'a-f-G' })),
61-
how: Type.Optional(Type.String()),
62-
time: Type.Optional(Type.Union([Type.String()])),
63-
start: Type.Optional(Type.Union([Type.String()])),
64-
stale: Type.Optional(Type.Union([Type.Integer(), Type.String()])),
65-
center: Type.Optional(Position),
66-
course: Type.Optional(Type.Number()),
67-
slope: Type.Optional(Type.Number()),
68-
speed: Type.Optional(Type.Number()),
69-
'marker-color': Type.Optional(Type.String()),
70-
'marker-opacity': Type.Optional(Type.Number({ minimum: 0, maximum: 1 })),
71-
'stroke': Type.Optional(Type.String()),
72-
'stroke-opacity': Type.Optional(Type.Number({ minimum: 0, maximum: 1 })),
73-
'stroke-width': Type.Optional(Type.Number()),
74-
'stroke-style': Type.Optional(Type.String()),
75-
'fill': Type.Optional(Type.String()),
76-
'fill-opacity': Type.Optional(Type.Number({ minimum: 0, maximum: 1 })),
77-
metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
78-
archived: Type.Optional(Type.Boolean()),
79-
geofence: Type.Optional(GeofenceAttributes),
80-
contact: Type.Optional(ContactAttributes),
81-
shape: Type.Optional(Type.Object({
82-
ellipse: Type.Optional(ShapeEllipseAttributes)
83-
})),
84-
remarks: Type.Optional(Type.String()),
85-
mission: Type.Optional(FeaturePropertyMission),
86-
fileshare: Type.Optional(FileShareAttributes),
87-
ackrequest: Type.Optional(ACKRequestAttributes),
88-
attachments: Type.Optional(Type.Array(Type.String())),
89-
sensor: Type.Optional(SensorAttributes),
90-
video: Type.Optional(Type.Composite([
91-
VideoAttributes,
92-
Type.Optional(Type.Object({
93-
connection: Type.Optional(VideoConnectionEntryAttributes)
94-
}))
95-
])),
96-
links: Type.Optional(Type.Array(LinkAttributes)),
97-
chat: Type.Optional(Type.Object({
98-
parent: Type.Optional(Type.String()),
99-
groupOwner: Type.Optional(Type.String()),
100-
messageId: Type.Optional(Type.String()),
101-
chatroom: Type.String(),
102-
id: Type.String(),
103-
senderCallsign: Type.String(),
104-
chatgrp: Type.Any()
105-
})),
106-
track: Type.Optional(TrackAttributes),
107-
dest: Type.Optional(Type.Union([MartiDestAttributes, Type.Array(MartiDestAttributes)])),
108-
icon: Type.Optional(Type.String()),
109-
droid: Type.Optional(Type.String()),
110-
takv: Type.Optional(TakVersionAttributes),
111-
group: Type.Optional(GroupAttributes),
112-
status: Type.Optional(StatusAttributes),
113-
precisionlocation: Type.Optional(PrecisionLocationAttributes),
114-
flow: Type.Optional(Type.Record(Type.String(), Type.String())),
115-
})
116-
11759
export const Properties = Type.Object({
11860
callsign: Type.String({ default: 'UNKNOWN' }),
11961
type: Type.String({ default: 'a-f-G' }),
@@ -122,6 +64,7 @@ export const Properties = Type.Object({
12264
start: Type.String(),
12365
stale: Type.String(),
12466
center: Position,
67+
creator: Type.Optional(CreatorAttributes),
12568
course: Type.Optional(Type.Number()),
12669
slope: Type.Optional(Type.Number()),
12770
speed: Type.Optional(Type.Number()),
@@ -217,6 +160,13 @@ export const Feature = Type.Object({
217160
geometry: Geometry
218161
})
219162

163+
export const InputProperties = Type.Partial(Type.Composite([
164+
Type.Omit(Properties, ['stale']),
165+
Type.Object({
166+
stale: Type.Union([Type.Integer(), Type.String()])
167+
})
168+
]))
169+
220170
export const InputFeature = Type.Object({
221171
id: Type.Optional(Type.String()),
222172
type: Type.Const('Feature'),

0 commit comments

Comments
 (0)