@aeroswarm/codec 0.0.0-stage → 0.1.8

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 (85) hide show
  1. package/README.md +157 -3
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +924 -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/ais/index.d.ts +17 -0
  8. package/dist/surveillance/ais/index.d.ts.map +1 -0
  9. package/dist/surveillance/asmgcs/index.d.ts +14 -0
  10. package/dist/surveillance/asmgcs/index.d.ts.map +1 -0
  11. package/dist/surveillance/asterix/cat10/index.d.ts +3 -0
  12. package/dist/surveillance/asterix/cat10/index.d.ts.map +1 -0
  13. package/dist/surveillance/asterix/cat21/index.d.ts +5 -0
  14. package/dist/surveillance/asterix/cat21/index.d.ts.map +1 -0
  15. package/dist/surveillance/asterix/cat48/index.d.ts +5 -0
  16. package/dist/surveillance/asterix/cat48/index.d.ts.map +1 -0
  17. package/dist/surveillance/asterix/cat62/index.d.ts +3 -0
  18. package/dist/surveillance/asterix/cat62/index.d.ts.map +1 -0
  19. package/dist/surveillance/asterix/decoder.d.ts +14 -0
  20. package/dist/surveillance/asterix/decoder.d.ts.map +1 -0
  21. package/dist/surveillance/asterix/fields.d.ts +4 -0
  22. package/dist/surveillance/asterix/fields.d.ts.map +1 -0
  23. package/dist/surveillance/asterix/index.d.ts +5 -0
  24. package/dist/surveillance/asterix/index.d.ts.map +1 -0
  25. package/dist/surveillance/asterix/parser.d.ts +16 -0
  26. package/dist/surveillance/asterix/parser.d.ts.map +1 -0
  27. package/dist/surveillance/asterix/parser.test.d.ts +2 -0
  28. package/dist/surveillance/asterix/parser.test.d.ts.map +1 -0
  29. package/dist/surveillance/asterix/resolvers.d.ts +17 -0
  30. package/dist/surveillance/asterix/resolvers.d.ts.map +1 -0
  31. package/dist/surveillance/asterix/types.d.ts +50 -0
  32. package/dist/surveillance/asterix/types.d.ts.map +1 -0
  33. package/dist/surveillance/asterix/values.d.ts +10 -0
  34. package/dist/surveillance/asterix/values.d.ts.map +1 -0
  35. package/dist/surveillance/asterix/xml-loader.d.ts +46 -0
  36. package/dist/surveillance/asterix/xml-loader.d.ts.map +1 -0
  37. package/dist/surveillance/index.d.ts +6 -0
  38. package/dist/surveillance/index.d.ts.map +1 -0
  39. package/dist/surveillance/mlat/index.d.ts +16 -0
  40. package/dist/surveillance/mlat/index.d.ts.map +1 -0
  41. package/package.json +38 -4
  42. package/src/config/asterix.dtd +78 -0
  43. package/src/config/asterix.ini +17 -0
  44. package/src/config/asterix_bds.xml +462 -0
  45. package/src/config/asterix_cat001_1_1.xml +872 -0
  46. package/src/config/asterix_cat001_1_2.xml +836 -0
  47. package/src/config/asterix_cat002_1_0.xml +258 -0
  48. package/src/config/asterix_cat004_1_12.xml +1713 -0
  49. package/src/config/asterix_cat008_1_0.xml +353 -0
  50. package/src/config/asterix_cat010_1_1.xml +791 -0
  51. package/src/config/asterix_cat011_1_2.xml +1691 -0
  52. package/src/config/asterix_cat019_1_2.xml +293 -0
  53. package/src/config/asterix_cat020_1_7.xml +1256 -0
  54. package/src/config/asterix_cat021_0_26.xml +902 -0
  55. package/src/config/asterix_cat021_1_4.xml +1594 -0
  56. package/src/config/asterix_cat021_1_8.xml +1594 -0
  57. package/src/config/asterix_cat021_2_4.xml +2191 -0
  58. package/src/config/asterix_cat023_1_2.xml +357 -0
  59. package/src/config/asterix_cat025_1_2.xml +306 -0
  60. package/src/config/asterix_cat030_6_2.xml +1387 -0
  61. package/src/config/asterix_cat031_6_2.xml +214 -0
  62. package/src/config/asterix_cat032_6_2.xml +465 -0
  63. package/src/config/asterix_cat032_7_0.xml +702 -0
  64. package/src/config/asterix_cat034_1_26.xml +586 -0
  65. package/src/config/asterix_cat034_1_27.xml +683 -0
  66. package/src/config/asterix_cat048_1_14.xml +1142 -0
  67. package/src/config/asterix_cat048_1_21.xml +1241 -0
  68. package/src/config/asterix_cat062_0_17.xml +1442 -0
  69. package/src/config/asterix_cat062_1_16.xml +3031 -0
  70. package/src/config/asterix_cat062_1_18.xml +3303 -0
  71. package/src/config/asterix_cat062_1_7.xml +2817 -0
  72. package/src/config/asterix_cat062_1_9.xml +737 -0
  73. package/src/config/asterix_cat063_1_3.xml +298 -0
  74. package/src/config/asterix_cat065_1_2.xml +168 -0
  75. package/src/config/asterix_cat065_1_3.xml +183 -0
  76. package/src/config/asterix_cat094_0_1.xml +1103 -0
  77. package/src/config/asterix_cat205_1_0.xml +385 -0
  78. package/src/config/asterix_cat240_1_3.xml +289 -0
  79. package/src/config/asterix_cat242_1_0.xml +644 -0
  80. package/src/config/asterix_cat247_1_2.xml +92 -0
  81. package/src/config/asterix_cat252_6_1.xml +1826 -0
  82. package/src/config/asterix_cat252_6_2.xml +1831 -0
  83. package/src/config/asterix_cat252_7_0.xml +1867 -0
  84. package/src/config/eurocontroll_version/asterix-spec.xsd +343 -0
  85. package/src/config/eurocontroll_version/asterix_cat094.xml +457 -0
package/dist/index.js ADDED
@@ -0,0 +1,924 @@
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 decodeKnownField(cat, id, data, out, raw = {}) {
272
+ try {
273
+ if (cat === 21) cat021(id, data, out, raw);
274
+ else if (cat === 48) cat048(id, data, out, raw);
275
+ else if (cat === 23) cat023(id, data, out, raw);
276
+ else if (cat === 20) cat020(id, data, out, raw);
277
+ else if (cat === 10 || cat === 11) cat010(id, data, out, raw);
278
+ else if (cat === 62) cat062(id, data, out, raw);
279
+ else if (cat === 34 || cat === 63) catServiceMsg(id, data, out, raw);
280
+ else if (cat === 19 || cat === 247) catSystemStatus(id, data, out, raw);
281
+ } catch {
282
+ }
283
+ }
284
+ function catSystemStatus(id, d, out, raw) {
285
+ switch (id) {
286
+ case "010": {
287
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
288
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
289
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
290
+ break;
291
+ }
292
+ case "140": {
293
+ const t = num(raw, "ToD", "time_of_day");
294
+ if (t !== void 0) out.timeOfDay = t;
295
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
296
+ break;
297
+ }
298
+ }
299
+ }
300
+ function cat021(id, d, out, raw) {
301
+ switch (id) {
302
+ case "010": {
303
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
304
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
305
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
306
+ break;
307
+ }
308
+ case "130": {
309
+ const lat = num(raw, "Lat"), lon = num(raw, "Lon");
310
+ if (lat !== void 0 && lon !== void 0) {
311
+ if (validLatLon(lat, lon)) {
312
+ out.lat = lat;
313
+ out.lon = lon;
314
+ }
315
+ } else if (d.length === 6) {
316
+ const flat = readSigned(d, 0, 3) * (180 / 8388608);
317
+ const flon = readSigned(d, 3, 3) * (180 / 8388608);
318
+ if (validLatLon(flat, flon)) {
319
+ out.lat = flat;
320
+ out.lon = flon;
321
+ }
322
+ }
323
+ break;
324
+ }
325
+ case "080": {
326
+ const addr = str(raw, "TAddr");
327
+ if (addr) out.icao24 = addr.toLowerCase();
328
+ else if (d.length >= 3) out.icao24 = d.readUIntBE(0, 3).toString(16).padStart(6, "0");
329
+ break;
330
+ }
331
+ case "170": {
332
+ const cs = str(raw, "TId");
333
+ if (cs !== void 0) {
334
+ if (cs) out.callsign = cs;
335
+ } else if (d.length >= 6) out.callsign = readCallsign(d);
336
+ break;
337
+ }
338
+ case "140": {
339
+ const ft = num(raw, "GAlt", "geometric_height");
340
+ if (ft !== void 0) {
341
+ if (ft >= -2e3 && ft <= 1e5) out.altitudeFt = ft;
342
+ } else if (d.length >= 2) {
343
+ const f = readSigned(d, 0, 2) * 6.25;
344
+ if (f >= -2e3 && f <= 1e5) out.altitudeFt = f;
345
+ }
346
+ break;
347
+ }
348
+ case "145": {
349
+ if (out.altitudeFt != null) break;
350
+ const fl = num(raw, "FL");
351
+ if (fl !== void 0) out.altitudeFt = fl * 100;
352
+ else if (d.length >= 2) out.altitudeFt = readSigned(d, 0, 2) * 25;
353
+ break;
354
+ }
355
+ case "152": {
356
+ const hdg = num(raw, "MHdg");
357
+ if (hdg !== void 0) out.velocity = { ...out.velocity ?? {}, headingDeg: hdg };
358
+ else if (d.length >= 2) out.velocity = { ...out.velocity ?? {}, headingDeg: d.readUIntBE(0, 2) / 65536 * 360 };
359
+ break;
360
+ }
361
+ case "160": {
362
+ const gs = num(raw, "GS");
363
+ const ta = num(raw, "TA");
364
+ if (gs !== void 0 || ta !== void 0) {
365
+ const re = num(raw, "RE");
366
+ if (gs !== void 0 && re !== 1) {
367
+ out.velocity = { ...out.velocity ?? {}, groundSpeedKt: Math.round(gs * 3600) };
368
+ }
369
+ if (ta !== void 0 && out.velocity?.headingDeg == null) {
370
+ out.velocity = { ...out.velocity ?? {}, headingDeg: ta };
371
+ }
372
+ } else if (d.length >= 4) {
373
+ const spdRaw = d.readUIntBE(0, 2);
374
+ const hdgRaw = d.readUIntBE(2, 2);
375
+ if (!(spdRaw >> 14 & 1)) {
376
+ out.velocity = { ...out.velocity ?? {}, groundSpeedKt: Math.round((spdRaw & 16383) * (3600 / 16384)) };
377
+ }
378
+ if (out.velocity?.headingDeg == null) {
379
+ out.velocity = { ...out.velocity ?? {}, headingDeg: hdgRaw / 65536 * 360 };
380
+ }
381
+ }
382
+ break;
383
+ }
384
+ case "070": {
385
+ const sq = str(raw, "Mode3A");
386
+ if (sq) out.squawk = sq.padStart(4, "0").slice(-4);
387
+ else if (d.length >= 2) out.squawk = (d.readUIntBE(0, 2) & 4095).toString(8).padStart(4, "0");
388
+ break;
389
+ }
390
+ case "030": {
391
+ const t = num(raw, "ToD");
392
+ if (t !== void 0) out.timeOfDay = t;
393
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
394
+ break;
395
+ }
396
+ }
397
+ }
398
+ function cat048(id, d, out, raw) {
399
+ switch (id) {
400
+ case "010": {
401
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
402
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
403
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
404
+ break;
405
+ }
406
+ case "220": {
407
+ const addr = str(raw, "ACAddr", "TAddr");
408
+ if (addr) out.icao24 = addr.toLowerCase();
409
+ else if (d.length >= 3) out.icao24 = d.readUIntBE(0, 3).toString(16).padStart(6, "0");
410
+ break;
411
+ }
412
+ case "240": {
413
+ const cs = str(raw, "TId");
414
+ if (cs !== void 0) {
415
+ if (cs) out.callsign = cs;
416
+ } else if (d.length >= 6) out.callsign = readCallsign(d);
417
+ break;
418
+ }
419
+ case "042": {
420
+ const x = num(raw, "X"), y = num(raw, "Y");
421
+ if (x !== void 0 && y !== void 0) out.positionCartesian = { xNm: x, yNm: y };
422
+ else if (d.length >= 4) out.positionCartesian = { xNm: readSigned(d, 0, 2) / 128, yNm: readSigned(d, 2, 2) / 128 };
423
+ break;
424
+ }
425
+ case "040": {
426
+ const rho = num(raw, "RHO"), theta = num(raw, "THETA");
427
+ if (rho !== void 0 && theta !== void 0) out.measuredPosition = { rangeNm: rho, azimuthDeg: theta };
428
+ else if (d.length >= 4) {
429
+ out.measuredPosition = { rangeNm: d.readUIntBE(0, 2) / 256, azimuthDeg: d.readUIntBE(2, 2) / 65536 * 360 };
430
+ }
431
+ break;
432
+ }
433
+ case "200": {
434
+ const gs = num(raw, "CGS"), hdg = num(raw, "CHdg");
435
+ if (gs !== void 0 && hdg !== void 0) {
436
+ out.velocity = { groundSpeedKt: Math.round(gs * 3600), headingDeg: hdg };
437
+ } else if (d.length >= 4) {
438
+ out.velocity = {
439
+ groundSpeedKt: Math.round(d.readUIntBE(0, 2) * (3600 / 16384)),
440
+ headingDeg: d.readUIntBE(2, 2) / 65536 * 360
441
+ };
442
+ }
443
+ break;
444
+ }
445
+ case "070": {
446
+ const sq = str(raw, "Mode3A");
447
+ if (sq) out.squawk = sq.padStart(4, "0").slice(-4);
448
+ else if (d.length >= 2) out.squawk = (d.readUIntBE(0, 2) & 4095).toString(8).padStart(4, "0");
449
+ break;
450
+ }
451
+ case "090": {
452
+ if (out.altitudeFt != null) break;
453
+ const fl = num(raw, "FL");
454
+ if (fl !== void 0) out.altitudeFt = fl * 100;
455
+ else if (d.length >= 2) {
456
+ const rawFl = d.readUIntBE(0, 2) & 4095;
457
+ out.altitudeFt = (rawFl >= 2048 ? rawFl - 4096 : rawFl) * 25;
458
+ }
459
+ break;
460
+ }
461
+ case "140": {
462
+ const t = num(raw, "ToD");
463
+ if (t !== void 0) out.timeOfDay = t;
464
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
465
+ break;
466
+ }
467
+ }
468
+ }
469
+ function cat023(id, d, out, raw) {
470
+ switch (id) {
471
+ case "000": {
472
+ const rt = num(raw, "report_type", "ReportType");
473
+ if (rt !== void 0) out.reportType = rt;
474
+ else if (d.length >= 1) out.reportType = d[0];
475
+ break;
476
+ }
477
+ case "010": {
478
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
479
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
480
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
481
+ break;
482
+ }
483
+ case "070": {
484
+ const t = num(raw, "ToD");
485
+ if (t !== void 0) out.timeOfDay = t;
486
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
487
+ break;
488
+ }
489
+ case "100": {
490
+ const nogo = num(raw, "NOGO");
491
+ if (nogo !== void 0) {
492
+ out.gsStatus = {
493
+ nogo: nogo === 1,
494
+ odp: num(raw, "ODP") === 1,
495
+ oxt: num(raw, "OXT") === 1,
496
+ msc: num(raw, "MSC") === 1,
497
+ tsv: num(raw, "TSV") === 1,
498
+ spo: num(raw, "SPO") === 1
499
+ };
500
+ } else if (d.length >= 1) {
501
+ const b = d[0];
502
+ out.gsStatus = {
503
+ nogo: !!(b & 128),
504
+ odp: !!(b & 64),
505
+ oxt: !!(b & 32),
506
+ msc: !!(b & 16),
507
+ tsv: !!(b & 8),
508
+ spo: !!(b & 4)
509
+ };
510
+ }
511
+ break;
512
+ }
513
+ case "200": {
514
+ const r = num(raw, "range", "Range", "RANGE");
515
+ if (r !== void 0) out.operationalRangeNm = r;
516
+ else if (d.length >= 1) out.operationalRangeNm = d[0];
517
+ break;
518
+ }
519
+ }
520
+ }
521
+ function cat010(id, d, out, raw) {
522
+ switch (id) {
523
+ case "010": {
524
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
525
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
526
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
527
+ break;
528
+ }
529
+ case "041": {
530
+ const lat = num(raw, "Lat"), lon = num(raw, "Lon");
531
+ if (lat !== void 0 && lon !== void 0 && validLatLon(lat, lon)) {
532
+ out.lat = lat;
533
+ out.lon = lon;
534
+ }
535
+ break;
536
+ }
537
+ case "042": {
538
+ const x = num(raw, "X"), y = num(raw, "Y");
539
+ if (x !== void 0 && y !== void 0) {
540
+ out.positionCartesian = { xNm: x / 1852, yNm: y / 1852 };
541
+ }
542
+ break;
543
+ }
544
+ case "200": {
545
+ const gs = num(raw, "GS"), ta = num(raw, "TA");
546
+ if (gs !== void 0) out.velocity = { ...out.velocity ?? {}, groundSpeedKt: Math.round(gs * 3600) };
547
+ if (ta !== void 0) out.velocity = { ...out.velocity ?? {}, headingDeg: ta };
548
+ break;
549
+ }
550
+ case "220": {
551
+ const addr = str(raw, "TAddr");
552
+ if (addr) out.icao24 = addr.toLowerCase();
553
+ else if (d.length >= 3) out.icao24 = d.readUIntBE(0, 3).toString(16).padStart(6, "0");
554
+ break;
555
+ }
556
+ case "245": {
557
+ const cs = str(raw, "TId");
558
+ if (cs !== void 0) {
559
+ if (cs) out.callsign = cs;
560
+ } else if (d.length >= 7) out.callsign = readCallsign(d.subarray(1));
561
+ break;
562
+ }
563
+ case "060": {
564
+ const sq = str(raw, "Mode3A");
565
+ if (sq) out.squawk = sq.padStart(4, "0").slice(-4);
566
+ else if (d.length >= 2) out.squawk = (d.readUIntBE(0, 2) & 4095).toString(8).padStart(4, "0");
567
+ break;
568
+ }
569
+ case "161": {
570
+ const tn = num(raw, "TrkNb", "TRACK_NUMBER", "TrackNumber");
571
+ if (tn !== void 0) out.trackNumber = tn;
572
+ else if (d.length >= 2) out.trackNumber = d.readUIntBE(0, 2) & 4095;
573
+ break;
574
+ }
575
+ case "300": {
576
+ const vfi = num(raw, "VFI");
577
+ out.vehicleFleet = vfi ?? (d.length >= 1 ? d[0] : void 0);
578
+ out.targetType = "vehicle";
579
+ break;
580
+ }
581
+ case "140": {
582
+ const t = num(raw, "ToD");
583
+ if (t !== void 0) out.timeOfDay = t;
584
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
585
+ break;
586
+ }
587
+ }
588
+ }
589
+ var MS_TO_KT = 1 / 0.514444;
590
+ function cat062(id, d, out, raw) {
591
+ switch (id) {
592
+ case "010": {
593
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
594
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
595
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
596
+ break;
597
+ }
598
+ case "105": {
599
+ const lat = num(raw, "Lat"), lon = num(raw, "Lon");
600
+ if (lat !== void 0 && lon !== void 0 && validLatLon(lat, lon)) {
601
+ out.lat = lat;
602
+ out.lon = lon;
603
+ }
604
+ break;
605
+ }
606
+ case "185": {
607
+ const vx = num(raw, "Vx"), vy = num(raw, "Vy");
608
+ if (vx !== void 0 && vy !== void 0) {
609
+ const speedKt = Math.round(Math.sqrt(vx * vx + vy * vy) * MS_TO_KT);
610
+ const headingDeg = (Math.atan2(vx, vy) * 180 / Math.PI + 360) % 360;
611
+ out.velocity = { groundSpeedKt: speedKt, headingDeg };
612
+ }
613
+ break;
614
+ }
615
+ case "136": {
616
+ const ft = num(raw, "MFL");
617
+ if (ft !== void 0 && ft >= -2e3 && ft <= 15e4 && out.altitudeFt == null) out.altitudeFt = ft;
618
+ break;
619
+ }
620
+ case "060": {
621
+ const sq = str(raw, "Mode3A");
622
+ if (sq) out.squawk = sq.padStart(4, "0").slice(-4);
623
+ break;
624
+ }
625
+ case "040": {
626
+ const tn = num(raw, "TrkN", "TrkNb");
627
+ if (tn !== void 0) out.trackNumber = tn;
628
+ else if (d.length >= 2) out.trackNumber = d.readUIntBE(0, 2);
629
+ break;
630
+ }
631
+ case "070": {
632
+ const t = num(raw, "ToD", "TOTI");
633
+ if (t !== void 0) out.timeOfDay = t;
634
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
635
+ break;
636
+ }
637
+ }
638
+ }
639
+ function catServiceMsg(id, d, out, raw) {
640
+ if (id === "010") {
641
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
642
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
643
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
644
+ } else if (id === "000" && d.length >= 1) {
645
+ out.reportType = d[0];
646
+ }
647
+ }
648
+ function cat020(id, d, out, raw) {
649
+ switch (id) {
650
+ case "010": {
651
+ const sac = num(raw, "SAC"), sic = num(raw, "SIC");
652
+ if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
653
+ else if (d.length >= 2) out.dataSource = { sac: d[0], sic: d[1] };
654
+ break;
655
+ }
656
+ case "220": {
657
+ const addr = str(raw, "TAddr", "ACAddr");
658
+ if (addr) out.icao24 = addr.toLowerCase();
659
+ else if (d.length >= 3) out.icao24 = d.readUIntBE(0, 3).toString(16).padStart(6, "0");
660
+ break;
661
+ }
662
+ case "041": {
663
+ const lat = num(raw, "Lat"), lon = num(raw, "Lon");
664
+ if (lat !== void 0 && lon !== void 0) {
665
+ if (validLatLon(lat, lon)) {
666
+ out.lat = lat;
667
+ out.lon = lon;
668
+ }
669
+ } else if (d.length >= 8) {
670
+ const flat = readSigned(d, 0, 4) * (180 / 33554432);
671
+ const flon = readSigned(d, 4, 4) * (180 / 33554432);
672
+ if (validLatLon(flat, flon)) {
673
+ out.lat = flat;
674
+ out.lon = flon;
675
+ }
676
+ }
677
+ break;
678
+ }
679
+ case "042": {
680
+ const x = num(raw, "X"), y = num(raw, "Y");
681
+ if (x !== void 0 && y !== void 0) {
682
+ out.positionCartesian = { xNm: x / 1852, yNm: y / 1852 };
683
+ } else if (d.length >= 6) {
684
+ out.positionCartesian = {
685
+ xNm: readSigned(d, 0, 3) * 0.5 / 1852,
686
+ yNm: readSigned(d, 3, 3) * 0.5 / 1852
687
+ };
688
+ }
689
+ break;
690
+ }
691
+ case "161": {
692
+ const tn = num(raw, "TrkNb", "TRACK_NUMBER", "TrackNumber");
693
+ if (tn !== void 0) out.trackNumber = tn;
694
+ else if (d.length >= 2) out.trackNumber = d.readUIntBE(0, 2) & 4095;
695
+ break;
696
+ }
697
+ case "202": {
698
+ let vx = num(raw, "Vx", "X"), vy = num(raw, "Vy", "Y");
699
+ if (vx === void 0 || vy === void 0) {
700
+ if (d.length >= 4) {
701
+ vx = readSigned(d, 0, 2) * 0.25;
702
+ vy = readSigned(d, 2, 2) * 0.25;
703
+ }
704
+ }
705
+ if (vx !== void 0 && vy !== void 0) {
706
+ out.velocity = {
707
+ groundSpeedKt: Math.round(Math.hypot(vx, vy) * 1.94384),
708
+ headingDeg: (Math.atan2(vx, vy) * 180 / Math.PI + 360) % 360
709
+ };
710
+ }
711
+ break;
712
+ }
713
+ case "090": {
714
+ const fl = num(raw, "FL");
715
+ if (fl !== void 0) out.altitudeFt = fl * 100;
716
+ else if (d.length >= 2) out.altitudeFt = readSigned(d, 0, 2) / 4 * 100;
717
+ break;
718
+ }
719
+ case "070": {
720
+ const sq = str(raw, "Mode3A");
721
+ if (sq) out.squawk = sq.padStart(4, "0").slice(-4);
722
+ else if (d.length >= 2) out.squawk = (d.readUIntBE(0, 2) & 4095).toString(8).padStart(4, "0");
723
+ break;
724
+ }
725
+ case "245": {
726
+ const cs = str(raw, "TId");
727
+ if (cs !== void 0) {
728
+ if (cs) out.callsign = cs;
729
+ } else if (d.length >= 7) out.callsign = readCallsign(d.subarray(1));
730
+ break;
731
+ }
732
+ case "300": {
733
+ const vfi = num(raw, "VFI");
734
+ out.vehicleFleet = vfi ?? (d.length >= 1 ? d[0] : void 0);
735
+ out.targetType = "vehicle";
736
+ break;
737
+ }
738
+ case "140": {
739
+ const t = num(raw, "ToD");
740
+ if (t !== void 0) out.timeOfDay = t;
741
+ else if (d.length >= 3) out.timeOfDay = d.readUIntBE(0, 3) / 128;
742
+ break;
743
+ }
744
+ }
745
+ }
746
+
747
+ // src/surveillance/asterix/values.ts
748
+ var ICAO62 = ` ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !"#$%&'()*+,-./0123456789:;<=>?`;
749
+ function extractBitFields(data, defs) {
750
+ const out = {};
751
+ if (data.length === 0 || defs.length === 0) return out;
752
+ let acc = 0n;
753
+ for (const b of data) acc = acc << 8n | BigInt(b);
754
+ const totalBits = data.length * 8;
755
+ for (const def of defs) {
756
+ if (def.high > totalBits || def.low < 1) continue;
757
+ const width = def.high - def.low + 1;
758
+ let raw = acc >> BigInt(def.low - 1) & (1n << BigInt(width)) - 1n;
759
+ switch (def.encode) {
760
+ case "signed": {
761
+ const sign = 1n << BigInt(width - 1);
762
+ if (raw & sign) raw -= sign << 1n;
763
+ out[def.name] = Number(raw) * def.scale;
764
+ break;
765
+ }
766
+ case "octal":
767
+ out[def.name] = Number(raw).toString(8).padStart(Math.ceil(width / 3), "0");
768
+ break;
769
+ case "hex":
770
+ out[def.name] = Number(raw).toString(16).padStart(Math.ceil(width / 4), "0");
771
+ break;
772
+ case "6bitschar": {
773
+ let s = "";
774
+ for (let shift = width - 6; shift >= 0; shift -= 6) {
775
+ s += ICAO62[Number(raw >> BigInt(shift) & 0x3fn)] ?? " ";
776
+ }
777
+ out[def.name] = s.trim();
778
+ break;
779
+ }
780
+ case "ascii": {
781
+ let s = "";
782
+ for (let shift = width - 8; shift >= 0; shift -= 8) {
783
+ s += String.fromCharCode(Number(raw >> BigInt(shift) & 0xffn));
784
+ }
785
+ out[def.name] = s.trim();
786
+ break;
787
+ }
788
+ default:
789
+ out[def.name] = Number(raw) * def.scale;
790
+ }
791
+ }
792
+ return out;
793
+ }
794
+
795
+ // src/surveillance/asterix/parser.ts
796
+ var EDITIONS = {
797
+ 10: ["1.1"],
798
+ 11: ["1.2"],
799
+ 19: ["1.2"],
800
+ 20: ["1.7"],
801
+ 21: ["2.4", "1.8", "1.4", "0.26"],
802
+ 23: ["1.2"],
803
+ 34: ["1.27", "1.26"],
804
+ 48: ["1.21", "1.14"],
805
+ 62: ["1.18", "1.16", "1.9", "1.7", "0.17"],
806
+ 63: ["1.3"],
807
+ 94: ["0.1"],
808
+ 247: ["1.2"]
809
+ };
810
+ function parseAsterix(buf, defaultEdition) {
811
+ const records = [];
812
+ let offset = 0;
813
+ while (offset + 3 <= buf.length) {
814
+ const cat = buf[offset];
815
+ const len = buf[offset + 1] << 8 | buf[offset + 2];
816
+ if (len < 3 || offset + len > buf.length) break;
817
+ const block = buf.slice(offset + 3, offset + len);
818
+ offset += len;
819
+ const toTry = defaultEdition?.[cat] ? [defaultEdition[cat]] : EDITIONS[cat] ?? [];
820
+ let best = null;
821
+ for (const edition of toTry) {
822
+ const def = loadDef(cat, edition);
823
+ if (!def) continue;
824
+ const fields = {};
825
+ try {
826
+ decodeBlock(block, def, (id, data, itemDef) => {
827
+ const bits = itemDef.type === "fixed" || itemDef.type === "variable" ? itemDef.bits : void 0;
828
+ const bitData = itemDef.type === "variable" ? data.subarray(0, itemDef.chunkBytes) : data;
829
+ const raw = bits?.length ? extractBitFields(bitData, bits) : {};
830
+ decodeKnownField(cat, id, data, fields, raw);
831
+ });
832
+ } catch {
833
+ continue;
834
+ }
835
+ if (!isPlausible(fields)) continue;
836
+ const score = plausibilityScore(fields);
837
+ if (!best || score > best.score) {
838
+ best = { edition, fields, score };
839
+ }
840
+ }
841
+ if (best) records.push({ category: cat, edition: best.edition, fields: best.fields });
842
+ }
843
+ return records;
844
+ }
845
+ function isPlausible(f) {
846
+ if (f.lat !== void 0 && (f.lat < -90 || f.lat > 90)) return false;
847
+ if (f.lon !== void 0 && (f.lon < -180 || f.lon > 180)) return false;
848
+ return true;
849
+ }
850
+ function plausibilityScore(f) {
851
+ let score = Object.keys(f).length;
852
+ const gs = f.velocity?.groundSpeedKt;
853
+ if (gs !== void 0) score += gs >= 0 && gs <= 1200 ? 2 : -4;
854
+ const hdg = f.velocity?.headingDeg;
855
+ if (hdg !== void 0) score += hdg >= 0 && hdg < 360 ? 1 : -4;
856
+ if (f.altitudeFt !== void 0) {
857
+ score += f.altitudeFt >= -2e3 && f.altitudeFt <= 6e4 ? 2 : -4;
858
+ }
859
+ if (f.callsign !== void 0) {
860
+ const cs = f.callsign.trim();
861
+ score += /^[A-Z0-9 ]+$/.test(cs) && cs.length >= 2 ? 2 : -4;
862
+ }
863
+ if (f.timeOfDay !== void 0) {
864
+ score += f.timeOfDay >= 0 && f.timeOfDay < 86400 ? 1 : -4;
865
+ }
866
+ return score;
867
+ }
868
+
869
+ // src/surveillance/asterix/resolvers.ts
870
+ function resolveAsterixPosition(fields, radarSite) {
871
+ if (typeof fields.lat === "number" && typeof fields.lon === "number") {
872
+ return { lat: fields.lat, lon: fields.lon };
873
+ }
874
+ if (!radarSite) return null;
875
+ const cart = fields.positionCartesian;
876
+ const polar = fields.measuredPosition;
877
+ let xNm;
878
+ let yNm;
879
+ if (typeof cart?.xNm === "number" && typeof cart?.yNm === "number") {
880
+ xNm = cart.xNm;
881
+ yNm = cart.yNm;
882
+ } else if (typeof polar?.rangeNm === "number" && typeof polar?.azimuthDeg === "number") {
883
+ const az = polar.azimuthDeg * Math.PI / 180;
884
+ xNm = polar.rangeNm * Math.sin(az);
885
+ yNm = polar.rangeNm * Math.cos(az);
886
+ }
887
+ if (xNm == null || yNm == null) return null;
888
+ return {
889
+ lat: radarSite.lat + yNm / 60,
890
+ lon: radarSite.lon + xNm / (60 * Math.cos(radarSite.lat * Math.PI / 180))
891
+ };
892
+ }
893
+ function resolveAsterixAltitudeFt(fields) {
894
+ return typeof fields.altitudeFt === "number" ? fields.altitudeFt : void 0;
895
+ }
896
+
897
+ // src/surveillance/adsb/index.ts
898
+ function decodeAdsbFrame(_frame) {
899
+ return null;
900
+ }
901
+
902
+ // src/surveillance/ais/index.ts
903
+ function decodeAis(_sentence) {
904
+ return null;
905
+ }
906
+
907
+ // src/surveillance/mlat/index.ts
908
+ function decodeMlatFrame(_frame) {
909
+ return null;
910
+ }
911
+
912
+ // src/surveillance/asmgcs/index.ts
913
+ function decodeAsmgcsFrame(_frame) {
914
+ return null;
915
+ }
916
+ export {
917
+ decodeAdsbFrame,
918
+ decodeAis,
919
+ decodeAsmgcsFrame,
920
+ decodeMlatFrame,
921
+ parseAsterix,
922
+ resolveAsterixAltitudeFt,
923
+ resolveAsterixPosition
924
+ };