@airframes/acars-decoder 1.9.4 → 1.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -138,8 +138,8 @@ var CoordinateUtils = class _CoordinateUtils {
138
138
  };
139
139
  }
140
140
  static coordinateString(coords) {
141
- const latDir = coords.latitude > 0 ? "N" : "S";
142
- const lonDir = coords.longitude > 0 ? "E" : "W";
141
+ const latDir = coords.latitude >= 0 ? "N" : "S";
142
+ const lonDir = coords.longitude >= 0 ? "E" : "W";
143
143
  return `${Math.abs(coords.latitude).toFixed(3)} ${latDir}, ${Math.abs(coords.longitude).toFixed(3)} ${lonDir}`;
144
144
  }
145
145
  static getDirection(coord) {
@@ -189,16 +189,16 @@ var DateTimeUtils = class {
189
189
  const h = Number(time.substring(0, 2));
190
190
  const m = Number(time.substring(2, 4));
191
191
  const s = Number(time.substring(4, 6));
192
- const tod = h * 3600 + m * 60 + s;
193
- return tod;
192
+ const tod2 = h * 3600 + m * 60 + s;
193
+ return tod2;
194
194
  }
195
195
  static convertDayTimeToTod(time) {
196
196
  const d = Number(time.substring(0, 2));
197
197
  const h = Number(time.substring(2, 4));
198
198
  const m = Number(time.substring(4, 6));
199
199
  const s = Number(time.substring(6, 8));
200
- const tod = d * 86400 + h * 3600 + m * 60 + s;
201
- return tod;
200
+ const tod2 = d * 86400 + h * 3600 + m * 60 + s;
201
+ return tod2;
202
202
  }
203
203
  /**
204
204
  *
@@ -474,6 +474,16 @@ var ResultFormatter = class {
474
474
  value: decodeResult.raw.alternate_icao
475
475
  });
476
476
  }
477
+ /** The airport landing data is for: the destination, or the airport of a divert. */
478
+ static landingAirport(decodeResult, value) {
479
+ decodeResult.raw.landing_icao = value;
480
+ decodeResult.formatted.items.push({
481
+ type: "icao",
482
+ code: "LAND",
483
+ label: "Landing Airport",
484
+ value: decodeResult.raw.landing_icao
485
+ });
486
+ }
477
487
  static eta(decodeResult, time) {
478
488
  if (isNaN(time)) {
479
489
  return;
@@ -664,6 +674,144 @@ var ResultFormatter = class {
664
674
  value: `${decodeResult.raw.heading}`
665
675
  });
666
676
  }
677
+ static indicatedAirspeed(decodeResult, value) {
678
+ if (isNaN(value)) {
679
+ return;
680
+ }
681
+ decodeResult.raw.indicated_airspeed = value;
682
+ decodeResult.formatted.items.push({
683
+ type: "indicated_airspeed",
684
+ code: "IAS",
685
+ label: "Indicated Airspeed",
686
+ value: `${decodeResult.raw.indicated_airspeed} knots`
687
+ });
688
+ }
689
+ static track(decodeResult, value) {
690
+ if (isNaN(value)) {
691
+ return;
692
+ }
693
+ decodeResult.raw.track = value;
694
+ decodeResult.formatted.items.push({
695
+ type: "track",
696
+ code: "TRK",
697
+ label: "Track",
698
+ value: `${decodeResult.raw.track} degrees`
699
+ });
700
+ }
701
+ /** Wind at the aircraft; either half may be missing. */
702
+ static wind(decodeResult, direction, speed) {
703
+ const hasDirection = direction !== void 0 && !isNaN(direction);
704
+ const hasSpeed = speed !== void 0 && !isNaN(speed);
705
+ if (hasDirection) decodeResult.raw.wind_direction = direction;
706
+ if (hasSpeed) decodeResult.raw.wind_speed = speed;
707
+ if (!hasDirection && !hasSpeed) {
708
+ return;
709
+ }
710
+ decodeResult.formatted.items.push({
711
+ type: "wind",
712
+ code: "WIND",
713
+ label: !hasSpeed ? "Wind Direction" : !hasDirection ? "Wind Speed" : "Wind",
714
+ value: [
715
+ hasDirection ? `${direction} degrees` : "",
716
+ hasSpeed ? `${speed} knots` : ""
717
+ ].filter((s) => s !== "").join(" at ")
718
+ });
719
+ }
720
+ static variableWind(decodeResult, speed) {
721
+ if (isNaN(speed)) {
722
+ return;
723
+ }
724
+ decodeResult.raw.wind_speed = speed;
725
+ decodeResult.raw.wind_variable = true;
726
+ decodeResult.formatted.items.push({
727
+ type: "wind",
728
+ code: "WIND",
729
+ label: "Wind",
730
+ value: `variable at ${speed} knots`
731
+ });
732
+ }
733
+ /**
734
+ * One point of a recorded track. The track is the route as flown, so each point is added to
735
+ * the route's waypoints; the item shows what the aircraft measured there as well.
736
+ */
737
+ static trackPoint(decodeResult, point) {
738
+ decodeResult.raw.route = {
739
+ ...decodeResult.raw.route,
740
+ waypoints: [...decodeResult.raw.route?.waypoints ?? [], point]
741
+ };
742
+ decodeResult.formatted.items.push({
743
+ type: "track_point",
744
+ code: "TRKPT",
745
+ label: "Track Point",
746
+ value: `${CoordinateUtils.coordinateString(point)} on day ${point.day} at ${DateTimeUtils.timestampToString(point.time)}, ${point.altitude} feet, ${point.temperature} C, wind ${point.windDirection} degrees at ${point.windSpeed} knots, ${point.phase}` + (point.waterVapor === void 0 ? "" : `, water vapor ${point.waterVapor} ppmv`)
747
+ });
748
+ }
749
+ static runwayLandingData(decodeResult, data) {
750
+ decodeResult.raw.landing_runways = [
751
+ ...decodeResult.raw.landing_runways ?? [],
752
+ data
753
+ ];
754
+ const parts = [];
755
+ if (data.length !== void 0) parts.push(`${data.length} feet`);
756
+ if (data.flaps !== void 0) parts.push(`flaps ${data.flaps}`);
757
+ if (data.headwind !== void 0) {
758
+ parts.push(
759
+ data.headwind < 0 ? `tailwind ${-data.headwind} knots` : `headwind ${data.headwind} knots`
760
+ );
761
+ }
762
+ if (data.crosswind !== void 0) {
763
+ const side = { L: " left", R: " right" }[data.crosswindSide ?? "L"];
764
+ parts.push(
765
+ `crosswind ${data.crosswind} knots${data.crosswindSide ? side : ""}`
766
+ );
767
+ }
768
+ if (data.vref !== void 0) parts.push(`VREF ${data.vref} knots`);
769
+ if (data.vtarget !== void 0) {
770
+ const add = data.additive === void 0 ? "" : ` (${data.additive < 0 ? "" : "+"}${data.additive})`;
771
+ parts.push(`VTGT ${data.vtarget} knots${add}`);
772
+ }
773
+ if (data.maxLandingWeight !== void 0) {
774
+ parts.push(`MRLW ${data.maxLandingWeight}`);
775
+ }
776
+ if (data.landingWeight !== void 0) {
777
+ parts.push(`LDW ${data.landingWeight} pounds`);
778
+ }
779
+ const margins = Object.entries(data.stoppingMargins).map(
780
+ ([setting, feet]) => `${setting} ${feet} feet`
781
+ );
782
+ if (margins.length > 0) {
783
+ parts.push(`stopping margin ${margins.join(", ")}`);
784
+ }
785
+ parts.push(...data.notes ?? []);
786
+ const variant2 = data.variant === void 0 ? "" : ` (${data.variant})`;
787
+ decodeResult.formatted.items.push({
788
+ type: "runway_landing_data",
789
+ code: "RWYLDG",
790
+ label: "Runway Landing Data",
791
+ value: `${data.runway}${variant2}: ${parts.join(", ")}`
792
+ });
793
+ }
794
+ static grossWeight(decodeResult, pounds3) {
795
+ if (isNaN(pounds3)) {
796
+ return;
797
+ }
798
+ decodeResult.raw.gross_weight = pounds3;
799
+ decodeResult.formatted.items.push({
800
+ type: "gross_weight",
801
+ code: "GWT",
802
+ label: "Gross Weight",
803
+ value: `${decodeResult.raw.gross_weight} pounds`
804
+ });
805
+ }
806
+ static arrivalGate(decodeResult, value) {
807
+ decodeResult.raw.arrival_gate = value;
808
+ decodeResult.formatted.items.push({
809
+ type: "gate",
810
+ code: "AGATE",
811
+ label: "Arrival Gate",
812
+ value: decodeResult.raw.arrival_gate
813
+ });
814
+ }
667
815
  static tail(decodeResult, value) {
668
816
  decodeResult.raw.tail = value;
669
817
  decodeResult.formatted.items.push({
@@ -983,6 +1131,14 @@ var ResultFormatter = class {
983
1131
  value: decodeResult.raw.sequence_response.toString()
984
1132
  });
985
1133
  }
1134
+ /**
1135
+ * A field with no specific helper: `key` is its name in `raw` and its item type. `rawValue`
1136
+ * is stored in `raw` when it differs from the text shown (a number, for example).
1137
+ */
1138
+ static raw(decodeResult, key, code, label, value, rawValue = value) {
1139
+ decodeResult.raw[key] = rawValue;
1140
+ decodeResult.formatted.items.push({ type: key, code, label, value });
1141
+ }
986
1142
  static unknown(decodeResult, value, sep = ",") {
987
1143
  if (!decodeResult.remaining.text) decodeResult.remaining.text = value;
988
1144
  else decodeResult.remaining.text += sep + value;
@@ -1328,13 +1484,13 @@ function addCompanyRoute(decodeResult, value) {
1328
1484
  const segments = value.split(".");
1329
1485
  const parens_idx = segments[0].indexOf("(");
1330
1486
  let name;
1331
- let runway;
1487
+ let runway2;
1332
1488
  if (parens_idx === -1) {
1333
1489
  name = segments[0];
1334
1490
  } else {
1335
1491
  name = segments[0].slice(0, parens_idx);
1336
1492
  const close_paren_idx = segments[0].indexOf(")");
1337
- runway = segments[0].slice(
1493
+ runway2 = segments[0].slice(
1338
1494
  parens_idx + 1,
1339
1495
  close_paren_idx === -1 ? void 0 : close_paren_idx
1340
1496
  );
@@ -1345,7 +1501,7 @@ function addCompanyRoute(decodeResult, value) {
1345
1501
  }
1346
1502
  decodeResult.raw.company_route = {
1347
1503
  name,
1348
- runway,
1504
+ runway: runway2,
1349
1505
  waypoints
1350
1506
  };
1351
1507
  decodeResult.formatted.items.push({
@@ -2181,7 +2337,54 @@ var Label_5Z_Slash = class extends DecoderPlugin {
2181
2337
  }
2182
2338
  };
2183
2339
 
2340
+ // lib/utils/swa_request.ts
2341
+ var AIRLINES = { "189": "Southwest" };
2342
+ function swaRequestHeader(decodeResult, airlineId, layout, software) {
2343
+ const airline = AIRLINES[airlineId];
2344
+ ResultFormatter.raw(
2345
+ decodeResult,
2346
+ "airline_id",
2347
+ "AIRLINE",
2348
+ "Airline ID",
2349
+ airline ? `${airlineId} (${airline})` : airlineId,
2350
+ airlineId
2351
+ );
2352
+ ResultFormatter.raw(
2353
+ decodeResult,
2354
+ "request_layout",
2355
+ "LAYOUT",
2356
+ "Request Layout",
2357
+ layout
2358
+ );
2359
+ ResultFormatter.raw(
2360
+ decodeResult,
2361
+ "software_version",
2362
+ "SWVER",
2363
+ "Software Version",
2364
+ software
2365
+ );
2366
+ }
2367
+
2184
2368
  // lib/plugins/Label_10_LDR.ts
2369
+ function pounds(value) {
2370
+ if (value.trim() === "") return void 0;
2371
+ const thousands = Number(value);
2372
+ return Number.isFinite(thousands) ? Math.round(thousands * 1e3) : void 0;
2373
+ }
2374
+ function runway(value) {
2375
+ const [rwy, suffix = ""] = value.trim().split("/");
2376
+ return suffix.trim() === "" ? rwy.trim() : `${rwy.trim()}/${suffix.trim()}`;
2377
+ }
2378
+ var RUNWAY_CONDITION = {
2379
+ 6: "dry",
2380
+ 5: "good",
2381
+ 4: "good to medium",
2382
+ 3: "medium",
2383
+ 2: "medium to poor",
2384
+ 1: "poor",
2385
+ 0: "nil"
2386
+ };
2387
+ var FLAPS = { "0": 30, "1": 40, "2": 15 };
2185
2388
  var Label_10_LDR = class extends DecoderPlugin {
2186
2389
  name = "label-10-ldr";
2187
2390
  qualifiers() {
@@ -2193,18 +2396,13 @@ var Label_10_LDR = class extends DecoderPlugin {
2193
2396
  decode(message, options = {}) {
2194
2397
  const decodeResult = this.defaultResult();
2195
2398
  decodeResult.decoder.name = this.name;
2196
- decodeResult.formatted.description = "Position Report";
2399
+ decodeResult.formatted.description = "Landing Data Request";
2197
2400
  decodeResult.message = message;
2198
2401
  const parts = message.text.split(",");
2199
- if (parts.length < 17) {
2200
- if (options.debug) {
2201
- console.log(`Decoder: Unknown 10 message: ${message.text}`);
2202
- }
2203
- ResultFormatter.unknown(decodeResult, message.text);
2204
- decodeResult.decoded = false;
2205
- decodeResult.decoder.decodeLevel = "none";
2206
- return decodeResult;
2402
+ if (parts.length < 31) {
2403
+ return this.failUnknown(decodeResult, message.text, options);
2207
2404
  }
2405
+ swaRequestHeader(decodeResult, parts[1], parts[2], parts[3]);
2208
2406
  const lat = parts[5];
2209
2407
  const lon = parts[6];
2210
2408
  const position = {
@@ -2212,25 +2410,209 @@ var Label_10_LDR = class extends DecoderPlugin {
2212
2410
  longitude: (lon[0] === "E" ? 1 : -1) * Number(lon.substring(1).trim())
2213
2411
  };
2214
2412
  ResultFormatter.position(decodeResult, position);
2215
- ResultFormatter.altitude(decodeResult, Number(parts[7]));
2413
+ if (parts[7].trim() !== "") {
2414
+ ResultFormatter.altitude(decodeResult, Number(parts[7]));
2415
+ }
2416
+ const fuel = pounds(parts[8]);
2417
+ if (fuel !== void 0) ResultFormatter.currentFuel(decodeResult, fuel);
2216
2418
  ResultFormatter.departureAirport(decodeResult, parts[9]);
2217
2419
  ResultFormatter.arrivalAirport(decodeResult, parts[10]);
2218
- ResultFormatter.alternateAirport(decodeResult, parts[11]);
2219
- ResultFormatter.arrivalRunway(decodeResult, parts[12].split("/")[0]);
2220
- const altRwy = [parts[13].split("/")[0], parts[14].split("/")[0]].filter((r) => r != "").join(",");
2420
+ ResultFormatter.landingAirport(decodeResult, parts[11]);
2421
+ ResultFormatter.arrivalRunway(decodeResult, runway(parts[12]));
2422
+ const altRwy = [runway(parts[13]), runway(parts[14])].filter((r) => r != "").join(",");
2221
2423
  if (altRwy != "") {
2222
2424
  ResultFormatter.alternateRunway(decodeResult, altRwy);
2223
2425
  }
2224
- ResultFormatter.unknownArr(decodeResult, [
2225
- ...parts.slice(0, 5),
2226
- ...parts.slice(15)
2227
- ]);
2426
+ const unknown = [parts[4]];
2427
+ const condition = parts[15].trim();
2428
+ if (/^[1-6]$/.test(condition)) {
2429
+ const code = 6 - Number(condition);
2430
+ ResultFormatter.raw(
2431
+ decodeResult,
2432
+ "runway_condition_code",
2433
+ "RCC",
2434
+ "Runway Condition Code",
2435
+ `${code} (${RUNWAY_CONDITION[code]})`,
2436
+ code
2437
+ );
2438
+ } else if (condition !== "0") {
2439
+ unknown.push(parts[15]);
2440
+ }
2441
+ const flaps = FLAPS[parts[16].trim()];
2442
+ if (flaps !== void 0) {
2443
+ ResultFormatter.raw(
2444
+ decodeResult,
2445
+ "landing_flaps",
2446
+ "FLAPS",
2447
+ "Landing Flaps",
2448
+ `${flaps}`,
2449
+ flaps
2450
+ );
2451
+ } else {
2452
+ unknown.push(parts[16]);
2453
+ }
2454
+ const [windDir, windSpeed, gust, temp, dew, altimeter] = parts.slice(17, 23).map((field) => {
2455
+ const value = field.trim();
2456
+ if (/^-?\d{1,4}$/.test(value)) return Number(value);
2457
+ if (value !== "") unknown.push(field);
2458
+ return void 0;
2459
+ });
2460
+ if (windDir !== void 0 || windSpeed !== void 0) {
2461
+ ResultFormatter.wind(decodeResult, windDir, windSpeed);
2462
+ }
2463
+ if (gust !== void 0) {
2464
+ ResultFormatter.raw(
2465
+ decodeResult,
2466
+ "wind_gust",
2467
+ "GUST",
2468
+ "Wind Gust",
2469
+ `${gust} knots`,
2470
+ gust
2471
+ );
2472
+ }
2473
+ ResultFormatter.temperature(
2474
+ decodeResult,
2475
+ temp === void 0 ? "" : `${temp}`
2476
+ );
2477
+ if (dew !== void 0) {
2478
+ ResultFormatter.raw(
2479
+ decodeResult,
2480
+ "dew_point",
2481
+ "DEWPT",
2482
+ "Dew Point (C)",
2483
+ `${dew} degrees`,
2484
+ dew
2485
+ );
2486
+ }
2487
+ if (altimeter !== void 0) {
2488
+ const inHg = altimeter / 100;
2489
+ ResultFormatter.raw(
2490
+ decodeResult,
2491
+ "altimeter",
2492
+ "ALTIM",
2493
+ "Altimeter",
2494
+ `${inHg.toFixed(2)} inHg`,
2495
+ inHg
2496
+ );
2497
+ }
2498
+ const antiIce = parts[23].trim();
2499
+ if (/^[1-9]$/.test(antiIce)) {
2500
+ ResultFormatter.raw(
2501
+ decodeResult,
2502
+ "anti_ice",
2503
+ "ANTIICE",
2504
+ "Anti-Ice",
2505
+ antiIce === "1" ? "engine" : antiIce,
2506
+ Number(antiIce)
2507
+ );
2508
+ } else if (antiIce !== "0") {
2509
+ unknown.push(parts[23]);
2510
+ }
2511
+ const flags = [
2512
+ [parts[24], "enroute_icing", "ICING", "En Route Icing"],
2513
+ // the card says HGS AIII: 113 of 113 pairs, 32 requests
2514
+ [parts[25], "hgs_approach", "HGS", "HGS Approach"]
2515
+ ];
2516
+ for (const [value, key, code, label] of flags) {
2517
+ if (value.trim() === "1") {
2518
+ ResultFormatter.raw(decodeResult, key, code, label, "yes", true);
2519
+ } else if (value.trim() !== "0") {
2520
+ unknown.push(value);
2521
+ }
2522
+ }
2523
+ const nonNormal = parts[26].trim();
2524
+ if (/^\d+$/.test(nonNormal) && Number(nonNormal) !== 0) {
2525
+ ResultFormatter.raw(
2526
+ decodeResult,
2527
+ "non_normal_configuration",
2528
+ "NNC",
2529
+ "Non-Normal Configuration",
2530
+ `${Number(nonNormal)}`,
2531
+ Number(nonNormal)
2532
+ );
2533
+ } else if (!/^0+$/.test(nonNormal)) {
2534
+ unknown.push(parts[26]);
2535
+ }
2536
+ if (parts[27].trim() !== "") unknown.push(parts[27]);
2537
+ const weights = [
2538
+ ["zero_fuel_weight", "ZFW", "Zero Fuel Weight", parts[28]],
2539
+ ["landing_fuel", "LFUEL", "Landing Fuel", parts[29]],
2540
+ ["landing_weight", "LDW", "Landing Weight", parts[30]]
2541
+ ];
2542
+ for (const [key, code, label, value] of weights) {
2543
+ const amount = pounds(value);
2544
+ if (amount === void 0) continue;
2545
+ ResultFormatter.raw(
2546
+ decodeResult,
2547
+ key,
2548
+ code,
2549
+ label,
2550
+ `${amount} pounds`,
2551
+ amount
2552
+ );
2553
+ }
2554
+ const gradient = (parts[31] ?? "").trim();
2555
+ if (/^\d+$/.test(gradient)) {
2556
+ ResultFormatter.raw(
2557
+ decodeResult,
2558
+ "missed_approach_gradient",
2559
+ "MAGRAD",
2560
+ "Missed Approach Climb Gradient",
2561
+ `${Number(gradient)} feet per nautical mile`,
2562
+ Number(gradient)
2563
+ );
2564
+ } else if (gradient !== "" && gradient !== "----.") {
2565
+ unknown.push(parts[31]);
2566
+ }
2567
+ const missedAltitude = (parts[32] ?? "").trim();
2568
+ if (/^\d+$/.test(missedAltitude)) {
2569
+ ResultFormatter.raw(
2570
+ decodeResult,
2571
+ "missed_approach_altitude",
2572
+ "MAALT",
2573
+ "Missed Approach Altitude",
2574
+ `${Number(missedAltitude)} feet`,
2575
+ Number(missedAltitude)
2576
+ );
2577
+ } else if (missedAltitude !== "") {
2578
+ unknown.push(parts[32]);
2579
+ }
2580
+ unknown.push(...parts.slice(33).filter((field) => field.trim() !== ""));
2581
+ ResultFormatter.unknownArr(decodeResult, unknown);
2228
2582
  decodeResult.decoded = true;
2229
2583
  decodeResult.decoder.decodeLevel = "partial";
2230
2584
  return decodeResult;
2231
2585
  }
2232
2586
  };
2233
2587
 
2588
+ // lib/plugins/Label_10_LRW.ts
2589
+ var Label_10_LRW = class extends DecoderPlugin {
2590
+ name = "label-10-lrw";
2591
+ qualifiers() {
2592
+ return {
2593
+ labels: ["10"],
2594
+ preambles: ["LRW01,"]
2595
+ };
2596
+ }
2597
+ decode(message, options = {}) {
2598
+ const decodeResult = this.initResult(message, "Landing Runways Request");
2599
+ const parts = message.text.split(",");
2600
+ if (parts.length < 8 || parts[7].trim() === "") {
2601
+ return this.failUnknown(decodeResult, message.text, options);
2602
+ }
2603
+ swaRequestHeader(decodeResult, parts[1], parts[2], parts[3]);
2604
+ ResultFormatter.departureAirport(decodeResult, parts[6]);
2605
+ ResultFormatter.arrivalAirport(decodeResult, parts[7]);
2606
+ ResultFormatter.unknownArr(decodeResult, [
2607
+ parts[4],
2608
+ parts[5],
2609
+ ...parts.slice(8).filter((field) => field.trim() !== "")
2610
+ ]);
2611
+ this.setDecodeLevel(decodeResult, true);
2612
+ return decodeResult;
2613
+ }
2614
+ };
2615
+
2234
2616
  // lib/plugins/Label_10_POS.ts
2235
2617
  var Label_10_POS = class extends DecoderPlugin {
2236
2618
  name = "label-10-pos";
@@ -2330,6 +2712,364 @@ var Label_10_Slash = class extends DecoderPlugin {
2330
2712
  }
2331
2713
  };
2332
2714
 
2715
+ // lib/utils/runway_utils.ts
2716
+ var SIDES = {
2717
+ N: "north",
2718
+ NE: "northeast",
2719
+ E: "east",
2720
+ SE: "southeast",
2721
+ S: "south",
2722
+ SW: "southwest",
2723
+ W: "west",
2724
+ NW: "northwest"
2725
+ };
2726
+ var RunwayUtils = class {
2727
+ /**
2728
+ * Describe the condition that names a runway variant in Southwest landing data, e.g.
2729
+ * `11/AIB -E1804F`: the runway, the variant's code and its condition.
2730
+ *
2731
+ * -E1804F is 1804 feet taken off the east end, -SWCBL the arresting cable at the southwest
2732
+ * end. Checked on the LRW01 runway lists of six airports, where every variant's length is
2733
+ * the runway's less the feet given, or less 1500 feet for a cable. LAHSO is sent as written.
2734
+ *
2735
+ * @param name - the runway as sent, with its variant
2736
+ * @returns a description of the condition, or undefined when the name has none
2737
+ */
2738
+ static variantDescription(name) {
2739
+ const text = name.trim();
2740
+ const cut = text.match(/-(NE|NW|SE|SW|N|E|S|W)(\d+)F$/);
2741
+ if (cut) {
2742
+ return `${Number(cut[2])} feet off the ${SIDES[cut[1]]} end`;
2743
+ }
2744
+ const cable = text.match(/-(NE|NW|SE|SW|N|E|S|W)CBL$/);
2745
+ if (cable) {
2746
+ return `arresting cable at the ${SIDES[cable[1]]} end`;
2747
+ }
2748
+ if (/(^|\s)LAHSO$/.test(text)) {
2749
+ return "land and hold short";
2750
+ }
2751
+ return void 0;
2752
+ }
2753
+ };
2754
+
2755
+ // lib/plugins/Label_11_LDR.ts
2756
+ function tod(hours, minutes) {
2757
+ const h = Number(hours);
2758
+ const m = Number(minutes);
2759
+ return h <= 23 && m <= 59 ? h * 3600 + m * 60 : void 0;
2760
+ }
2761
+ function pounds2(value) {
2762
+ return Math.round(Number(value) * 1e3);
2763
+ }
2764
+ function variant(runway2) {
2765
+ const description = RunwayUtils.variantDescription(runway2);
2766
+ return description === void 0 ? {} : { variant: description };
2767
+ }
2768
+ var HEADER = [
2769
+ /^LDR01,(\w{4}) LANDING DATA,FLT(?:\s+METAR-(\d{2})(\d{2})Z)?\s*$/,
2770
+ /^(\d{1,4})\s+WIND\s+LDW\s*$/,
2771
+ /^TIME\s+(\d{3}|VRB)\/(\d{2,3})G(\d{2,3}|--)\s+(\d+\.\d)\s*$/,
2772
+ /^(\d{2})(\d{2})Z\s+OAT\/DEW\s*(\S*)\s*$/,
2773
+ /^\s+(-?\d{2,3})\/(-?\d{2,3})\s*(\S*)\s*$/,
2774
+ /^\s+QNH\s*(\S*)\s*$/,
2775
+ // QNH in inches of mercury, or hectopascals outside the US
2776
+ /^\s+(\d{2}\.\d{2}|\d{4})\s*(\S*)\s*$/
2777
+ ];
2778
+ var RUNWAY = /^(\d{1,2}[LRC]?(?:\/\S+)?(?: -\S+)?)\s+(\d{1,5})\s*$/;
2779
+ var RUNWAY_START = /^\d{1,2}[LRC]?(?:\/\S+)?(?: -\S+)?(?:\s{2,}\d*)?\s*$/;
2780
+ var ALTERNATIVES = "--- ALTERNATIVE RWYS ---";
2781
+ var ALTERNATIVE = /^(?:(.+?)\s{2,})?(\d{1,2}[LRC]?(?:\/\S+)?)\s*$/;
2782
+ var BLOCK = [
2783
+ /^FLAPS (\d{2}|--)\s*$/,
2784
+ /^VREF\/VTGT\s+(?:(\d{2})([HT])\/(\d{2})([LR])|---\/---)\s*$/,
2785
+ /^\s*(?:(\d{3})\/(\d{3}) ([+-]\d+)|---\/---)\s+(AB\d)\s+(\d+|----)\s*$/,
2786
+ /^MRLW (\d+\.\d\/\S|-+\/-)\s+(AB\d)\s+(\d+|----)\s*$/,
2787
+ /^\s*LDW (\d+\.\d)\s+(ABM)\s+(\d+|----)\s*$/
2788
+ ];
2789
+ var Label_11_LDR = class extends DecoderPlugin {
2790
+ name = "label-11-ldr";
2791
+ qualifiers() {
2792
+ return {
2793
+ labels: ["11"],
2794
+ preambles: ["LDR01,"]
2795
+ };
2796
+ }
2797
+ decode(message, options = {}) {
2798
+ const decodeResult = this.initResult(message, "Landing Data");
2799
+ const nl = message.text.includes("\r\n") ? "\r\n" : "\n";
2800
+ const lines = message.text.split(/\r?\n/);
2801
+ const first = lines[0].match(HEADER[0]);
2802
+ if (!first) {
2803
+ return this.failUnknown(decodeResult, message.text, options);
2804
+ }
2805
+ const unknown = [];
2806
+ ResultFormatter.landingAirport(decodeResult, first[1]);
2807
+ const metar = first[2] === void 0 ? void 0 : tod(first[2], first[3]);
2808
+ if (first[2] !== void 0 && metar === void 0) {
2809
+ unknown.push(`METAR-${first[2]}${first[3]}Z`);
2810
+ }
2811
+ if (metar !== void 0) {
2812
+ ResultFormatter.raw(
2813
+ decodeResult,
2814
+ "metar_time",
2815
+ "METAR",
2816
+ "METAR Time",
2817
+ DateTimeUtils.timestampToString(metar),
2818
+ metar
2819
+ );
2820
+ }
2821
+ const runways = [];
2822
+ let i = 1;
2823
+ for (; i < HEADER.length && i < lines.length; i++) {
2824
+ const m = lines[i].match(HEADER[i]);
2825
+ if (!m) break;
2826
+ if (i === 1) {
2827
+ ResultFormatter.flightNumber(decodeResult, m[1]);
2828
+ } else if (i === 2) {
2829
+ if (m[1] === "VRB") {
2830
+ ResultFormatter.variableWind(decodeResult, Number(m[2]));
2831
+ } else {
2832
+ ResultFormatter.wind(decodeResult, Number(m[1]), Number(m[2]));
2833
+ }
2834
+ if (m[3] !== "--") {
2835
+ ResultFormatter.raw(
2836
+ decodeResult,
2837
+ "wind_gust",
2838
+ "GUST",
2839
+ "Wind Gust",
2840
+ `${Number(m[3])} knots`,
2841
+ Number(m[3])
2842
+ );
2843
+ }
2844
+ ResultFormatter.raw(
2845
+ decodeResult,
2846
+ "landing_weight",
2847
+ "LDW",
2848
+ "Landing Weight",
2849
+ `${pounds2(m[4])} pounds`,
2850
+ pounds2(m[4])
2851
+ );
2852
+ } else if (i === 3) {
2853
+ const time = tod(m[1], m[2]);
2854
+ if (time === void 0) {
2855
+ unknown.push(`${m[1]}${m[2]}Z`);
2856
+ } else {
2857
+ ResultFormatter.timestamp(decodeResult, time);
2858
+ }
2859
+ runways.push(m[3]);
2860
+ } else if (i === 4) {
2861
+ ResultFormatter.temperature(decodeResult, `${Number(m[1])}`);
2862
+ ResultFormatter.raw(
2863
+ decodeResult,
2864
+ "dew_point",
2865
+ "DEWPT",
2866
+ "Dew Point (C)",
2867
+ `${Number(m[2])} degrees`,
2868
+ Number(m[2])
2869
+ );
2870
+ runways.push(m[3]);
2871
+ } else if (i === 5) {
2872
+ runways.push(m[1]);
2873
+ } else {
2874
+ ResultFormatter.raw(
2875
+ decodeResult,
2876
+ "altimeter",
2877
+ "ALTIM",
2878
+ "Altimeter",
2879
+ `${m[1]} ${m[1].includes(".") ? "inHg" : "hPa"}`,
2880
+ Number(m[1])
2881
+ );
2882
+ runways.push(m[2]);
2883
+ }
2884
+ }
2885
+ const listed = runways.filter((r) => r !== "");
2886
+ if (listed.length > 0) {
2887
+ ResultFormatter.raw(
2888
+ decodeResult,
2889
+ "runways",
2890
+ "RWYS",
2891
+ "Runways",
2892
+ listed.join(", "),
2893
+ listed
2894
+ );
2895
+ }
2896
+ const rest = lines.slice(i);
2897
+ let at = 0;
2898
+ const alternatives = [];
2899
+ if (rest[0]?.trim() === "REMARKS") {
2900
+ const remarks = [];
2901
+ for (at = 1; at < rest.length && !RUNWAY_START.test(rest[at]); at++) {
2902
+ if (rest[at].trim() === ALTERNATIVES) {
2903
+ at = this.alternatives(rest, at + 1, alternatives, unknown);
2904
+ break;
2905
+ }
2906
+ if (rest[at].trim() !== "") remarks.push(rest[at].trim());
2907
+ }
2908
+ if (remarks.length > 0) {
2909
+ ResultFormatter.raw(
2910
+ decodeResult,
2911
+ "remarks",
2912
+ "RMK",
2913
+ "Remarks",
2914
+ remarks.join(" ")
2915
+ );
2916
+ }
2917
+ }
2918
+ if (alternatives.length > 0) {
2919
+ ResultFormatter.raw(
2920
+ decodeResult,
2921
+ "alternative_runways",
2922
+ "ALTRWY",
2923
+ "Alternative Runways",
2924
+ alternatives.map((a) => a.variant ? `${a.runway} (${a.variant})` : a.runway).join(", "),
2925
+ alternatives
2926
+ );
2927
+ }
2928
+ const blocks = [];
2929
+ while (at < rest.length) {
2930
+ const block = this.runwayBlock(rest.slice(at, at + 1 + BLOCK.length));
2931
+ if (block) {
2932
+ blocks.push(block);
2933
+ at += 1 + BLOCK.length;
2934
+ continue;
2935
+ }
2936
+ const line = rest[at];
2937
+ const head = line.match(RUNWAY);
2938
+ const next = rest.slice(at + 1).find((l) => l.trim() !== "");
2939
+ if (head && next !== void 0 && !next.startsWith("FLAPS") && // a FLAPS line cut off by the end of the block
2940
+ !"FLAPS".startsWith(next.trim()) && !RUNWAY_START.test(next)) {
2941
+ blocks.push({
2942
+ runway: head[1],
2943
+ ...variant(head[1]),
2944
+ length: Number(head[2]),
2945
+ stoppingMargins: {}
2946
+ });
2947
+ at++;
2948
+ continue;
2949
+ }
2950
+ const last = blocks[blocks.length - 1];
2951
+ if (line.trim() !== "") {
2952
+ if (last && !RUNWAY_START.test(line)) {
2953
+ last.notes = [...last.notes ?? [], line.trim()];
2954
+ } else {
2955
+ unknown.push(...rest.slice(at).filter((l) => l.trim() !== ""));
2956
+ break;
2957
+ }
2958
+ }
2959
+ at++;
2960
+ }
2961
+ for (const block of blocks) {
2962
+ ResultFormatter.runwayLandingData(decodeResult, block);
2963
+ }
2964
+ if (unknown.length > 0) {
2965
+ ResultFormatter.unknown(decodeResult, unknown.join(nl), nl);
2966
+ }
2967
+ this.setDecodeLevel(
2968
+ decodeResult,
2969
+ true,
2970
+ i < HEADER.length ? "partial" : void 0
2971
+ );
2972
+ return decodeResult;
2973
+ }
2974
+ /** The six lines of one runway, or undefined if they are not a whole block. */
2975
+ runwayBlock(lines) {
2976
+ const head = lines[0]?.match(RUNWAY);
2977
+ if (!head || lines.length < 1 + BLOCK.length) return void 0;
2978
+ const m = BLOCK.map((re, n) => lines[n + 1].match(re));
2979
+ if (m.some((x) => !x)) return void 0;
2980
+ const [flaps, wind, speeds, mrlw, ldw] = m;
2981
+ const data = {
2982
+ runway: head[1],
2983
+ ...variant(head[1]),
2984
+ length: Number(head[2]),
2985
+ stoppingMargins: {}
2986
+ };
2987
+ if (flaps[1] !== "--") data.flaps = Number(flaps[1]);
2988
+ if (wind[1] !== void 0) {
2989
+ data.headwind = Number(wind[1]) * (wind[2] === "T" ? -1 : 1);
2990
+ data.crosswind = Number(wind[3]);
2991
+ data.crosswindSide = wind[4];
2992
+ }
2993
+ if (speeds[1] !== void 0) {
2994
+ data.vref = Number(speeds[1]);
2995
+ data.vtarget = Number(speeds[2]);
2996
+ data.additive = Number(speeds[3]);
2997
+ }
2998
+ if (!mrlw[1].startsWith("-")) data.maxLandingWeight = mrlw[1];
2999
+ data.landingWeight = pounds2(ldw[1]);
3000
+ for (const [setting, feet] of [
3001
+ [speeds[4], speeds[5]],
3002
+ [mrlw[2], mrlw[3]],
3003
+ [ldw[2], ldw[3]]
3004
+ ]) {
3005
+ if (feet !== "----") data.stoppingMargins[setting] = Number(feet);
3006
+ }
3007
+ return data;
3008
+ }
3009
+ /**
3010
+ * The runways listed after ALTERNATIVE RWYS, which run to the end of the card. Returns the
3011
+ * index of the line after them.
3012
+ */
3013
+ alternatives(lines, from, out, unknown) {
3014
+ for (let n = from; n < lines.length; n++) {
3015
+ const line = lines[n];
3016
+ if (line.trim() === "") continue;
3017
+ const m = line.match(ALTERNATIVE);
3018
+ if (!m || n === lines.length - 1) {
3019
+ unknown.push(line.trim());
3020
+ continue;
3021
+ }
3022
+ const name = m[1]?.trim();
3023
+ out.push({
3024
+ runway: m[2],
3025
+ ...name === void 0 ? {} : { name },
3026
+ ...variant(name ?? m[2])
3027
+ });
3028
+ }
3029
+ return lines.length;
3030
+ }
3031
+ };
3032
+
3033
+ // lib/plugins/Label_11_LRW.ts
3034
+ var RUNWAY2 = /^(\d{1,2}[LRC]?)\s?(?:\/(\S+))?\s+(?:(-\S+|LAHSO)\s+)?(\d{3,5})\s*$/;
3035
+ var Label_11_LRW = class extends DecoderPlugin {
3036
+ name = "label-11-lrw";
3037
+ qualifiers() {
3038
+ return {
3039
+ labels: ["11"],
3040
+ preambles: ["LRW01,"]
3041
+ };
3042
+ }
3043
+ decode(message, options = {}) {
3044
+ const decodeResult = this.initResult(message, "Landing Runways");
3045
+ const nl = message.text.includes("\r\n") ? "\r\n" : "\n";
3046
+ const first = message.text.match(/^LRW01,(\w{4}) LANDING RUNWAYS,/);
3047
+ if (!first) {
3048
+ return this.failUnknown(decodeResult, message.text, options);
3049
+ }
3050
+ ResultFormatter.landingAirport(decodeResult, first[1]);
3051
+ const lines = message.text.slice(first[0].length).split(/\r?\n/);
3052
+ for (const line of lines) {
3053
+ if (line.trim() === "") continue;
3054
+ const m = line.match(RUNWAY2);
3055
+ if (!m) {
3056
+ ResultFormatter.unknown(decodeResult, line.trim(), nl);
3057
+ continue;
3058
+ }
3059
+ const runway2 = [m[2] ? `${m[1]}/${m[2]}` : m[1], m[3]].filter((part) => part !== void 0).join(" ");
3060
+ const description = RunwayUtils.variantDescription(runway2);
3061
+ ResultFormatter.runwayLandingData(decodeResult, {
3062
+ runway: runway2,
3063
+ ...description === void 0 ? {} : { variant: description },
3064
+ length: Number(m[4]),
3065
+ stoppingMargins: {}
3066
+ });
3067
+ }
3068
+ this.setDecodeLevel(decodeResult, true);
3069
+ return decodeResult;
3070
+ }
3071
+ };
3072
+
2333
3073
  // lib/plugins/Label_12_N_Space.ts
2334
3074
  var Label_12_N_Space = class extends DecoderPlugin {
2335
3075
  name = "label-12-n-space";
@@ -3757,6 +4497,713 @@ var Label_30_Slash_EA = class extends DecoderPlugin {
3757
4497
  }
3758
4498
  };
3759
4499
 
4500
+ // lib/utils/swa_compressed.ts
4501
+ var SWA_ALPHABET = " (),-./0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ";
4502
+ var SWA_UNREAD = "\xA4";
4503
+ var SWA_LABEL_37_TABLES = {
4504
+ "01": "VIK86MRN5-0PZS4UYXACEHLOTW BGD.123Q(7J9:F)/,",
4505
+ "02": "HG.KZL97QY15WRVTN4JA06(D ,:3MPBICU/8EOFSX)2-",
4506
+ "03": "4GXE/QU5FT)20P8RSJ-169AWHLB.,37MNVCYODIKZ (:",
4507
+ "04": "1BEHMPSUY,048DG269JLOQTWZ (-/37:ACFIKNRVX).5",
4508
+ "05": "3579BDFHJLNPRTVXZ(,.02468:ACEGIKMOQSUWY )-/1",
4509
+ "06": "AGFEDCBRQPONMLKJIH:9876543210/.-,)( ZYXWVUTS",
4510
+ "07": ":.02468ACEGIKMOQSUWY )-/13579BDFHJLNPRTVXZ(,",
4511
+ "08": "1MDS/JQ.OWZ79LNR532-6(: ,APEFTVIKYB08H)U4CXG",
4512
+ "09": "6LQMSR UZYVX(7:3-5KPTACB)021,894NODGJEFIHW./"
4513
+ };
4514
+ var SWA_LABEL_40_TABLES = {
4515
+ "01": "IRXZ2MY3NOP71-S,UVAJBLCWKD(T)E.0F4Q/6G8 H:95",
4516
+ "02": "GEXHZ)QC3YJ:4D0R/IBMOFSU( NA,.K9TL16V8P75W2-",
4517
+ "03": "XY3KAJ-5B4L 0CM7DZEIQT,1(GU:VHNOS6.892/PF)RW",
4518
+ "04": "HIX2JYK3 8L4Z9M5:NO(P6)Q7,RASB-TC.DU/E0VFW1G",
4519
+ "05": "V:WAXBYCZD E(F)G,HI-J.K/L0M1N2O3P4Q5R6S7T8U9",
4520
+ "06": "RQPONMLKJIHGFEDCBA /.-,)(:9876543210ZYXWVUTS",
4521
+ "07": "CYDZE(F)G,H-I.J/K 0L1M2N3O4P5Q6R7S8T9U:VAWBX",
4522
+ "08": "FDUGB0-R A:W9C4S5EHQX)JKZTN.O6(71I/8,LVM2YP3",
4523
+ "09": "/5GK9YZHJI8N: 6LM7DFEQTURWVSA(,OPB).-C03X421"
4524
+ };
4525
+ var PASS_37 = { "*": "*", ">": "+" };
4526
+ var PASS_40 = {
4527
+ "*": "*",
4528
+ "?": "?",
4529
+ "'": "'",
4530
+ "#": "#",
4531
+ _: "_"
4532
+ };
4533
+ function lookup(table, c) {
4534
+ const i = SWA_ALPHABET.indexOf(c);
4535
+ return i < 0 ? void 0 : table[i];
4536
+ }
4537
+ function plain(table, body, pass) {
4538
+ let out = "";
4539
+ for (const c of body) {
4540
+ if (c === "\n") out += "|";
4541
+ else out += lookup(table, c) ?? pass[c] ?? SWA_UNREAD;
4542
+ }
4543
+ return out;
4544
+ }
4545
+ function decodeSwaLabel37(body) {
4546
+ const b = body.replace(/\r/g, "");
4547
+ const table = SWA_LABEL_37_TABLES[b.substring(0, 2)];
4548
+ if (!table) return void 0;
4549
+ let nl = b.indexOf("\n");
4550
+ if (nl !== 7 && b.length > 7 && lookup(table, b[7]) === ",") nl = 7;
4551
+ if (nl < 0 || nl > 8) return void 0;
4552
+ let type = "";
4553
+ for (const c of b.substring(2, Math.min(nl, 7))) {
4554
+ type += lookup(table, c) ?? SWA_UNREAD;
4555
+ }
4556
+ return { type, text: plain(table, b.substring(nl + 1), PASS_37) };
4557
+ }
4558
+ function decodeSwaLabel40(body) {
4559
+ const b = body.replace(/\r/g, "");
4560
+ if (b.startsWith("POS01,")) {
4561
+ return { type: "POS01", text: b.trim() };
4562
+ }
4563
+ const code = b[5] === "," ? b.substring(6, 8) : "";
4564
+ if (code === "00") {
4565
+ return {
4566
+ type: b.substring(0, 5),
4567
+ text: b.substring(8).replace(/\n/g, "|")
4568
+ };
4569
+ }
4570
+ const table = SWA_LABEL_40_TABLES[code];
4571
+ if (!table) return void 0;
4572
+ return {
4573
+ type: b.substring(0, 5),
4574
+ text: plain(table, b.substring(8), PASS_40)
4575
+ };
4576
+ }
4577
+ var SWA_TYPES = {
4578
+ "37": /* @__PURE__ */ new Set([
4579
+ "ADV01",
4580
+ "CLB02",
4581
+ "DIV02",
4582
+ "DVC01",
4583
+ "ETA03",
4584
+ "FCM01",
4585
+ "FCR03",
4586
+ "FFD01",
4587
+ "FHD04",
4588
+ "FLI01",
4589
+ "FTD02",
4590
+ "GAM02",
4591
+ "GSR02",
4592
+ "HGD03",
4593
+ "HLD01",
4594
+ "INN03",
4595
+ "INR04",
4596
+ "MXR03",
4597
+ "OFF02",
4598
+ "OFP01",
4599
+ "ONN02",
4600
+ "OUT03",
4601
+ "PFR03",
4602
+ "PFR04",
4603
+ "PJS01",
4604
+ "PNS01",
4605
+ "POS01",
4606
+ "POS02",
4607
+ "PRM01",
4608
+ "RRR01",
4609
+ "RTG03",
4610
+ "RTG04",
4611
+ "RTG05",
4612
+ "TAR02",
4613
+ "TDM01",
4614
+ "TOD03",
4615
+ "TRB02",
4616
+ "TXI02",
4617
+ "VXR02"
4618
+ ]),
4619
+ "40": /* @__PURE__ */ new Set([
4620
+ "ASR01",
4621
+ "DVP01",
4622
+ "FTX01",
4623
+ "GAR03",
4624
+ "GEN01",
4625
+ "PIC01",
4626
+ "POS01",
4627
+ "WFC01"
4628
+ ])
4629
+ };
4630
+ function isSwaType(type, label) {
4631
+ return SWA_TYPES[label].has(type);
4632
+ }
4633
+
4634
+ // lib/utils/swa_fields.ts
4635
+ var APT = "(?:[A-Z]{4}| {4}|)";
4636
+ var LAT = "(?:[NS] ?\\d{1,2}\\.\\d{3}|-{4}\\.-{3}|)";
4637
+ var LON = "(?:[EW] ?\\d{1,3}\\.\\d{3}|-{4}\\.-{3}|)";
4638
+ var T4 = "(?:\\d{4}|\\*{4}|-{4})";
4639
+ var FOB = "(?: ?\\d{1,2}\\.\\d|\\*\\*\\.\\*)";
4640
+ var INT = "(?: *-? *\\d+| *|-+|\\*+)";
4641
+ var MACH = "\\.\\d{3}";
4642
+ var HMS = "\\d{6}";
4643
+ var OC = "[OC]";
4644
+ var POS = [
4645
+ APT,
4646
+ APT,
4647
+ LAT,
4648
+ LON,
4649
+ INT,
4650
+ INT,
4651
+ INT,
4652
+ MACH,
4653
+ FOB,
4654
+ T4,
4655
+ INT,
4656
+ INT,
4657
+ INT,
4658
+ INT
4659
+ ];
4660
+ var ASR = " *-?\\d+(?:\\.[\\d-])?|XX\\.X";
4661
+ var SWA_LAYOUTS = {
4662
+ ASR01: [[ASR, ASR, ASR, ASR, ASR, ASR, ASR, ASR, "", ""]],
4663
+ CLB02: [[APT, APT, LAT, "", LON, FOB, T4]],
4664
+ ETA03: [[APT, APT, T4, FOB, LAT, LON]],
4665
+ FCM01: [["L[\\d-]", "R[\\d-]", "A[\\d-]", "B[\\d-]", "\\d+", T4]],
4666
+ FHD04: [
4667
+ [
4668
+ ...POS,
4669
+ "(?:OUT|OFF|ON|IN|RET IN)",
4670
+ "X",
4671
+ INT,
4672
+ " *\\d*",
4673
+ HMS,
4674
+ "(?:\\d{6}|-{6})",
4675
+ "\\d{5}"
4676
+ ]
4677
+ ],
4678
+ FLI01: [[APT, APT, T4, FOB, LAT, LON]],
4679
+ GAM02: [[APT, APT, T4, LAT, LON]],
4680
+ GAR03: [
4681
+ [
4682
+ "[A-Z0-9]{1,4} *",
4683
+ "[YN]",
4684
+ "[YN]",
4685
+ "[YN]",
4686
+ "[YN]",
4687
+ "[YN]",
4688
+ "(?:1[23]\\d\\.\\d{1,3}|999\\.999)",
4689
+ "",
4690
+ ""
4691
+ ]
4692
+ ],
4693
+ HGD03: [[...POS, INT]],
4694
+ INN03: [[APT, APT, T4, T4, FOB, OC, OC, OC, OC]],
4695
+ INR04: [[APT, APT, T4, T4, FOB, LAT, LON]],
4696
+ OUT03: [[APT, APT, T4, FOB, FOB, T4]],
4697
+ POS01: [
4698
+ [...POS, INT],
4699
+ [...POS, INT, HMS]
4700
+ ],
4701
+ POS02: [[...POS, INT, HMS]],
4702
+ RTG04: [[APT, APT, T4, T4, T4, FOB, OC, OC, OC, OC]],
4703
+ RTG05: [[APT, APT, T4, T4, T4, FOB, OC, OC, OC, OC]],
4704
+ TDM01: [[APT, APT, LAT, LON, "(?:L1|L2|CR|SV)", OC, T4, HMS]],
4705
+ TXI02: [[APT, APT, T4, FOB]],
4706
+ VXR02: [["[A-Z]{3}"]]
4707
+ };
4708
+ var SWA_PREFIX_LAYOUTS = {
4709
+ MXR03: [APT, "(?:\\d{4}|)", "[0-3]"]
4710
+ };
4711
+ function swaFields(text) {
4712
+ return text.replace(/\|/g, ",").split(",");
4713
+ }
4714
+ function swaMatchesLayout(fields, layouts) {
4715
+ return layouts.some(
4716
+ (layout) => layout.length === fields.length && layout.every((rx, i) => new RegExp(`^(?:${rx})$`).test(fields[i]))
4717
+ );
4718
+ }
4719
+ var SwaFieldWriter = class {
4720
+ constructor(decodeResult, fields) {
4721
+ this.decodeResult = decodeResult;
4722
+ this.fields = fields;
4723
+ }
4724
+ decodeResult;
4725
+ fields;
4726
+ used = /* @__PURE__ */ new Set();
4727
+ take(i) {
4728
+ this.used.add(i);
4729
+ return this.fields[i] ?? "";
4730
+ }
4731
+ /** A field with no specific ResultFormatter helper; `key` is its name in `raw`. */
4732
+ item(i, key, code, label, value) {
4733
+ this.used.add(i);
4734
+ ResultFormatter.raw(this.decodeResult, key, code, label, value);
4735
+ }
4736
+ /** Marks a field as read without writing it (it was written together with another). */
4737
+ use(i) {
4738
+ return this.take(i);
4739
+ }
4740
+ departure(i) {
4741
+ const v = this.take(i);
4742
+ if (/^[A-Z]{4}$/.test(v))
4743
+ ResultFormatter.departureAirport(this.decodeResult, v);
4744
+ }
4745
+ arrival(i) {
4746
+ const v = this.take(i);
4747
+ if (/^[A-Z]{4}$/.test(v))
4748
+ ResultFormatter.arrivalAirport(this.decodeResult, v);
4749
+ }
4750
+ /**
4751
+ * Writes the position when both coordinates are present and in range. Otherwise the
4752
+ * placeholders are consumed and any coordinate that was sent stays for `remaining`.
4753
+ */
4754
+ position(latIndex, lonIndex) {
4755
+ const lat = this.fields[latIndex] ?? "";
4756
+ const lon = this.fields[lonIndex] ?? "";
4757
+ const latitude = /^[NS] ?\d/.test(lat) ? CoordinateUtils.getDirection(lat) * Number(lat.substring(1).trim()) : NaN;
4758
+ const longitude = /^[EW] ?\d/.test(lon) ? CoordinateUtils.getDirection(lon) * Number(lon.substring(1).trim()) : NaN;
4759
+ if (Math.abs(latitude) <= 90 && Math.abs(longitude) <= 180) {
4760
+ this.used.add(latIndex);
4761
+ this.used.add(lonIndex);
4762
+ ResultFormatter.position(this.decodeResult, { latitude, longitude });
4763
+ return;
4764
+ }
4765
+ if (!/^[NS] ?\d/.test(lat)) this.used.add(latIndex);
4766
+ if (!/^[EW] ?\d/.test(lon)) this.used.add(lonIndex);
4767
+ }
4768
+ /** A whole number, or undefined for a placeholder. */
4769
+ int(i) {
4770
+ const v = this.take(i).replace(/ /g, "");
4771
+ return /^-?\d+$/.test(v) ? Number(v) : void 0;
4772
+ }
4773
+ altitude(i) {
4774
+ const v = this.int(i);
4775
+ if (v !== void 0) ResultFormatter.altitude(this.decodeResult, v);
4776
+ }
4777
+ /** Fuel on board, sent in thousands of pounds; written in pounds. */
4778
+ fuel(i) {
4779
+ const v = this.take(i).trim();
4780
+ if (/^\d+\.\d$/.test(v)) {
4781
+ ResultFormatter.currentFuel(
4782
+ this.decodeResult,
4783
+ Math.round(Number(v) * 1e3)
4784
+ );
4785
+ }
4786
+ }
4787
+ /**
4788
+ * HHMM or HHMMSS as seconds of the day. A placeholder is consumed; a time out of range is
4789
+ * left for `remaining`.
4790
+ */
4791
+ tod(i) {
4792
+ const v = this.fields[i] ?? "";
4793
+ if (/^(?:[01]\d|2[0-3])[0-5]\d(?:[0-5]\d)?$/.test(v)) {
4794
+ this.used.add(i);
4795
+ return DateTimeUtils.convertHHMMSSToTod(v);
4796
+ }
4797
+ if (!/^\d+$/.test(v)) this.used.add(i);
4798
+ return void 0;
4799
+ }
4800
+ /** Writes a time through a ResultFormatter helper such as `out`, `in` or `timestamp`. */
4801
+ time(i, write) {
4802
+ const t = this.tod(i);
4803
+ if (t !== void 0) write(this.decodeResult, t);
4804
+ }
4805
+ /** A time with no ResultFormatter helper, written as HH:MM:SS. */
4806
+ timeItem(i, key, code, label) {
4807
+ const t = this.tod(i);
4808
+ if (t !== void 0) {
4809
+ this.item(i, key, code, label, DateTimeUtils.timestampToString(t));
4810
+ }
4811
+ }
4812
+ eta(i) {
4813
+ this.time(i, (r, t) => ResultFormatter.eta(r, t));
4814
+ }
4815
+ /** Fields not written above, non-empty, to `remaining`. */
4816
+ finish() {
4817
+ const rest = this.fields.filter(
4818
+ (v, i) => !this.used.has(i) && v.trim() !== ""
4819
+ );
4820
+ if (rest.length > 0) ResultFormatter.unknownArr(this.decodeResult, rest);
4821
+ return rest.length;
4822
+ }
4823
+ };
4824
+
4825
+ // lib/plugins/Label_37_SWA.ts
4826
+ var DESCRIPTIONS = {
4827
+ ADV01: "Ride Conditions Report",
4828
+ CLB02: "Climb Report",
4829
+ DVC01: "Divert Plan (Crew Copy)",
4830
+ ETA03: "ETA Report",
4831
+ FCM01: "Engine and Fuel State",
4832
+ FCR03: "Runway Condition Report",
4833
+ FHD04: "Frequency Handoff Report",
4834
+ FLI01: "Flight Initialization",
4835
+ FTD02: "Free Text to Dispatch",
4836
+ GAM02: "Gate Assignment Request",
4837
+ HGD03: "HowGozit Report",
4838
+ HLD01: "Holding Report",
4839
+ INN03: "In Report",
4840
+ INR04: "In Range Report",
4841
+ MXR03: "Maintenance Report",
4842
+ OFF02: "Off Report",
4843
+ ONN02: "On Report",
4844
+ OUT03: "Out Report",
4845
+ PFR03: "Post Flight Report",
4846
+ POS01: "Position Report",
4847
+ POS02: "Position Report",
4848
+ RRR01: "Reroute Report",
4849
+ RTG04: "Return to Gate Report",
4850
+ RTG05: "Return to Gate Report",
4851
+ TDM01: "Door Report",
4852
+ TRB02: "Turbulence Report",
4853
+ TXI02: "Taxi Report",
4854
+ VXR02: "Voice Call Request"
4855
+ };
4856
+ var DOORS = {
4857
+ L1: "L1 (forward entry)",
4858
+ L2: "L2 (aft entry)",
4859
+ CR: "Cargo",
4860
+ SV: "Service"
4861
+ };
4862
+ var MXR_CODES = {
4863
+ "0": "Nothing to report",
4864
+ "1": "Status option",
4865
+ "2": "Category and text",
4866
+ "3": "Free text"
4867
+ };
4868
+ var Label_37_SWA = class extends DecoderPlugin {
4869
+ name = "label-37-swa";
4870
+ qualifiers() {
4871
+ return {
4872
+ labels: ["37"],
4873
+ preambles: ["01", "02", "03", "04", "05", "06", "07", "08", "09"]
4874
+ };
4875
+ }
4876
+ decode(message, options = {}) {
4877
+ const decodeResult = this.defaultResult();
4878
+ decodeResult.decoder.name = this.name;
4879
+ decodeResult.message = message;
4880
+ const d = decodeSwaLabel37(message.text);
4881
+ if (!d || !isSwaType(d.type, "37")) {
4882
+ if (options.debug) {
4883
+ console.log(`Decoder: Unknown 37 message: ${message.text}`);
4884
+ }
4885
+ ResultFormatter.unknown(decodeResult, message.text);
4886
+ decodeResult.formatted.description = "Unknown";
4887
+ return decodeResult;
4888
+ }
4889
+ decodeResult.decoded = true;
4890
+ decodeResult.formatted.description = DESCRIPTIONS[d.type] ?? "Southwest Message";
4891
+ decodeResult.raw.preamble = d.type;
4892
+ const text = d.text;
4893
+ const fields = swaFields(text);
4894
+ const layouts = SWA_LAYOUTS[d.type];
4895
+ const prefix = SWA_PREFIX_LAYOUTS[d.type];
4896
+ const clean = !text.includes(SWA_UNREAD);
4897
+ if (clean && layouts && swaMatchesLayout(fields, layouts)) {
4898
+ const rest = this.writeFields(decodeResult, d.type, fields);
4899
+ decodeResult.decoder.decodeLevel = rest === 0 ? "full" : "partial";
4900
+ return decodeResult;
4901
+ }
4902
+ if (clean && prefix && swaMatchesLayout(fields.slice(0, prefix.length), [prefix])) {
4903
+ const rest = this.writeMaintenance(decodeResult, text, fields);
4904
+ decodeResult.decoder.decodeLevel = rest === 0 ? "full" : "partial";
4905
+ return decodeResult;
4906
+ }
4907
+ if (clean && !layouts && !prefix) {
4908
+ const lines = text.split("|");
4909
+ const route = lines[0].match(/^([A-Z]{4}),([A-Z]{4})$/);
4910
+ if (route && d.type === "FTD02") {
4911
+ ResultFormatter.departureAirport(decodeResult, route[1]);
4912
+ ResultFormatter.arrivalAirport(decodeResult, route[2]);
4913
+ lines.shift();
4914
+ }
4915
+ ResultFormatter.text(decodeResult, lines.join("\n"));
4916
+ decodeResult.decoder.decodeLevel = "full";
4917
+ return decodeResult;
4918
+ }
4919
+ ResultFormatter.text(decodeResult, text.split("|").join("\n"));
4920
+ decodeResult.decoder.decodeLevel = "partial";
4921
+ return decodeResult;
4922
+ }
4923
+ /** Writes a laid-out type's fields; returns how many went to `remaining`. */
4924
+ writeFields(decodeResult, type, fields) {
4925
+ const w = new SwaFieldWriter(decodeResult, fields);
4926
+ switch (type) {
4927
+ case "POS01":
4928
+ case "POS02":
4929
+ case "HGD03":
4930
+ case "FHD04": {
4931
+ w.departure(0);
4932
+ w.arrival(1);
4933
+ w.position(2, 3);
4934
+ w.altitude(4);
4935
+ const ias = w.int(5);
4936
+ if (ias !== void 0)
4937
+ ResultFormatter.indicatedAirspeed(decodeResult, ias);
4938
+ const tas = w.int(6);
4939
+ if (tas !== void 0) ResultFormatter.airspeed(decodeResult, tas);
4940
+ ResultFormatter.mach(decodeResult, Number(w.use(7)));
4941
+ w.fuel(8);
4942
+ w.eta(9);
4943
+ const track = w.int(10);
4944
+ if (track !== void 0) ResultFormatter.track(decodeResult, track);
4945
+ ResultFormatter.wind(decodeResult, w.int(11), w.int(12));
4946
+ const sat = w.int(13);
4947
+ if (sat !== void 0)
4948
+ ResultFormatter.temperature(decodeResult, `${sat}`);
4949
+ const gwIndex = type === "FHD04" ? 16 : 14;
4950
+ const gw = w.int(gwIndex);
4951
+ if (gw !== void 0) ResultFormatter.grossWeight(decodeResult, gw);
4952
+ if (type !== "FHD04" && fields.length === 16) {
4953
+ w.time(15, (r, t) => ResultFormatter.timestamp(r, t));
4954
+ }
4955
+ if (type === "FHD04") {
4956
+ w.item(14, "event", "EVT", "Event", fields[14]);
4957
+ w.use(15);
4958
+ const freq = w.use(19);
4959
+ if (/^1\d{5}$/.test(freq)) {
4960
+ w.item(
4961
+ 19,
4962
+ "frequency",
4963
+ "FREQ",
4964
+ "ACARS Frequency",
4965
+ `${freq.substring(0, 3)}.${freq.substring(3)} MHz`
4966
+ );
4967
+ }
4968
+ }
4969
+ break;
4970
+ }
4971
+ case "GAM02":
4972
+ w.departure(0);
4973
+ w.arrival(1);
4974
+ w.eta(2);
4975
+ w.position(3, 4);
4976
+ break;
4977
+ case "CLB02":
4978
+ w.departure(0);
4979
+ w.arrival(1);
4980
+ w.position(2, 4);
4981
+ w.fuel(5);
4982
+ w.eta(6);
4983
+ break;
4984
+ case "ETA03":
4985
+ w.departure(0);
4986
+ w.arrival(1);
4987
+ w.eta(2);
4988
+ w.fuel(3);
4989
+ w.position(4, 5);
4990
+ break;
4991
+ case "INR04":
4992
+ w.departure(0);
4993
+ w.arrival(1);
4994
+ w.eta(2);
4995
+ w.time(3, (r, t) => ResultFormatter.timestamp(r, t));
4996
+ w.fuel(4);
4997
+ w.position(5, 6);
4998
+ break;
4999
+ case "FLI01":
5000
+ w.departure(0);
5001
+ w.arrival(1);
5002
+ w.time(2, (r, t) => ResultFormatter.timestamp(r, t));
5003
+ w.fuel(3);
5004
+ w.position(4, 5);
5005
+ break;
5006
+ case "OUT03":
5007
+ w.departure(0);
5008
+ w.arrival(1);
5009
+ w.time(2, (r, t) => ResultFormatter.out(r, t));
5010
+ w.fuel(3);
5011
+ w.timeItem(5, "front_door_closed_time", "L1CLS", "Front Door Closed");
5012
+ break;
5013
+ case "TXI02":
5014
+ w.departure(0);
5015
+ w.arrival(1);
5016
+ w.timeItem(2, "taxi_start_time", "TAXI", "Taxi Start Time");
5017
+ w.fuel(3);
5018
+ break;
5019
+ case "INN03":
5020
+ w.departure(0);
5021
+ w.arrival(1);
5022
+ w.time(2, (r, t) => ResultFormatter.in(r, t));
5023
+ w.timeItem(3, "front_door_open_time", "L1OPN", "Front Door Opened");
5024
+ w.fuel(4);
5025
+ break;
5026
+ case "RTG04":
5027
+ case "RTG05":
5028
+ w.departure(0);
5029
+ w.arrival(1);
5030
+ w.time(2, (r, t) => ResultFormatter.out(r, t));
5031
+ w.time(3, (r, t) => ResultFormatter.in(r, t));
5032
+ w.time(4, (r, t) => ResultFormatter.timestamp(r, t));
5033
+ w.fuel(5);
5034
+ break;
5035
+ case "TDM01": {
5036
+ w.departure(0);
5037
+ w.arrival(1);
5038
+ w.position(2, 3);
5039
+ ResultFormatter.door_event(
5040
+ decodeResult,
5041
+ DOORS[w.use(4)],
5042
+ w.use(5) === "O" ? "open" : "closed"
5043
+ );
5044
+ const md = fields[6];
5045
+ if (/^(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])$/.test(md)) {
5046
+ w.use(6);
5047
+ ResultFormatter.month(decodeResult, Number(md.substring(0, 2)));
5048
+ ResultFormatter.day(decodeResult, Number(md.substring(2)));
5049
+ } else if (!/^\d+$/.test(md)) {
5050
+ w.use(6);
5051
+ }
5052
+ w.time(7, (r, t) => ResultFormatter.timestamp(r, t));
5053
+ break;
5054
+ }
5055
+ case "FCM01": {
5056
+ const names = {
5057
+ L: "Left Engine",
5058
+ R: "Right Engine",
5059
+ A: "APU"
5060
+ };
5061
+ for (let i = 0; i < 3; i++) {
5062
+ const v = fields[i];
5063
+ if (v[1] === "0" || v[1] === "1") {
5064
+ w.item(
5065
+ i,
5066
+ `${names[v[0]].toLowerCase().replace(" ", "_")}`,
5067
+ v[0],
5068
+ names[v[0]],
5069
+ v[1] === "1" ? "running" : "off"
5070
+ );
5071
+ }
5072
+ }
5073
+ ResultFormatter.currentFuel(decodeResult, Number(w.use(4)) * 100);
5074
+ w.time(5, (r, t) => ResultFormatter.timestamp(r, t));
5075
+ break;
5076
+ }
5077
+ case "VXR02":
5078
+ w.item(0, "station_iata", "STN", "Station", fields[0]);
5079
+ break;
5080
+ }
5081
+ return w.finish();
5082
+ }
5083
+ /** MXR03: destination, ETA, code, then the category and text; returns how many header
5084
+ * fields went to `remaining`. */
5085
+ writeMaintenance(decodeResult, text, fields) {
5086
+ const w = new SwaFieldWriter(decodeResult, fields.slice(0, 3));
5087
+ w.arrival(0);
5088
+ w.eta(1);
5089
+ w.item(
5090
+ 2,
5091
+ "maintenance_code",
5092
+ "MXCODE",
5093
+ "Report Code",
5094
+ `${fields[2]} (${MXR_CODES[fields[2]]})`
5095
+ );
5096
+ const header = text.match(/^[^,|]*[,|][^,|]*[,|][^,|]*[,|]?/);
5097
+ const body = text.substring(header ? header[0].length : 0);
5098
+ if (body.trim() !== "") {
5099
+ ResultFormatter.text(decodeResult, body.split("|").join("\n"));
5100
+ }
5101
+ return w.finish();
5102
+ }
5103
+ };
5104
+
5105
+ // lib/plugins/Label_40_SWA.ts
5106
+ var DESCRIPTIONS2 = {
5107
+ ASR01: "Approach Stability Report",
5108
+ DVP01: "Divert Plan",
5109
+ FTX01: "Dispatch Free Text",
5110
+ GAR03: "Gate Assignment Response",
5111
+ GEN01: "Company Message",
5112
+ PIC01: "Part 117 Duty Extension Request",
5113
+ POS01: "Position Report Request",
5114
+ WFC01: "Field Condition Report"
5115
+ };
5116
+ var ASR01 = [
5117
+ [
5118
+ "reverser_distance",
5119
+ "REVDIST",
5120
+ "Threshold to Thrust Reverser Deployment (feet)"
5121
+ ],
5122
+ ["max_ias", "MAXIAS", "Highest IAS, 1000 feet to Touchdown (knots)"],
5123
+ ["min_ias", "MINIAS", "Lowest IAS, to Touchdown (knots)"],
5124
+ ["touchdown_distance", "TDDIST", "Threshold to Touchdown (feet)"],
5125
+ ["glideslope_above", "GSHIGH", "Largest Deviation Above Glideslope (dots)"],
5126
+ ["glideslope_below", "GSLOW", "Largest Deviation Below Glideslope (dots)"],
5127
+ ["max_pitch", "MAXPITCH", "Highest Pitch, Flare and Landing (degrees)"],
5128
+ ["tailstrike_pitch", "TSPITCH", "Tail Strike Pitch Limit (degrees)"]
5129
+ ];
5130
+ var Label_40_SWA = class extends DecoderPlugin {
5131
+ name = "label-40-swa";
5132
+ qualifiers() {
5133
+ return {
5134
+ labels: ["40"]
5135
+ };
5136
+ }
5137
+ decode(message, options = {}) {
5138
+ const decodeResult = this.defaultResult();
5139
+ decodeResult.decoder.name = this.name;
5140
+ decodeResult.message = message;
5141
+ const d = decodeSwaLabel40(message.text);
5142
+ if (!d || !isSwaType(d.type, "40")) {
5143
+ if (options.debug) {
5144
+ console.log(`Decoder: Unknown 40 message: ${message.text}`);
5145
+ }
5146
+ ResultFormatter.unknown(decodeResult, message.text);
5147
+ decodeResult.formatted.description = "Unknown";
5148
+ return decodeResult;
5149
+ }
5150
+ decodeResult.decoded = true;
5151
+ decodeResult.formatted.description = DESCRIPTIONS2[d.type] ?? "Southwest Message";
5152
+ decodeResult.raw.preamble = d.type;
5153
+ if (d.type === "POS01") {
5154
+ const m = d.text.match(/^POS01,Y,(\d{2})$/);
5155
+ if (m) {
5156
+ ResultFormatter.raw(
5157
+ decodeResult,
5158
+ "report_interval",
5159
+ "RPTINT",
5160
+ "Position Report Interval",
5161
+ `${Number(m[1])} minutes`,
5162
+ Number(m[1])
5163
+ );
5164
+ decodeResult.decoder.decodeLevel = "full";
5165
+ } else {
5166
+ ResultFormatter.unknown(decodeResult, d.text);
5167
+ decodeResult.decoder.decodeLevel = "partial";
5168
+ }
5169
+ return decodeResult;
5170
+ }
5171
+ const text = d.text;
5172
+ const fields = swaFields(text);
5173
+ const layouts = SWA_LAYOUTS[d.type];
5174
+ const clean = !text.includes(SWA_UNREAD);
5175
+ if (clean && layouts && swaMatchesLayout(fields, layouts)) {
5176
+ const w = new SwaFieldWriter(decodeResult, fields);
5177
+ if (d.type === "ASR01") {
5178
+ ASR01.forEach(([key, code, label], i) => {
5179
+ const v = fields[i].trim();
5180
+ if (v !== "XX.X") w.item(i, key, code, label, v);
5181
+ else w.use(i);
5182
+ });
5183
+ } else if (d.type === "GAR03") {
5184
+ ResultFormatter.arrivalGate(decodeResult, w.use(0).trim());
5185
+ if (fields[6] !== "999.999") {
5186
+ w.item(
5187
+ 6,
5188
+ "ramp_frequency",
5189
+ "RAMPFREQ",
5190
+ "Ramp Frequency",
5191
+ `${fields[6]} MHz`
5192
+ );
5193
+ } else {
5194
+ w.use(6);
5195
+ }
5196
+ }
5197
+ const rest = w.finish();
5198
+ decodeResult.decoder.decodeLevel = rest === 0 ? "full" : "partial";
5199
+ return decodeResult;
5200
+ }
5201
+ ResultFormatter.text(decodeResult, text.split("|").join("\n"));
5202
+ decodeResult.decoder.decodeLevel = clean && !layouts ? "full" : "partial";
5203
+ return decodeResult;
5204
+ }
5205
+ };
5206
+
3760
5207
  // lib/plugins/Label_44_ETA.ts
3761
5208
  var Label_44_ETA = class extends DecoderPlugin {
3762
5209
  name = "label-44-eta";
@@ -4795,7 +6242,7 @@ var Label_H1_76401 = class extends DecoderPlugin {
4795
6242
  );
4796
6243
  if (position) {
4797
6244
  waypoints.push({
4798
- name: CoordinateUtils.coordinateString(position),
6245
+ name: line.substring(0, 13),
4799
6246
  latitude: position.latitude,
4800
6247
  longitude: position.longitude,
4801
6248
  time: DateTimeUtils.convertHHMMSSToTod(line.substring(13, 19))
@@ -5042,11 +6489,11 @@ var Label_H1_M_POS = class extends DecoderPlugin {
5042
6489
  const timestamp = fields[2].trim();
5043
6490
  if (timestamp.length === 6) {
5044
6491
  const day = Number(timestamp.substring(0, 2));
5045
- const tod = DateTimeUtils.convertHHMMSSToTod(
6492
+ const tod2 = DateTimeUtils.convertHHMMSSToTod(
5046
6493
  timestamp.substring(2) + "00"
5047
6494
  );
5048
6495
  ResultFormatter.day(decodeResult, day);
5049
- ResultFormatter.timestamp(decodeResult, tod);
6496
+ ResultFormatter.timestamp(decodeResult, tod2);
5050
6497
  }
5051
6498
  const lat = parseFloat(fields[3].replace(/\s/g, ""));
5052
6499
  const lon = parseFloat(fields[4].replace(/\s/g, ""));
@@ -5440,6 +6887,135 @@ var Label_H1_StarPOS = class extends DecoderPlugin {
5440
6887
  }
5441
6888
  };
5442
6889
 
6890
+ // lib/plugins/Label_H1_SWA_Track.ts
6891
+ var PHASES = {
6892
+ TO: "Takeoff",
6893
+ IC: "Initial Climb",
6894
+ CL: "Climb",
6895
+ CR: "Cruise",
6896
+ ER: "En Route",
6897
+ DC: "Descent",
6898
+ AP: "Approach",
6899
+ RO: "Rollout"
6900
+ };
6901
+ var POINT = /^([NS]\d{4})\.(\d),([EW]\d{5})\.(\d),(\d{2})(\d{2})(\d{2}),\s?(-?\d+),\s?(-?\d+\.\d),(\d{3}),(\d{3}),([A-Z]{2}),(\d{5}),([01]),$/;
6902
+ var Label_H1_SWA_Track = class extends DecoderPlugin {
6903
+ name = "label-h1-swa-track";
6904
+ qualifiers() {
6905
+ return {
6906
+ labels: ["H1"],
6907
+ preambles: ["++76502,", "++86501,", "#DFB++76502,", "#DFB++86501,"]
6908
+ };
6909
+ }
6910
+ decode(message, options = {}) {
6911
+ const decodeResult = this.initResult(message, "Track Report");
6912
+ const nl = message.text.includes("\r\n") ? "\r\n" : "\n";
6913
+ const lines = message.text.replace(/#DFB/g, "").split(/\r?\n/);
6914
+ const header = lines[0].replace(/^\+\+/, "").split(",");
6915
+ if (header.length !== 9 || !/^\d{6}$/.test(header[3])) {
6916
+ return this.failUnknown(decodeResult, message.text, options);
6917
+ }
6918
+ const unknown = [];
6919
+ if (/^N\w+$/.test(header[1])) {
6920
+ ResultFormatter.tail(decodeResult, header[1]);
6921
+ } else if (/^\d{1,4}$/.test(header[1])) {
6922
+ ResultFormatter.raw(
6923
+ decodeResult,
6924
+ "ship_number",
6925
+ "SHIP",
6926
+ "Ship Number",
6927
+ header[1]
6928
+ );
6929
+ } else if (header[1] !== "XXX") {
6930
+ unknown.push(header[1]);
6931
+ }
6932
+ ResultFormatter.raw(
6933
+ decodeResult,
6934
+ "aircraft_type",
6935
+ "ACTYPE",
6936
+ "Aircraft Type",
6937
+ header[2]
6938
+ );
6939
+ const d = header[3];
6940
+ ResultFormatter.raw(
6941
+ decodeResult,
6942
+ "report_date",
6943
+ "DATE",
6944
+ "Report Date",
6945
+ `20${d.slice(0, 2)}-${d.slice(2, 4)}-${d.slice(4, 6)}`
6946
+ );
6947
+ ResultFormatter.flightNumber(decodeResult, header[4]);
6948
+ ResultFormatter.departureAirport(decodeResult, header[5]);
6949
+ ResultFormatter.arrivalAirport(decodeResult, header[6]);
6950
+ if (/^\d{1,15}$/.test(header[7])) {
6951
+ ResultFormatter.raw(
6952
+ decodeResult,
6953
+ "leg_counter",
6954
+ "LEGS",
6955
+ "Leg Counter",
6956
+ `${Number(header[7])}`,
6957
+ Number(header[7])
6958
+ );
6959
+ } else {
6960
+ unknown.push(header[7]);
6961
+ }
6962
+ ResultFormatter.raw(
6963
+ decodeResult,
6964
+ "software_version",
6965
+ "SWVER",
6966
+ "Software Version",
6967
+ header[8]
6968
+ );
6969
+ if (unknown.length > 0) ResultFormatter.unknownArr(decodeResult, unknown);
6970
+ const count = lines[1]?.match(/^(\d+),?$/)?.[1];
6971
+ if (lines[1] !== void 0 && count === void 0) {
6972
+ ResultFormatter.unknown(decodeResult, lines[1], nl);
6973
+ }
6974
+ for (const line of lines.slice(2)) {
6975
+ if (line === "" || line === ":") continue;
6976
+ const m = line.match(POINT);
6977
+ if (!m || !(Number(m[5]) >= 1 && Number(m[5]) <= 31) || Number(m[6]) > 23 || Number(m[7]) > 59 || Number(m[10]) > 360 || Number(m[1].slice(3)) > 59 || Number(m[3].slice(4)) > 59) {
6978
+ ResultFormatter.unknown(decodeResult, line, nl);
6979
+ continue;
6980
+ }
6981
+ const position = CoordinateUtils.decodeStringCoordinatesDecimalMinutes(
6982
+ `${m[1]}${m[2]}${m[3]}${m[4]}`
6983
+ );
6984
+ if (!position || Math.abs(position.latitude) > 90 || Math.abs(position.longitude) > 180) {
6985
+ ResultFormatter.unknown(decodeResult, line, nl);
6986
+ continue;
6987
+ }
6988
+ ResultFormatter.trackPoint(decodeResult, {
6989
+ name: `${m[1]}.${m[2]}${m[3]}.${m[4]}`,
6990
+ ...position,
6991
+ day: Number(m[5]),
6992
+ time: Number(m[6]) * 3600 + Number(m[7]) * 60,
6993
+ altitude: Number(m[8]),
6994
+ // -00.0 is sent; keep it as 0
6995
+ temperature: Number(m[9]) || 0,
6996
+ windDirection: Number(m[10]),
6997
+ windSpeed: Number(m[11]),
6998
+ phase: PHASES[m[12]] ?? m[12],
6999
+ humiditySensor: m[14] === "1",
7000
+ ...m[14] === "1" && Number(m[13]) > 0 ? { waterVapor: Number(m[13]) } : {}
7001
+ });
7002
+ }
7003
+ const points = decodeResult.raw.route?.waypoints ?? [];
7004
+ const last = points[points.length - 1];
7005
+ if (last) {
7006
+ ResultFormatter.position(decodeResult, {
7007
+ latitude: last.latitude,
7008
+ longitude: last.longitude
7009
+ });
7010
+ ResultFormatter.altitude(decodeResult, last.altitude);
7011
+ ResultFormatter.timestamp(decodeResult, last.time);
7012
+ }
7013
+ const counted = count !== void 0 && Number(count) === points.length;
7014
+ this.setDecodeLevel(decodeResult, true, counted ? void 0 : "partial");
7015
+ return decodeResult;
7016
+ }
7017
+ };
7018
+
5443
7019
  // lib/plugins/Label_H1_WRN.ts
5444
7020
  var Label_H1_WRN = class extends DecoderPlugin {
5445
7021
  name = "label-h1-wrn";
@@ -5538,7 +7114,7 @@ var Label_H2_02E = class extends DecoderPlugin {
5538
7114
  if (text.length !== 32 || !pos) {
5539
7115
  return null;
5540
7116
  }
5541
- const tod = DateTimeUtils.convertHHMMSSToTod(text.substring(13, 17));
7117
+ const tod2 = DateTimeUtils.convertHHMMSSToTod(text.substring(13, 17));
5542
7118
  const flightLevel = parseInt(text.substring(17, 20), 10);
5543
7119
  const tempSign = text[21] === "M" ? -1 : 1;
5544
7120
  const tempDegreesRaw = parseInt(text.substring(22, 25), 10);
@@ -5553,7 +7129,7 @@ var Label_H2_02E = class extends DecoderPlugin {
5553
7129
  name: posString,
5554
7130
  latitude: pos.latitude,
5555
7131
  longitude: pos.longitude,
5556
- time: tod
7132
+ time: tod2
5557
7133
  },
5558
7134
  flightLevel,
5559
7135
  windDirection,
@@ -6861,8 +8437,11 @@ var pluginClasses = [
6861
8437
  Label_ColonComma,
6862
8438
  Label_5Z_Slash,
6863
8439
  Label_10_LDR,
8440
+ Label_10_LRW,
6864
8441
  Label_10_POS,
6865
8442
  Label_10_Slash,
8443
+ Label_11_LDR,
8444
+ Label_11_LRW,
6866
8445
  Label_12_N_Space,
6867
8446
  Label_12_POS,
6868
8447
  Label_13Through18_Slash,
@@ -6886,6 +8465,8 @@ var pluginClasses = [
6886
8465
  Label_2P_FM4,
6887
8466
  Label_2P_FM5,
6888
8467
  Label_30_Slash_EA,
8468
+ Label_37_SWA,
8469
+ Label_40_SWA,
6889
8470
  Label_44_ETA,
6890
8471
  Label_44_IN,
6891
8472
  Label_44_OFF,
@@ -6911,6 +8492,7 @@ var pluginClasses = [
6911
8492
  Label_H1_Paren,
6912
8493
  Label_H1_WRN,
6913
8494
  Label_H1_StarPOS,
8495
+ Label_H1_SWA_Track,
6914
8496
  Label_H2_02E,
6915
8497
  Label_HX,
6916
8498
  Label_58,