@aeroswarm/codec 0.0.0-stage → 0.1.9

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.
Files changed (93) hide show
  1. package/README.md +182 -3
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +1249 -0
  5. package/dist/surveillance/adsb/index.d.ts +16 -0
  6. package/dist/surveillance/adsb/index.d.ts.map +1 -0
  7. package/dist/surveillance/aftn/index.d.ts +3 -0
  8. package/dist/surveillance/aftn/index.d.ts.map +1 -0
  9. package/dist/surveillance/aftn/parser.d.ts +16 -0
  10. package/dist/surveillance/aftn/parser.d.ts.map +1 -0
  11. package/dist/surveillance/aftn/parser.test.d.ts +2 -0
  12. package/dist/surveillance/aftn/parser.test.d.ts.map +1 -0
  13. package/dist/surveillance/aftn/types.d.ts +75 -0
  14. package/dist/surveillance/aftn/types.d.ts.map +1 -0
  15. package/dist/surveillance/ais/index.d.ts +17 -0
  16. package/dist/surveillance/ais/index.d.ts.map +1 -0
  17. package/dist/surveillance/asmgcs/index.d.ts +14 -0
  18. package/dist/surveillance/asmgcs/index.d.ts.map +1 -0
  19. package/dist/surveillance/asterix/cat10/index.d.ts +3 -0
  20. package/dist/surveillance/asterix/cat10/index.d.ts.map +1 -0
  21. package/dist/surveillance/asterix/cat21/index.d.ts +5 -0
  22. package/dist/surveillance/asterix/cat21/index.d.ts.map +1 -0
  23. package/dist/surveillance/asterix/cat48/index.d.ts +5 -0
  24. package/dist/surveillance/asterix/cat48/index.d.ts.map +1 -0
  25. package/dist/surveillance/asterix/cat62/index.d.ts +3 -0
  26. package/dist/surveillance/asterix/cat62/index.d.ts.map +1 -0
  27. package/dist/surveillance/asterix/decoder.d.ts +14 -0
  28. package/dist/surveillance/asterix/decoder.d.ts.map +1 -0
  29. package/dist/surveillance/asterix/fields.d.ts +4 -0
  30. package/dist/surveillance/asterix/fields.d.ts.map +1 -0
  31. package/dist/surveillance/asterix/index.d.ts +5 -0
  32. package/dist/surveillance/asterix/index.d.ts.map +1 -0
  33. package/dist/surveillance/asterix/parser.d.ts +16 -0
  34. package/dist/surveillance/asterix/parser.d.ts.map +1 -0
  35. package/dist/surveillance/asterix/parser.test.d.ts +2 -0
  36. package/dist/surveillance/asterix/parser.test.d.ts.map +1 -0
  37. package/dist/surveillance/asterix/resolvers.d.ts +17 -0
  38. package/dist/surveillance/asterix/resolvers.d.ts.map +1 -0
  39. package/dist/surveillance/asterix/types.d.ts +56 -0
  40. package/dist/surveillance/asterix/types.d.ts.map +1 -0
  41. package/dist/surveillance/asterix/values.d.ts +10 -0
  42. package/dist/surveillance/asterix/values.d.ts.map +1 -0
  43. package/dist/surveillance/asterix/xml-loader.d.ts +46 -0
  44. package/dist/surveillance/asterix/xml-loader.d.ts.map +1 -0
  45. package/dist/surveillance/index.d.ts +7 -0
  46. package/dist/surveillance/index.d.ts.map +1 -0
  47. package/dist/surveillance/mlat/index.d.ts +16 -0
  48. package/dist/surveillance/mlat/index.d.ts.map +1 -0
  49. package/package.json +40 -6
  50. package/src/config/asterix.dtd +78 -0
  51. package/src/config/asterix.ini +17 -0
  52. package/src/config/asterix_bds.xml +462 -0
  53. package/src/config/asterix_cat001_1_1.xml +872 -0
  54. package/src/config/asterix_cat001_1_2.xml +836 -0
  55. package/src/config/asterix_cat002_1_0.xml +258 -0
  56. package/src/config/asterix_cat004_1_12.xml +1713 -0
  57. package/src/config/asterix_cat008_1_0.xml +353 -0
  58. package/src/config/asterix_cat010_1_1.xml +791 -0
  59. package/src/config/asterix_cat011_1_2.xml +1691 -0
  60. package/src/config/asterix_cat019_1_2.xml +293 -0
  61. package/src/config/asterix_cat020_1_7.xml +1256 -0
  62. package/src/config/asterix_cat021_0_26.xml +902 -0
  63. package/src/config/asterix_cat021_1_4.xml +1594 -0
  64. package/src/config/asterix_cat021_1_8.xml +1594 -0
  65. package/src/config/asterix_cat021_2_4.xml +2191 -0
  66. package/src/config/asterix_cat023_1_2.xml +357 -0
  67. package/src/config/asterix_cat025_1_2.xml +306 -0
  68. package/src/config/asterix_cat030_6_2.xml +1387 -0
  69. package/src/config/asterix_cat031_6_2.xml +214 -0
  70. package/src/config/asterix_cat032_6_2.xml +465 -0
  71. package/src/config/asterix_cat032_7_0.xml +702 -0
  72. package/src/config/asterix_cat034_1_26.xml +586 -0
  73. package/src/config/asterix_cat034_1_27.xml +683 -0
  74. package/src/config/asterix_cat048_1_14.xml +1142 -0
  75. package/src/config/asterix_cat048_1_21.xml +1241 -0
  76. package/src/config/asterix_cat062_0_17.xml +1442 -0
  77. package/src/config/asterix_cat062_1_16.xml +3031 -0
  78. package/src/config/asterix_cat062_1_18.xml +3303 -0
  79. package/src/config/asterix_cat062_1_7.xml +2817 -0
  80. package/src/config/asterix_cat062_1_9.xml +737 -0
  81. package/src/config/asterix_cat063_1_3.xml +298 -0
  82. package/src/config/asterix_cat065_1_2.xml +168 -0
  83. package/src/config/asterix_cat065_1_3.xml +183 -0
  84. package/src/config/asterix_cat094_0_1.xml +1103 -0
  85. package/src/config/asterix_cat205_1_0.xml +385 -0
  86. package/src/config/asterix_cat240_1_3.xml +289 -0
  87. package/src/config/asterix_cat242_1_0.xml +644 -0
  88. package/src/config/asterix_cat247_1_2.xml +92 -0
  89. package/src/config/asterix_cat252_6_1.xml +1826 -0
  90. package/src/config/asterix_cat252_6_2.xml +1831 -0
  91. package/src/config/asterix_cat252_7_0.xml +1867 -0
  92. package/src/config/eurocontroll_version/asterix-spec.xsd +343 -0
  93. package/src/config/eurocontroll_version/asterix_cat094.xml +457 -0
package/dist/index.js ADDED
@@ -0,0 +1,1249 @@
1
+ // src/surveillance/asterix/xml-loader.ts
2
+ import { readFileSync, existsSync } from "fs";
3
+ import { fileURLToPath } from "url";
4
+ import { dirname, join } from "path";
5
+ var _here = dirname(fileURLToPath(import.meta.url));
6
+ var CONFIG_DIR = [
7
+ join(_here, "..", "src", "config"),
8
+ // bundled: <pkg>/dist → <pkg>/src/config
9
+ join(_here, "..", "..", "config")
10
+ // source: src/surveillance/asterix → src/config
11
+ ].find((p) => existsSync(p)) ?? join(_here, "..", "src", "config");
12
+ var FILE_MAP = {
13
+ "10:1.1": "asterix_cat010_1_1.xml",
14
+ "11:1.2": "asterix_cat011_1_2.xml",
15
+ "19:1.2": "asterix_cat019_1_2.xml",
16
+ "20:1.7": "asterix_cat020_1_7.xml",
17
+ "21:0.26": "asterix_cat021_0_26.xml",
18
+ "21:1.4": "asterix_cat021_1_4.xml",
19
+ "21:1.8": "asterix_cat021_1_8.xml",
20
+ "21:2.4": "asterix_cat021_2_4.xml",
21
+ "23:1.2": "asterix_cat023_1_2.xml",
22
+ "34:1.26": "asterix_cat034_1_26.xml",
23
+ "34:1.27": "asterix_cat034_1_27.xml",
24
+ "48:1.14": "asterix_cat048_1_14.xml",
25
+ "48:1.21": "asterix_cat048_1_21.xml",
26
+ "62:0.17": "asterix_cat062_0_17.xml",
27
+ "62:1.7": "asterix_cat062_1_7.xml",
28
+ "62:1.9": "asterix_cat062_1_9.xml",
29
+ "62:1.16": "asterix_cat062_1_16.xml",
30
+ "62:1.18": "asterix_cat062_1_18.xml",
31
+ "63:1.3": "asterix_cat063_1_3.xml",
32
+ "94:0.1": "asterix_cat094_0_1.xml",
33
+ "247:1.2": "asterix_cat247_1_2.xml"
34
+ };
35
+ var _cache = /* @__PURE__ */ new Map();
36
+ function loadDef(cat, ed) {
37
+ const key = `${cat}:${ed}`;
38
+ if (_cache.has(key)) return _cache.get(key) ?? null;
39
+ const filename = FILE_MAP[key];
40
+ if (!filename) {
41
+ _cache.set(key, null);
42
+ return null;
43
+ }
44
+ try {
45
+ const xml = readFileSync(join(CONFIG_DIR, filename), "utf8");
46
+ const def = { uap: buildUAP(xml), items: buildItems(xml) };
47
+ _cache.set(key, def);
48
+ return def;
49
+ } catch {
50
+ _cache.set(key, null);
51
+ return null;
52
+ }
53
+ }
54
+ function buildUAP(xml) {
55
+ const block = firstContent(xml, "UAP");
56
+ const entries = [];
57
+ const re = /<UAPItem\s+bit="(\d+)"\s+frn="([^"]+)"[^>]*>([^<]*)<\/UAPItem>/g;
58
+ let m;
59
+ while ((m = re.exec(block)) !== null) {
60
+ if (m[2] === "FX") continue;
61
+ const id = m[3].trim();
62
+ if (id === "-") continue;
63
+ entries.push({ bit: parseInt(m[1]), id });
64
+ }
65
+ return entries.sort((a, b) => a.bit - b.bit);
66
+ }
67
+ function buildItems(xml) {
68
+ const map = /* @__PURE__ */ new Map();
69
+ const re = /<DataItem\s+id="(\d+)"[^>]*>([\s\S]*?)<\/DataItem>/g;
70
+ let m;
71
+ while ((m = re.exec(xml)) !== null) {
72
+ const body = firstContent(m[2], "DataItemFormat") || m[2];
73
+ map.set(m[1], parseFormat(body));
74
+ }
75
+ if (!map.has("RE")) map.set("RE", { type: "explicit" });
76
+ if (!map.has("SP")) map.set("SP", { type: "explicit" });
77
+ return map;
78
+ }
79
+ function parseFormat(s) {
80
+ if (/<Compound[\s>]/.test(s)) return parseCompound(s);
81
+ if (/<Repetitive[\s>]/.test(s)) return parseRepetitive(s);
82
+ if (/<Variable[\s>]/.test(s)) return parseVariable(s);
83
+ if (/<Explicit[\s>]/.test(s)) return { type: "explicit" };
84
+ const m = s.match(/<Fixed\s+length="(\d+)"/);
85
+ return { type: "fixed", bytes: m ? parseInt(m[1]) : 0, bits: parseBits(firstContent(s, "Fixed")) };
86
+ }
87
+ function parseVariable(s) {
88
+ const inner = firstContent(s, "Variable");
89
+ const m = inner.match(/<Fixed\s+length="(\d+)"/);
90
+ return { type: "variable", chunkBytes: m ? parseInt(m[1]) : 1, bits: parseBits(firstContent(inner, "Fixed")) };
91
+ }
92
+ function parseBits(s) {
93
+ const out = [];
94
+ const re = /<Bits\s+([^>]*)>([\s\S]*?)<\/Bits>/g;
95
+ let m;
96
+ while ((m = re.exec(s)) !== null) {
97
+ const attrs = m[1];
98
+ const body = m[2];
99
+ const name = body.match(/<BitsShortName>([^<]*)<\/BitsShortName>/)?.[1]?.trim();
100
+ if (!name || name === "spare" || name === "-") continue;
101
+ let high, low;
102
+ const single = attrs.match(/\bbit="(\d+)"/);
103
+ if (single) {
104
+ high = low = parseInt(single[1]);
105
+ } else {
106
+ const from = attrs.match(/\bfrom="(\d+)"/);
107
+ const to = attrs.match(/\bto="(\d+)"/);
108
+ if (!from || !to) continue;
109
+ const a = parseInt(from[1]), b = parseInt(to[1]);
110
+ high = Math.max(a, b);
111
+ low = Math.min(a, b);
112
+ }
113
+ const encode = attrs.match(/\bencode="(\w+)"/)?.[1] ?? "unsigned";
114
+ const scale = parseFloat(body.match(/<BitsUnit[^>]*\bscale="([^"]+)"/)?.[1] ?? "1");
115
+ out.push({ name, high, low, encode, scale: Number.isFinite(scale) ? scale : 1 });
116
+ }
117
+ return out;
118
+ }
119
+ function parseRepetitive(s) {
120
+ const inner = firstContent(s, "Repetitive");
121
+ const repAttr = inner.match(/<Repetitive\s+rep="(\d+)"/);
122
+ const m = inner.match(/<Fixed\s+length="(\d+)"/);
123
+ return { type: "repetitive", repBytes: repAttr ? parseInt(repAttr[1]) : 1, itemBytes: m ? parseInt(m[1]) : 0 };
124
+ }
125
+ function parseCompound(s) {
126
+ const inner = firstContent(s, "Compound");
127
+ const varContent = firstContent(inner, "Variable");
128
+ const varFixed = varContent.match(/<Fixed\s+length="(\d+)"/);
129
+ const indicatorChunk = varFixed ? parseInt(varFixed[1]) : 1;
130
+ const varEnd = inner.indexOf("</Variable>");
131
+ const afterVar = varEnd >= 0 ? inner.slice(varEnd + "</Variable>".length) : "";
132
+ const subs = [];
133
+ const subRe = /<(Fixed|Variable|Repetitive|Explicit)\s/g;
134
+ let sm;
135
+ while ((sm = subRe.exec(afterVar)) !== null) {
136
+ const tag = sm[1];
137
+ const rest = afterVar.slice(sm.index);
138
+ if (tag === "Fixed") {
139
+ const lm = rest.match(/<Fixed\s+length="(\d+)"/);
140
+ if (lm) subs.push({ type: "fixed", bytes: parseInt(lm[1]) });
141
+ } else if (tag === "Variable") {
142
+ const lm = rest.match(/<Fixed\s+length="(\d+)"/);
143
+ subs.push({ type: "variable", chunkBytes: lm ? parseInt(lm[1]) : 1 });
144
+ } else if (tag === "Repetitive") {
145
+ const repAttr = rest.match(/<Repetitive\s+rep="(\d+)"/);
146
+ const lm = rest.match(/<Fixed\s+length="(\d+)"/);
147
+ subs.push({ type: "repetitive", repBytes: repAttr ? parseInt(repAttr[1]) : 1, itemBytes: lm ? parseInt(lm[1]) : 1 });
148
+ } else {
149
+ subs.push({ type: "explicit" });
150
+ }
151
+ }
152
+ return { type: "compound", indicatorChunk, subs };
153
+ }
154
+ function firstContent(s, tag) {
155
+ return s.match(new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`))?.[1] ?? "";
156
+ }
157
+
158
+ // src/surveillance/asterix/decoder.ts
159
+ function parseFspec(buf, start = 0) {
160
+ const bits = [];
161
+ let i = start;
162
+ while (i < buf.length) {
163
+ const b = buf[i];
164
+ for (let bit = 7; bit >= 1; bit--) bits.push((b >> bit & 1) === 1);
165
+ i++;
166
+ if ((b & 1) === 0) break;
167
+ }
168
+ return { bits, end: i };
169
+ }
170
+ function skipItem(buf, off, def) {
171
+ if (off >= buf.length) return buf.length;
172
+ switch (def.type) {
173
+ case "fixed":
174
+ return Math.min(off + def.bytes, buf.length);
175
+ case "variable": {
176
+ while (off + def.chunkBytes <= buf.length) {
177
+ const last = buf[off + def.chunkBytes - 1];
178
+ off += def.chunkBytes;
179
+ if ((last & 1) === 0) break;
180
+ }
181
+ return Math.min(off, buf.length);
182
+ }
183
+ case "repetitive": {
184
+ if (off + def.repBytes > buf.length) return buf.length;
185
+ let rep = 0;
186
+ for (let i = 0; i < def.repBytes; i++) rep = rep << 8 | buf[off + i];
187
+ return Math.min(off + def.repBytes + rep * def.itemBytes, buf.length);
188
+ }
189
+ case "compound": {
190
+ const presentIdxs = [];
191
+ let subBit = 0;
192
+ while (off < buf.length) {
193
+ const b = buf[off];
194
+ off++;
195
+ for (let bit = 7; bit >= 1; bit--) {
196
+ if (b >> bit & 1) presentIdxs.push(subBit);
197
+ subBit++;
198
+ }
199
+ if ((b & 1) === 0) break;
200
+ }
201
+ for (const idx of presentIdxs) {
202
+ if (idx >= def.subs.length) break;
203
+ off = skipItem(buf, off, def.subs[idx]);
204
+ }
205
+ return Math.min(off, buf.length);
206
+ }
207
+ case "explicit": {
208
+ if (off >= buf.length) return off;
209
+ const len = buf[off];
210
+ off++;
211
+ return Math.min(off + len - 1, buf.length);
212
+ }
213
+ }
214
+ }
215
+ function decodeBlock(block, def, onField) {
216
+ const { bits, end } = parseFspec(block);
217
+ let off = end;
218
+ const uapMap = new Map(def.uap.map((e) => [e.bit, e.id]));
219
+ for (let i = 0; i < bits.length; i++) {
220
+ if (!bits[i]) continue;
221
+ if (off >= block.length) break;
222
+ const uapPos = i + Math.floor(i / 7);
223
+ const id = uapMap.get(uapPos);
224
+ if (id === void 0) break;
225
+ const itemDef = def.items.get(id);
226
+ if (!itemDef) break;
227
+ const start = off;
228
+ off = skipItem(block, off, itemDef);
229
+ onField(id, block.slice(start, off), itemDef);
230
+ }
231
+ }
232
+
233
+ // src/surveillance/asterix/fields.ts
234
+ function readSigned(buf, offset, bytes) {
235
+ const raw = buf.readUIntBE(offset, bytes);
236
+ const threshold = 2 ** (bytes * 8 - 1);
237
+ return raw >= threshold ? raw - threshold * 2 : raw;
238
+ }
239
+ var ICAO6 = ` ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !"#$%&'()*+,-./0123456789:;<=>?`;
240
+ function readCallsign(data) {
241
+ const b = Array.from({ length: 6 }, (_, i) => data[i] ?? 0);
242
+ const chars = [
243
+ b[0] >> 2 & 63,
244
+ (b[0] & 3) << 4 | b[1] >> 4,
245
+ (b[1] & 15) << 2 | b[2] >> 6,
246
+ b[2] & 63,
247
+ b[3] >> 2 & 63,
248
+ (b[3] & 3) << 4 | b[4] >> 4,
249
+ (b[4] & 15) << 2 | b[5] >> 6,
250
+ b[5] & 63
251
+ ];
252
+ return chars.map((c) => ICAO6[c] ?? " ").join("").trim();
253
+ }
254
+ function num(raw, ...names) {
255
+ for (const n of names) {
256
+ const v = raw[n];
257
+ if (typeof v === "number") return v;
258
+ }
259
+ return void 0;
260
+ }
261
+ function str(raw, ...names) {
262
+ for (const n of names) {
263
+ const v = raw[n];
264
+ if (typeof v === "string") return v;
265
+ }
266
+ return void 0;
267
+ }
268
+ function validLatLon(lat, lon) {
269
+ return lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180;
270
+ }
271
+ function trdFlags(raw, out) {
272
+ if (num(raw, "TST") === 1) out.testTarget = true;
273
+ if (num(raw, "RAB") === 1) out.fixedTransponder = true;
274
+ if (num(raw, "SIM") === 1) out.simulated = true;
275
+ }
276
+ function decodeKnownField(cat, id, data, out, raw = {}) {
277
+ try {
278
+ if (cat === 21) cat021(id, data, out, raw);
279
+ else if (cat === 48) cat048(id, data, out, raw);
280
+ else if (cat === 23) cat023(id, data, out, raw);
281
+ else if (cat === 20) cat020(id, data, out, raw);
282
+ else if (cat === 10 || cat === 11) cat010(id, data, out, raw);
283
+ else if (cat === 62) cat062(id, data, out, raw);
284
+ else if (cat === 34 || cat === 63) catServiceMsg(id, data, out, raw);
285
+ else if (cat === 19 || cat === 247) catSystemStatus(id, data, out, raw);
286
+ } catch {
287
+ }
288
+ }
289
+ function catSystemStatus(id, d, out, raw) {
290
+ switch (id) {
291
+ case "010": {
292
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
293
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
294
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
295
+ break;
296
+ }
297
+ case "140": {
298
+ const t = num(raw, "ToD", "time_of_day");
299
+ if (t !== void 0) out.timeOfDay = t;
300
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
301
+ break;
302
+ }
303
+ }
304
+ }
305
+ function cat021(id, d, out, raw) {
306
+ switch (id) {
307
+ case "040":
308
+ trdFlags(raw, out);
309
+ break;
310
+ // I021/040 TRD
311
+ case "010": {
312
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
313
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
314
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
315
+ break;
316
+ }
317
+ case "130": {
318
+ const lat = num(raw, "Lat"), lon = num(raw, "Lon");
319
+ if (lat !== void 0 && lon !== void 0) {
320
+ if (validLatLon(lat, lon)) {
321
+ out.lat = lat;
322
+ out.lon = lon;
323
+ }
324
+ } else if (d.length === 6) {
325
+ const flat = readSigned(d, 0, 3) * (180 / 8388608);
326
+ const flon = readSigned(d, 3, 3) * (180 / 8388608);
327
+ if (validLatLon(flat, flon)) {
328
+ out.lat = flat;
329
+ out.lon = flon;
330
+ }
331
+ }
332
+ break;
333
+ }
334
+ case "080": {
335
+ const addr = str(raw, "TAddr");
336
+ if (addr) out.icao24 = addr.toLowerCase();
337
+ else if (d.length >= 3) out.icao24 = d.readUIntBE(0, 3).toString(16).padStart(6, "0");
338
+ break;
339
+ }
340
+ case "170": {
341
+ const cs = str(raw, "TId");
342
+ if (cs !== void 0) {
343
+ if (cs) out.callsign = cs;
344
+ } else if (d.length >= 6) out.callsign = readCallsign(d);
345
+ break;
346
+ }
347
+ case "140": {
348
+ const ft = num(raw, "GAlt", "geometric_height");
349
+ if (ft !== void 0) {
350
+ if (ft >= -2e3 && ft <= 1e5) out.altitudeFt = ft;
351
+ } else if (d.length >= 2) {
352
+ const f = readSigned(d, 0, 2) * 6.25;
353
+ if (f >= -2e3 && f <= 1e5) out.altitudeFt = f;
354
+ }
355
+ break;
356
+ }
357
+ case "145": {
358
+ if (out.altitudeFt != null) break;
359
+ const fl = num(raw, "FL");
360
+ if (fl !== void 0) out.altitudeFt = fl * 100;
361
+ else if (d.length >= 2) out.altitudeFt = readSigned(d, 0, 2) * 25;
362
+ break;
363
+ }
364
+ case "152": {
365
+ const hdg = num(raw, "MHdg");
366
+ if (hdg !== void 0) out.velocity = { ...out.velocity ?? {}, headingDeg: hdg };
367
+ else if (d.length >= 2) out.velocity = { ...out.velocity ?? {}, headingDeg: d.readUIntBE(0, 2) / 65536 * 360 };
368
+ break;
369
+ }
370
+ case "160": {
371
+ const gs = num(raw, "GS");
372
+ const ta = num(raw, "TA");
373
+ if (gs !== void 0 || ta !== void 0) {
374
+ const re = num(raw, "RE");
375
+ if (gs !== void 0 && re !== 1) {
376
+ out.velocity = { ...out.velocity ?? {}, groundSpeedKt: Math.round(gs * 3600) };
377
+ }
378
+ if (ta !== void 0 && out.velocity?.headingDeg == null) {
379
+ out.velocity = { ...out.velocity ?? {}, headingDeg: ta };
380
+ }
381
+ } else if (d.length >= 4) {
382
+ const spdRaw = d.readUIntBE(0, 2);
383
+ const hdgRaw = d.readUIntBE(2, 2);
384
+ if (!(spdRaw >> 14 & 1)) {
385
+ out.velocity = { ...out.velocity ?? {}, groundSpeedKt: Math.round((spdRaw & 16383) * (3600 / 16384)) };
386
+ }
387
+ if (out.velocity?.headingDeg == null) {
388
+ out.velocity = { ...out.velocity ?? {}, headingDeg: hdgRaw / 65536 * 360 };
389
+ }
390
+ }
391
+ break;
392
+ }
393
+ case "070": {
394
+ const sq = str(raw, "Mode3A");
395
+ if (sq) out.squawk = sq.padStart(4, "0").slice(-4);
396
+ else if (d.length >= 2) out.squawk = (d.readUIntBE(0, 2) & 4095).toString(8).padStart(4, "0");
397
+ break;
398
+ }
399
+ case "161": {
400
+ const tn = num(raw, "TrackN", "Tn");
401
+ if (tn !== void 0) out.trackNumber = tn;
402
+ else if (d.length >= 2) out.trackNumber = d.readUIntBE(0, 2) & 4095;
403
+ break;
404
+ }
405
+ case "030": {
406
+ const t = num(raw, "ToD");
407
+ if (t !== void 0) out.timeOfDay = t;
408
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
409
+ break;
410
+ }
411
+ }
412
+ }
413
+ function cat048(id, d, out, raw) {
414
+ switch (id) {
415
+ case "020":
416
+ trdFlags(raw, out);
417
+ break;
418
+ // I048/020 TRD
419
+ case "010": {
420
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
421
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
422
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
423
+ break;
424
+ }
425
+ case "220": {
426
+ const addr = str(raw, "ACAddr", "TAddr");
427
+ if (addr) out.icao24 = addr.toLowerCase();
428
+ else if (d.length >= 3) out.icao24 = d.readUIntBE(0, 3).toString(16).padStart(6, "0");
429
+ break;
430
+ }
431
+ case "240": {
432
+ const cs = str(raw, "TId");
433
+ if (cs !== void 0) {
434
+ if (cs) out.callsign = cs;
435
+ } else if (d.length >= 6) out.callsign = readCallsign(d);
436
+ break;
437
+ }
438
+ case "161": {
439
+ const tn = num(raw, "Tn");
440
+ if (tn !== void 0) out.trackNumber = tn;
441
+ else if (d.length >= 2) out.trackNumber = d.readUIntBE(0, 2) & 4095;
442
+ break;
443
+ }
444
+ case "042": {
445
+ const x = num(raw, "X"), y = num(raw, "Y");
446
+ if (x !== void 0 && y !== void 0) out.positionCartesian = { xNm: x, yNm: y };
447
+ else if (d.length >= 4) out.positionCartesian = { xNm: readSigned(d, 0, 2) / 128, yNm: readSigned(d, 2, 2) / 128 };
448
+ break;
449
+ }
450
+ case "040": {
451
+ const rho = num(raw, "RHO"), theta = num(raw, "THETA");
452
+ if (rho !== void 0 && theta !== void 0) out.measuredPosition = { rangeNm: rho, azimuthDeg: theta };
453
+ else if (d.length >= 4) {
454
+ out.measuredPosition = { rangeNm: d.readUIntBE(0, 2) / 256, azimuthDeg: d.readUIntBE(2, 2) / 65536 * 360 };
455
+ }
456
+ break;
457
+ }
458
+ case "200": {
459
+ const gs = num(raw, "CGS"), hdg = num(raw, "CHdg");
460
+ if (gs !== void 0 && hdg !== void 0) {
461
+ out.velocity = { groundSpeedKt: Math.round(gs * 3600), headingDeg: hdg };
462
+ } else if (d.length >= 4) {
463
+ out.velocity = {
464
+ groundSpeedKt: Math.round(d.readUIntBE(0, 2) * (3600 / 16384)),
465
+ headingDeg: d.readUIntBE(2, 2) / 65536 * 360
466
+ };
467
+ }
468
+ break;
469
+ }
470
+ case "070": {
471
+ const sq = str(raw, "Mode3A");
472
+ if (sq) out.squawk = sq.padStart(4, "0").slice(-4);
473
+ else if (d.length >= 2) out.squawk = (d.readUIntBE(0, 2) & 4095).toString(8).padStart(4, "0");
474
+ break;
475
+ }
476
+ case "090": {
477
+ if (out.altitudeFt != null) break;
478
+ const fl = num(raw, "FL");
479
+ if (fl !== void 0) out.altitudeFt = fl * 100;
480
+ else if (d.length >= 2) {
481
+ const rawFl = d.readUIntBE(0, 2) & 4095;
482
+ out.altitudeFt = (rawFl >= 2048 ? rawFl - 4096 : rawFl) * 25;
483
+ }
484
+ break;
485
+ }
486
+ case "140": {
487
+ const t = num(raw, "ToD");
488
+ if (t !== void 0) out.timeOfDay = t;
489
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
490
+ break;
491
+ }
492
+ }
493
+ }
494
+ function cat023(id, d, out, raw) {
495
+ switch (id) {
496
+ case "000": {
497
+ const rt = num(raw, "report_type", "ReportType");
498
+ if (rt !== void 0) out.reportType = rt;
499
+ else if (d.length >= 1) out.reportType = d[0];
500
+ break;
501
+ }
502
+ case "010": {
503
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
504
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
505
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
506
+ break;
507
+ }
508
+ case "070": {
509
+ const t = num(raw, "ToD");
510
+ if (t !== void 0) out.timeOfDay = t;
511
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
512
+ break;
513
+ }
514
+ case "100": {
515
+ const nogo = num(raw, "NOGO");
516
+ if (nogo !== void 0) {
517
+ out.gsStatus = {
518
+ nogo: nogo === 1,
519
+ odp: num(raw, "ODP") === 1,
520
+ oxt: num(raw, "OXT") === 1,
521
+ msc: num(raw, "MSC") === 1,
522
+ tsv: num(raw, "TSV") === 1,
523
+ spo: num(raw, "SPO") === 1
524
+ };
525
+ } else if (d.length >= 1) {
526
+ const b = d[0];
527
+ out.gsStatus = {
528
+ nogo: !!(b & 128),
529
+ odp: !!(b & 64),
530
+ oxt: !!(b & 32),
531
+ msc: !!(b & 16),
532
+ tsv: !!(b & 8),
533
+ spo: !!(b & 4)
534
+ };
535
+ }
536
+ break;
537
+ }
538
+ case "200": {
539
+ const r = num(raw, "range", "Range", "RANGE");
540
+ if (r !== void 0) out.operationalRangeNm = r;
541
+ else if (d.length >= 1) out.operationalRangeNm = d[0];
542
+ break;
543
+ }
544
+ }
545
+ }
546
+ function cat010(id, d, out, raw) {
547
+ switch (id) {
548
+ case "020":
549
+ trdFlags(raw, out);
550
+ break;
551
+ // I010/020 TRD
552
+ case "010": {
553
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
554
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
555
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
556
+ break;
557
+ }
558
+ case "041": {
559
+ const lat = num(raw, "Lat"), lon = num(raw, "Lon");
560
+ if (lat !== void 0 && lon !== void 0 && validLatLon(lat, lon)) {
561
+ out.lat = lat;
562
+ out.lon = lon;
563
+ }
564
+ break;
565
+ }
566
+ case "042": {
567
+ const x = num(raw, "X"), y = num(raw, "Y");
568
+ if (x !== void 0 && y !== void 0) {
569
+ out.positionCartesian = { xNm: x / 1852, yNm: y / 1852 };
570
+ }
571
+ break;
572
+ }
573
+ case "200": {
574
+ const gs = num(raw, "GS"), ta = num(raw, "TA");
575
+ if (gs !== void 0) out.velocity = { ...out.velocity ?? {}, groundSpeedKt: Math.round(gs * 3600) };
576
+ if (ta !== void 0) out.velocity = { ...out.velocity ?? {}, headingDeg: ta };
577
+ break;
578
+ }
579
+ case "220": {
580
+ const addr = str(raw, "TAddr");
581
+ if (addr) out.icao24 = addr.toLowerCase();
582
+ else if (d.length >= 3) out.icao24 = d.readUIntBE(0, 3).toString(16).padStart(6, "0");
583
+ break;
584
+ }
585
+ case "245": {
586
+ const cs = str(raw, "TId");
587
+ if (cs !== void 0) {
588
+ if (cs) out.callsign = cs;
589
+ } else if (d.length >= 7) out.callsign = readCallsign(d.subarray(1));
590
+ break;
591
+ }
592
+ case "060": {
593
+ const sq = str(raw, "Mode3A");
594
+ if (sq) out.squawk = sq.padStart(4, "0").slice(-4);
595
+ else if (d.length >= 2) out.squawk = (d.readUIntBE(0, 2) & 4095).toString(8).padStart(4, "0");
596
+ break;
597
+ }
598
+ case "161": {
599
+ const tn = num(raw, "TrkNb", "TRACK_NUMBER", "TrackNumber");
600
+ if (tn !== void 0) out.trackNumber = tn;
601
+ else if (d.length >= 2) out.trackNumber = d.readUIntBE(0, 2) & 4095;
602
+ break;
603
+ }
604
+ case "300": {
605
+ const vfi = num(raw, "VFI");
606
+ out.vehicleFleet = vfi ?? (d.length >= 1 ? d[0] : void 0);
607
+ out.targetType = "vehicle";
608
+ break;
609
+ }
610
+ case "140": {
611
+ const t = num(raw, "ToD");
612
+ if (t !== void 0) out.timeOfDay = t;
613
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
614
+ break;
615
+ }
616
+ }
617
+ }
618
+ var MS_TO_KT = 1 / 0.514444;
619
+ function cat062(id, d, out, raw) {
620
+ switch (id) {
621
+ case "080":
622
+ trdFlags(raw, out);
623
+ break;
624
+ // I062/080 TRD
625
+ case "010": {
626
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
627
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
628
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
629
+ break;
630
+ }
631
+ case "105": {
632
+ const lat = num(raw, "Lat"), lon = num(raw, "Lon");
633
+ if (lat !== void 0 && lon !== void 0 && validLatLon(lat, lon)) {
634
+ out.lat = lat;
635
+ out.lon = lon;
636
+ }
637
+ break;
638
+ }
639
+ case "185": {
640
+ const vx = num(raw, "Vx"), vy = num(raw, "Vy");
641
+ if (vx !== void 0 && vy !== void 0) {
642
+ const speedKt = Math.round(Math.sqrt(vx * vx + vy * vy) * MS_TO_KT);
643
+ const headingDeg = (Math.atan2(vx, vy) * 180 / Math.PI + 360) % 360;
644
+ out.velocity = { groundSpeedKt: speedKt, headingDeg };
645
+ }
646
+ break;
647
+ }
648
+ case "136": {
649
+ const ft = num(raw, "MFL");
650
+ if (ft !== void 0 && ft >= -2e3 && ft <= 15e4 && out.altitudeFt == null) out.altitudeFt = ft;
651
+ break;
652
+ }
653
+ case "060": {
654
+ const sq = str(raw, "Mode3A");
655
+ if (sq) out.squawk = sq.padStart(4, "0").slice(-4);
656
+ break;
657
+ }
658
+ case "040": {
659
+ const tn = num(raw, "TrkN", "TrkNb");
660
+ if (tn !== void 0) out.trackNumber = tn;
661
+ else if (d.length >= 2) out.trackNumber = d.readUIntBE(0, 2);
662
+ break;
663
+ }
664
+ case "380": {
665
+ let fsLen = 0;
666
+ while (fsLen < d.length) {
667
+ fsLen++;
668
+ if ((d[fsLen - 1] & 1) === 0) break;
669
+ }
670
+ if (fsLen < d.length && (d[0] & 128) !== 0 && d.length >= fsLen + 3) {
671
+ out.icao24 = d.readUIntBE(fsLen, 3).toString(16).padStart(6, "0");
672
+ }
673
+ break;
674
+ }
675
+ case "070": {
676
+ const t = num(raw, "ToD", "TOTI");
677
+ if (t !== void 0) out.timeOfDay = t;
678
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
679
+ break;
680
+ }
681
+ }
682
+ }
683
+ function catServiceMsg(id, d, out, raw) {
684
+ if (id === "010") {
685
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
686
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
687
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
688
+ } else if (id === "000" && d.length >= 1) {
689
+ out.reportType = d[0];
690
+ }
691
+ }
692
+ function cat020(id, d, out, raw) {
693
+ switch (id) {
694
+ case "020":
695
+ trdFlags(raw, out);
696
+ break;
697
+ // I020/020 TRD
698
+ case "010": {
699
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
700
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
701
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
702
+ break;
703
+ }
704
+ case "220": {
705
+ const addr = str(raw, "TAddr", "ACAddr");
706
+ if (addr) out.icao24 = addr.toLowerCase();
707
+ else if (d.length >= 3) out.icao24 = d.readUIntBE(0, 3).toString(16).padStart(6, "0");
708
+ break;
709
+ }
710
+ case "041": {
711
+ const lat = num(raw, "Lat"), lon = num(raw, "Lon");
712
+ if (lat !== void 0 && lon !== void 0) {
713
+ if (validLatLon(lat, lon)) {
714
+ out.lat = lat;
715
+ out.lon = lon;
716
+ }
717
+ } else if (d.length >= 8) {
718
+ const flat = readSigned(d, 0, 4) * (180 / 33554432);
719
+ const flon = readSigned(d, 4, 4) * (180 / 33554432);
720
+ if (validLatLon(flat, flon)) {
721
+ out.lat = flat;
722
+ out.lon = flon;
723
+ }
724
+ }
725
+ break;
726
+ }
727
+ case "042": {
728
+ const x = num(raw, "X"), y = num(raw, "Y");
729
+ if (x !== void 0 && y !== void 0) {
730
+ out.positionCartesian = { xNm: x / 1852, yNm: y / 1852 };
731
+ } else if (d.length >= 6) {
732
+ out.positionCartesian = {
733
+ xNm: readSigned(d, 0, 3) * 0.5 / 1852,
734
+ yNm: readSigned(d, 3, 3) * 0.5 / 1852
735
+ };
736
+ }
737
+ break;
738
+ }
739
+ case "161": {
740
+ const tn = num(raw, "TrkNb", "TRACK_NUMBER", "TrackNumber");
741
+ if (tn !== void 0) out.trackNumber = tn;
742
+ else if (d.length >= 2) out.trackNumber = d.readUIntBE(0, 2) & 4095;
743
+ break;
744
+ }
745
+ case "202": {
746
+ let vx = num(raw, "Vx", "X"), vy = num(raw, "Vy", "Y");
747
+ if (vx === void 0 || vy === void 0) {
748
+ if (d.length >= 4) {
749
+ vx = readSigned(d, 0, 2) * 0.25;
750
+ vy = readSigned(d, 2, 2) * 0.25;
751
+ }
752
+ }
753
+ if (vx !== void 0 && vy !== void 0) {
754
+ out.velocity = {
755
+ groundSpeedKt: Math.round(Math.hypot(vx, vy) * 1.94384),
756
+ headingDeg: (Math.atan2(vx, vy) * 180 / Math.PI + 360) % 360
757
+ };
758
+ }
759
+ break;
760
+ }
761
+ case "090": {
762
+ const fl = num(raw, "FL");
763
+ if (fl !== void 0) out.altitudeFt = fl * 100;
764
+ else if (d.length >= 2) out.altitudeFt = readSigned(d, 0, 2) / 4 * 100;
765
+ break;
766
+ }
767
+ case "070": {
768
+ const sq = str(raw, "Mode3A");
769
+ if (sq) out.squawk = sq.padStart(4, "0").slice(-4);
770
+ else if (d.length >= 2) out.squawk = (d.readUIntBE(0, 2) & 4095).toString(8).padStart(4, "0");
771
+ break;
772
+ }
773
+ case "245": {
774
+ const cs = str(raw, "TId");
775
+ if (cs !== void 0) {
776
+ if (cs) out.callsign = cs;
777
+ } else if (d.length >= 7) out.callsign = readCallsign(d.subarray(1));
778
+ break;
779
+ }
780
+ case "300": {
781
+ const vfi = num(raw, "VFI");
782
+ out.vehicleFleet = vfi ?? (d.length >= 1 ? d[0] : void 0);
783
+ out.targetType = "vehicle";
784
+ break;
785
+ }
786
+ case "140": {
787
+ const t = num(raw, "ToD");
788
+ if (t !== void 0) out.timeOfDay = t;
789
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
790
+ break;
791
+ }
792
+ }
793
+ }
794
+
795
+ // src/surveillance/asterix/values.ts
796
+ var ICAO62 = ` ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !"#$%&'()*+,-./0123456789:;<=>?`;
797
+ function extractBitFields(data, defs) {
798
+ const out = {};
799
+ if (data.length === 0 || defs.length === 0) return out;
800
+ let acc = 0n;
801
+ for (const b of data) acc = acc << 8n | BigInt(b);
802
+ const totalBits = data.length * 8;
803
+ for (const def of defs) {
804
+ if (def.high > totalBits || def.low < 1) continue;
805
+ const width = def.high - def.low + 1;
806
+ let raw = acc >> BigInt(def.low - 1) & (1n << BigInt(width)) - 1n;
807
+ switch (def.encode) {
808
+ case "signed": {
809
+ const sign = 1n << BigInt(width - 1);
810
+ if (raw & sign) raw -= sign << 1n;
811
+ out[def.name] = Number(raw) * def.scale;
812
+ break;
813
+ }
814
+ case "octal":
815
+ out[def.name] = Number(raw).toString(8).padStart(Math.ceil(width / 3), "0");
816
+ break;
817
+ case "hex":
818
+ out[def.name] = Number(raw).toString(16).padStart(Math.ceil(width / 4), "0");
819
+ break;
820
+ case "6bitschar": {
821
+ let s = "";
822
+ for (let shift = width - 6; shift >= 0; shift -= 6) {
823
+ s += ICAO62[Number(raw >> BigInt(shift) & 0x3fn)] ?? " ";
824
+ }
825
+ out[def.name] = s.trim();
826
+ break;
827
+ }
828
+ case "ascii": {
829
+ let s = "";
830
+ for (let shift = width - 8; shift >= 0; shift -= 8) {
831
+ s += String.fromCharCode(Number(raw >> BigInt(shift) & 0xffn));
832
+ }
833
+ out[def.name] = s.trim();
834
+ break;
835
+ }
836
+ default:
837
+ out[def.name] = Number(raw) * def.scale;
838
+ }
839
+ }
840
+ return out;
841
+ }
842
+
843
+ // src/surveillance/asterix/parser.ts
844
+ var EDITIONS = {
845
+ 10: ["1.1"],
846
+ 11: ["1.2"],
847
+ 19: ["1.2"],
848
+ 20: ["1.7"],
849
+ 21: ["2.4", "1.8", "1.4", "0.26"],
850
+ 23: ["1.2"],
851
+ 34: ["1.27", "1.26"],
852
+ 48: ["1.21", "1.14"],
853
+ 62: ["1.18", "1.16", "1.9", "1.7", "0.17"],
854
+ 63: ["1.3"],
855
+ 94: ["0.1"],
856
+ 247: ["1.2"]
857
+ };
858
+ function parseAsterix(buf, defaultEdition) {
859
+ const records = [];
860
+ let offset = 0;
861
+ while (offset + 3 <= buf.length) {
862
+ const cat = buf[offset];
863
+ const len = buf[offset + 1] << 8 | buf[offset + 2];
864
+ if (len < 3 || offset + len > buf.length) break;
865
+ const block = buf.slice(offset + 3, offset + len);
866
+ offset += len;
867
+ const toTry = defaultEdition?.[cat] ? [defaultEdition[cat]] : EDITIONS[cat] ?? [];
868
+ let best = null;
869
+ for (const edition of toTry) {
870
+ const def = loadDef(cat, edition);
871
+ if (!def) continue;
872
+ const fields = {};
873
+ try {
874
+ decodeBlock(block, def, (id, data, itemDef) => {
875
+ const bits = itemDef.type === "fixed" || itemDef.type === "variable" ? itemDef.bits : void 0;
876
+ const bitData = itemDef.type === "variable" ? data.subarray(0, itemDef.chunkBytes) : data;
877
+ const raw = bits?.length ? extractBitFields(bitData, bits) : {};
878
+ decodeKnownField(cat, id, data, fields, raw);
879
+ });
880
+ } catch {
881
+ continue;
882
+ }
883
+ if (!isPlausible(fields)) continue;
884
+ const score = plausibilityScore(fields);
885
+ if (!best || score > best.score) {
886
+ best = { edition, fields, score };
887
+ }
888
+ }
889
+ if (best) records.push({ category: cat, edition: best.edition, fields: best.fields });
890
+ }
891
+ return records;
892
+ }
893
+ function isPlausible(f) {
894
+ if (f.lat !== void 0 && (f.lat < -90 || f.lat > 90)) return false;
895
+ if (f.lon !== void 0 && (f.lon < -180 || f.lon > 180)) return false;
896
+ return true;
897
+ }
898
+ function plausibilityScore(f) {
899
+ let score = Object.keys(f).length;
900
+ const gs = f.velocity?.groundSpeedKt;
901
+ if (gs !== void 0) score += gs >= 0 && gs <= 1200 ? 2 : -4;
902
+ const hdg = f.velocity?.headingDeg;
903
+ if (hdg !== void 0) score += hdg >= 0 && hdg < 360 ? 1 : -4;
904
+ if (f.altitudeFt !== void 0) {
905
+ score += f.altitudeFt >= -2e3 && f.altitudeFt <= 6e4 ? 2 : -4;
906
+ }
907
+ if (f.callsign !== void 0) {
908
+ const cs = f.callsign.trim();
909
+ score += /^[A-Z0-9 ]+$/.test(cs) && cs.length >= 2 ? 2 : -4;
910
+ }
911
+ if (f.timeOfDay !== void 0) {
912
+ score += f.timeOfDay >= 0 && f.timeOfDay < 86400 ? 1 : -4;
913
+ }
914
+ return score;
915
+ }
916
+
917
+ // src/surveillance/asterix/resolvers.ts
918
+ function resolveAsterixPosition(fields, radarSite) {
919
+ if (typeof fields.lat === "number" && typeof fields.lon === "number") {
920
+ return { lat: fields.lat, lon: fields.lon };
921
+ }
922
+ if (!radarSite) return null;
923
+ const cart = fields.positionCartesian;
924
+ const polar = fields.measuredPosition;
925
+ let xNm;
926
+ let yNm;
927
+ if (typeof cart?.xNm === "number" && typeof cart?.yNm === "number") {
928
+ xNm = cart.xNm;
929
+ yNm = cart.yNm;
930
+ } else if (typeof polar?.rangeNm === "number" && typeof polar?.azimuthDeg === "number") {
931
+ const az = polar.azimuthDeg * Math.PI / 180;
932
+ xNm = polar.rangeNm * Math.sin(az);
933
+ yNm = polar.rangeNm * Math.cos(az);
934
+ }
935
+ if (xNm == null || yNm == null) return null;
936
+ return {
937
+ lat: radarSite.lat + yNm / 60,
938
+ lon: radarSite.lon + xNm / (60 * Math.cos(radarSite.lat * Math.PI / 180))
939
+ };
940
+ }
941
+ function resolveAsterixAltitudeFt(fields) {
942
+ return typeof fields.altitudeFt === "number" ? fields.altitudeFt : void 0;
943
+ }
944
+
945
+ // src/surveillance/adsb/index.ts
946
+ function decodeAdsbFrame(_frame) {
947
+ return null;
948
+ }
949
+
950
+ // src/surveillance/ais/index.ts
951
+ function decodeAis(_sentence) {
952
+ return null;
953
+ }
954
+
955
+ // src/surveillance/mlat/index.ts
956
+ function decodeMlatFrame(_frame) {
957
+ return null;
958
+ }
959
+
960
+ // src/surveillance/asmgcs/index.ts
961
+ function decodeAsmgcsFrame(_frame) {
962
+ return null;
963
+ }
964
+
965
+ // src/surveillance/aftn/parser.ts
966
+ var PRIORITY_LINE = /^(SS|DD|FF|GG|KK)\s+(.+)$/;
967
+ var ORIGIN_LINE = /^(\d{6})\s+([A-Z]{8})\b/;
968
+ var ADDRESSEES_ONLY = /^(?:[A-Z]{8}\s*)+$/;
969
+ var AERODROME_TIME = /^([A-Z]{4})(\d{4})?/;
970
+ var ITEM18_ENTRY = /([A-Z]{2,5})\/(.*?)(?=\s+[A-Z]{2,5}\/|\s*$)/gs;
971
+ function normalise(input) {
972
+ const text = typeof input === "string" ? input : input.toString("latin1");
973
+ return text.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "").replace(/\r\n?/g, "\n").replace(/^\s*ZCZC[^\n]*\n/m, "").replace(/\s*NNNN\s*$/, "");
974
+ }
975
+ function parseHeader(headerText) {
976
+ const header = { addressees: [] };
977
+ const lines = headerText.split("\n").map((l) => l.trim()).filter(Boolean);
978
+ let inAddressees = false;
979
+ for (const line of lines) {
980
+ const origin = ORIGIN_LINE.exec(line);
981
+ if (origin) {
982
+ header.originTime = origin[1];
983
+ header.originator = origin[2];
984
+ inAddressees = false;
985
+ continue;
986
+ }
987
+ const prio = PRIORITY_LINE.exec(line);
988
+ if (prio && !header.priority) {
989
+ header.priority = prio[1];
990
+ header.addressees.push(...prio[2].match(/[A-Z]{8}/g) ?? []);
991
+ inAddressees = true;
992
+ continue;
993
+ }
994
+ if (inAddressees && ADDRESSEES_ONLY.test(line)) {
995
+ header.addressees.push(...line.match(/[A-Z]{8}/g) ?? []);
996
+ }
997
+ }
998
+ return header;
999
+ }
1000
+ function parseItem18(raw) {
1001
+ const out = {};
1002
+ const text = raw.trim();
1003
+ if (!text || text === "0") return out;
1004
+ for (const m of text.matchAll(ITEM18_ENTRY)) {
1005
+ const key = m[1];
1006
+ const value = m[2].trim();
1007
+ if (value && out[key] === void 0) out[key] = value;
1008
+ }
1009
+ return out;
1010
+ }
1011
+ function splitCallsign(token) {
1012
+ const cs = token?.split("/")[0]?.trim().toUpperCase();
1013
+ return cs || void 0;
1014
+ }
1015
+ function applyAerodromeTime(token, onAerodrome, onTime) {
1016
+ const m = token ? AERODROME_TIME.exec(token.trim().toUpperCase()) : null;
1017
+ if (!m) return;
1018
+ onAerodrome(m[1]);
1019
+ if (m[2] && onTime) onTime(m[2]);
1020
+ }
1021
+ function applyItem18(msg, entries) {
1022
+ Object.assign(msg.other, entries);
1023
+ if (entries["REG"]) msg.registration = entries["REG"].replace(/[^A-Z0-9-]/gi, "").toUpperCase() || void 0;
1024
+ if (entries["DOF"] && /^\d{6}$/.test(entries["DOF"])) msg.dof = entries["DOF"];
1025
+ const code = entries["CODE"]?.trim();
1026
+ if (code && /^[0-9A-Fa-f]{6}$/.test(code)) msg.icao24 = code.toLowerCase();
1027
+ }
1028
+ var SPEED_LEVEL_ROUTE = /^([NMK]\d{3,4})(F\d{3}|A\d{3}|S\d{4}|M\d{4}|VFR)?\s*(.*)$/;
1029
+ var TYPE_WAKE = /^\d{0,2}([A-Z0-9]{2,4})\/([LMHJ])$/;
1030
+ function applyRoute(msg, text) {
1031
+ const route = text?.trim().toUpperCase();
1032
+ if (!route) return;
1033
+ const m = SPEED_LEVEL_ROUTE.exec(route);
1034
+ if (!m) {
1035
+ msg.route = route;
1036
+ return;
1037
+ }
1038
+ msg.cruiseSpeed = m[1];
1039
+ if (m[2]) msg.cruiseLevel = m[2];
1040
+ if (m[3]) msg.route = m[3].trim();
1041
+ }
1042
+ function applyAircraft(msg, text) {
1043
+ const aircraft = text?.trim().toUpperCase();
1044
+ if (!aircraft) return;
1045
+ const m = TYPE_WAKE.exec(aircraft);
1046
+ if (m) {
1047
+ msg.aircraftType = m[1];
1048
+ msg.wake = m[2];
1049
+ } else msg.aircraftType = aircraft;
1050
+ }
1051
+ function parseFpl(parts, msg) {
1052
+ msg.callsign = splitCallsign(parts[1]);
1053
+ const rulesType = parts[2]?.trim().toUpperCase();
1054
+ if (rulesType) {
1055
+ msg.flightRules = rulesType[0];
1056
+ if (rulesType[1]) msg.flightType = rulesType[1];
1057
+ }
1058
+ applyAircraft(msg, parts[3]);
1059
+ if (parts[4]) msg.equipment = parts[4].trim();
1060
+ applyAerodromeTime(parts[5], (a) => {
1061
+ msg.adep = a;
1062
+ }, (t) => {
1063
+ msg.eobt = t;
1064
+ });
1065
+ applyRoute(msg, parts[6]);
1066
+ const dest = parts[7]?.trim().toUpperCase();
1067
+ if (dest) {
1068
+ const m = /^([A-Z]{4})(\d{4})?(?:\s+([A-Z]{4}))?(?:\s+([A-Z]{4}))?/.exec(dest);
1069
+ if (m) {
1070
+ msg.ades = m[1];
1071
+ if (m[2]) msg.eet = m[2];
1072
+ const alts = [m[3], m[4]].filter((x) => !!x);
1073
+ if (alts.length) msg.alternates = alts;
1074
+ }
1075
+ }
1076
+ applyItem18(msg, parseItem18(parts.slice(8).join("-")));
1077
+ }
1078
+ function parseChg(parts, msg) {
1079
+ msg.callsign = splitCallsign(parts[1]);
1080
+ applyAerodromeTime(parts[2], (a) => {
1081
+ msg.adep = a;
1082
+ }, (t) => {
1083
+ msg.eobt = t;
1084
+ });
1085
+ applyAerodromeTime(parts[3], (a) => {
1086
+ msg.ades = a;
1087
+ });
1088
+ const changes = {};
1089
+ const item18Chunks = [];
1090
+ for (const part of parts.slice(4)) {
1091
+ const numbered = /^(\d{1,2})\/(.*)$/.exec(part.trim());
1092
+ if (numbered) changes[numbered[1]] = numbered[2].trim();
1093
+ else item18Chunks.push(part);
1094
+ }
1095
+ if (Object.keys(changes).length) {
1096
+ msg.changes = changes;
1097
+ const f13 = changes["13"];
1098
+ if (f13) applyAerodromeTime(f13, (a) => {
1099
+ msg.adep = a;
1100
+ }, (t) => {
1101
+ msg.eobt = t;
1102
+ });
1103
+ applyRoute(msg, changes["15"]);
1104
+ const f16 = changes["16"]?.toUpperCase();
1105
+ if (f16) applyAerodromeTime(f16, (a) => {
1106
+ msg.ades = a;
1107
+ }, (t) => {
1108
+ msg.eet = t;
1109
+ });
1110
+ applyAircraft(msg, changes["9"]);
1111
+ const f18 = changes["18"];
1112
+ if (f18) applyItem18(msg, parseItem18(f18));
1113
+ }
1114
+ if (item18Chunks.length) applyItem18(msg, parseItem18(item18Chunks.join(" ")));
1115
+ }
1116
+ function parseMovement(parts, msg) {
1117
+ msg.callsign = splitCallsign(parts[1]);
1118
+ const type = msg.type;
1119
+ if (type === "DEP") {
1120
+ applyAerodromeTime(parts[2], (a) => {
1121
+ msg.adep = a;
1122
+ }, (t) => {
1123
+ msg.atd = t;
1124
+ });
1125
+ applyAerodromeTime(parts[3], (a) => {
1126
+ msg.ades = a;
1127
+ });
1128
+ } else if (type === "ARR") {
1129
+ applyAerodromeTime(parts[2], (a) => {
1130
+ msg.adep = a;
1131
+ });
1132
+ applyAerodromeTime(parts[3], (a) => {
1133
+ msg.ades = a;
1134
+ }, (t) => {
1135
+ msg.ata = t;
1136
+ });
1137
+ } else if (type === "DLA") {
1138
+ applyAerodromeTime(parts[2], (a) => {
1139
+ msg.adep = a;
1140
+ }, (t) => {
1141
+ msg.eobt = t;
1142
+ });
1143
+ applyAerodromeTime(parts[3], (a) => {
1144
+ msg.ades = a;
1145
+ });
1146
+ } else {
1147
+ applyAerodromeTime(parts[2], (a) => {
1148
+ msg.adep = a;
1149
+ });
1150
+ applyAerodromeTime(parts[3], (a) => {
1151
+ msg.ades = a;
1152
+ });
1153
+ }
1154
+ applyItem18(msg, parseItem18(parts.slice(4).join(" ")));
1155
+ }
1156
+ function resolveBaseDay(msg, now) {
1157
+ if (msg.dof) {
1158
+ const yy = Number(msg.dof.slice(0, 2));
1159
+ return { y: 2e3 + yy, m: Number(msg.dof.slice(2, 4)) - 1, d: Number(msg.dof.slice(4, 6)) };
1160
+ }
1161
+ const origin = msg.header.originTime;
1162
+ if (!origin) return null;
1163
+ const day = Number(origin.slice(0, 2));
1164
+ const ref = new Date(now);
1165
+ let best = null;
1166
+ let bestDiff = Infinity;
1167
+ for (const offset of [-1, 0, 1]) {
1168
+ const probe = new Date(Date.UTC(ref.getUTCFullYear(), ref.getUTCMonth() + offset, day));
1169
+ if (probe.getUTCDate() !== day) continue;
1170
+ const diff = Math.abs(probe.getTime() - now);
1171
+ if (diff < bestDiff) {
1172
+ bestDiff = diff;
1173
+ best = { y: probe.getUTCFullYear(), m: probe.getUTCMonth(), d: day };
1174
+ }
1175
+ }
1176
+ return best;
1177
+ }
1178
+ function toEpoch(base, hhmm) {
1179
+ if (!hhmm || !/^\d{4}$/.test(hhmm)) return void 0;
1180
+ const hh = Number(hhmm.slice(0, 2));
1181
+ const mm = Number(hhmm.slice(2, 4));
1182
+ if (hh > 23 || mm > 59) return void 0;
1183
+ return Date.UTC(base.y, base.m, base.d, hh, mm);
1184
+ }
1185
+ function parseAftn(input, options = {}) {
1186
+ const s = normalise(input);
1187
+ const open = s.indexOf("(");
1188
+ const close = s.lastIndexOf(")");
1189
+ if (open < 0 || close <= open) return null;
1190
+ const text = s.slice(open, close + 1).replace(/\s*\n\s*/g, " ").trim();
1191
+ const inner = text.slice(1, -1);
1192
+ const parts = inner.split("-").map((p) => p.trim());
1193
+ const type = parts[0];
1194
+ if (!type || !/^[A-Z]{3}$/.test(type)) return null;
1195
+ const msg = {
1196
+ type,
1197
+ header: parseHeader(s.slice(0, open)),
1198
+ text,
1199
+ other: {}
1200
+ };
1201
+ switch (type) {
1202
+ case "FPL":
1203
+ parseFpl(parts, msg);
1204
+ break;
1205
+ case "CHG":
1206
+ parseChg(parts, msg);
1207
+ break;
1208
+ case "DEP":
1209
+ case "ARR":
1210
+ case "DLA":
1211
+ case "CNL":
1212
+ parseMovement(parts, msg);
1213
+ break;
1214
+ default:
1215
+ msg.callsign = splitCallsign(parts[1]);
1216
+ }
1217
+ const base = resolveBaseDay(msg, options.now ?? Date.now());
1218
+ if (base) {
1219
+ msg.eobtAt = toEpoch(base, msg.eobt);
1220
+ msg.atdAt = toEpoch(base, msg.atd);
1221
+ msg.ataAt = toEpoch(base, msg.ata);
1222
+ }
1223
+ return msg;
1224
+ }
1225
+ function splitAftnStream(buffer) {
1226
+ const messages = [];
1227
+ let rest = buffer;
1228
+ const terminator = /NNNN|\x03/;
1229
+ for (; ; ) {
1230
+ const m = terminator.exec(rest);
1231
+ if (!m) break;
1232
+ const end = m.index + m[0].length;
1233
+ const chunk = rest.slice(0, end);
1234
+ if (chunk.includes("(")) messages.push(chunk);
1235
+ rest = rest.slice(end);
1236
+ }
1237
+ return { messages, rest };
1238
+ }
1239
+ export {
1240
+ decodeAdsbFrame,
1241
+ decodeAis,
1242
+ decodeAsmgcsFrame,
1243
+ decodeMlatFrame,
1244
+ parseAftn,
1245
+ parseAsterix,
1246
+ resolveAsterixAltitudeFt,
1247
+ resolveAsterixPosition,
1248
+ splitAftnStream
1249
+ };