@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.
@@ -53,8 +53,8 @@ var CoordinateUtils = class _CoordinateUtils {
53
53
  };
54
54
  }
55
55
  static coordinateString(coords) {
56
- const latDir = coords.latitude > 0 ? "N" : "S";
57
- const lonDir = coords.longitude > 0 ? "E" : "W";
56
+ const latDir = coords.latitude >= 0 ? "N" : "S";
57
+ const lonDir = coords.longitude >= 0 ? "E" : "W";
58
58
  return `${Math.abs(coords.latitude).toFixed(3)} ${latDir}, ${Math.abs(coords.longitude).toFixed(3)} ${lonDir}`;
59
59
  }
60
60
  static getDirection(coord) {
@@ -104,16 +104,16 @@ var DateTimeUtils = class {
104
104
  const h = Number(time.substring(0, 2));
105
105
  const m = Number(time.substring(2, 4));
106
106
  const s = Number(time.substring(4, 6));
107
- const tod = h * 3600 + m * 60 + s;
108
- return tod;
107
+ const tod2 = h * 3600 + m * 60 + s;
108
+ return tod2;
109
109
  }
110
110
  static convertDayTimeToTod(time) {
111
111
  const d = Number(time.substring(0, 2));
112
112
  const h = Number(time.substring(2, 4));
113
113
  const m = Number(time.substring(4, 6));
114
114
  const s = Number(time.substring(6, 8));
115
- const tod = d * 86400 + h * 3600 + m * 60 + s;
116
- return tod;
115
+ const tod2 = d * 86400 + h * 3600 + m * 60 + s;
116
+ return tod2;
117
117
  }
118
118
  /**
119
119
  *
@@ -389,6 +389,16 @@ var ResultFormatter = class {
389
389
  value: decodeResult.raw.alternate_icao
390
390
  });
391
391
  }
392
+ /** The airport landing data is for: the destination, or the airport of a divert. */
393
+ static landingAirport(decodeResult, value) {
394
+ decodeResult.raw.landing_icao = value;
395
+ decodeResult.formatted.items.push({
396
+ type: "icao",
397
+ code: "LAND",
398
+ label: "Landing Airport",
399
+ value: decodeResult.raw.landing_icao
400
+ });
401
+ }
392
402
  static eta(decodeResult, time) {
393
403
  if (isNaN(time)) {
394
404
  return;
@@ -579,6 +589,144 @@ var ResultFormatter = class {
579
589
  value: `${decodeResult.raw.heading}`
580
590
  });
581
591
  }
592
+ static indicatedAirspeed(decodeResult, value) {
593
+ if (isNaN(value)) {
594
+ return;
595
+ }
596
+ decodeResult.raw.indicated_airspeed = value;
597
+ decodeResult.formatted.items.push({
598
+ type: "indicated_airspeed",
599
+ code: "IAS",
600
+ label: "Indicated Airspeed",
601
+ value: `${decodeResult.raw.indicated_airspeed} knots`
602
+ });
603
+ }
604
+ static track(decodeResult, value) {
605
+ if (isNaN(value)) {
606
+ return;
607
+ }
608
+ decodeResult.raw.track = value;
609
+ decodeResult.formatted.items.push({
610
+ type: "track",
611
+ code: "TRK",
612
+ label: "Track",
613
+ value: `${decodeResult.raw.track} degrees`
614
+ });
615
+ }
616
+ /** Wind at the aircraft; either half may be missing. */
617
+ static wind(decodeResult, direction, speed) {
618
+ const hasDirection = direction !== void 0 && !isNaN(direction);
619
+ const hasSpeed = speed !== void 0 && !isNaN(speed);
620
+ if (hasDirection) decodeResult.raw.wind_direction = direction;
621
+ if (hasSpeed) decodeResult.raw.wind_speed = speed;
622
+ if (!hasDirection && !hasSpeed) {
623
+ return;
624
+ }
625
+ decodeResult.formatted.items.push({
626
+ type: "wind",
627
+ code: "WIND",
628
+ label: !hasSpeed ? "Wind Direction" : !hasDirection ? "Wind Speed" : "Wind",
629
+ value: [
630
+ hasDirection ? `${direction} degrees` : "",
631
+ hasSpeed ? `${speed} knots` : ""
632
+ ].filter((s) => s !== "").join(" at ")
633
+ });
634
+ }
635
+ static variableWind(decodeResult, speed) {
636
+ if (isNaN(speed)) {
637
+ return;
638
+ }
639
+ decodeResult.raw.wind_speed = speed;
640
+ decodeResult.raw.wind_variable = true;
641
+ decodeResult.formatted.items.push({
642
+ type: "wind",
643
+ code: "WIND",
644
+ label: "Wind",
645
+ value: `variable at ${speed} knots`
646
+ });
647
+ }
648
+ /**
649
+ * One point of a recorded track. The track is the route as flown, so each point is added to
650
+ * the route's waypoints; the item shows what the aircraft measured there as well.
651
+ */
652
+ static trackPoint(decodeResult, point) {
653
+ decodeResult.raw.route = {
654
+ ...decodeResult.raw.route,
655
+ waypoints: [...decodeResult.raw.route?.waypoints ?? [], point]
656
+ };
657
+ decodeResult.formatted.items.push({
658
+ type: "track_point",
659
+ code: "TRKPT",
660
+ label: "Track Point",
661
+ 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`)
662
+ });
663
+ }
664
+ static runwayLandingData(decodeResult, data) {
665
+ decodeResult.raw.landing_runways = [
666
+ ...decodeResult.raw.landing_runways ?? [],
667
+ data
668
+ ];
669
+ const parts = [];
670
+ if (data.length !== void 0) parts.push(`${data.length} feet`);
671
+ if (data.flaps !== void 0) parts.push(`flaps ${data.flaps}`);
672
+ if (data.headwind !== void 0) {
673
+ parts.push(
674
+ data.headwind < 0 ? `tailwind ${-data.headwind} knots` : `headwind ${data.headwind} knots`
675
+ );
676
+ }
677
+ if (data.crosswind !== void 0) {
678
+ const side = { L: " left", R: " right" }[data.crosswindSide ?? "L"];
679
+ parts.push(
680
+ `crosswind ${data.crosswind} knots${data.crosswindSide ? side : ""}`
681
+ );
682
+ }
683
+ if (data.vref !== void 0) parts.push(`VREF ${data.vref} knots`);
684
+ if (data.vtarget !== void 0) {
685
+ const add = data.additive === void 0 ? "" : ` (${data.additive < 0 ? "" : "+"}${data.additive})`;
686
+ parts.push(`VTGT ${data.vtarget} knots${add}`);
687
+ }
688
+ if (data.maxLandingWeight !== void 0) {
689
+ parts.push(`MRLW ${data.maxLandingWeight}`);
690
+ }
691
+ if (data.landingWeight !== void 0) {
692
+ parts.push(`LDW ${data.landingWeight} pounds`);
693
+ }
694
+ const margins = Object.entries(data.stoppingMargins).map(
695
+ ([setting, feet]) => `${setting} ${feet} feet`
696
+ );
697
+ if (margins.length > 0) {
698
+ parts.push(`stopping margin ${margins.join(", ")}`);
699
+ }
700
+ parts.push(...data.notes ?? []);
701
+ const variant2 = data.variant === void 0 ? "" : ` (${data.variant})`;
702
+ decodeResult.formatted.items.push({
703
+ type: "runway_landing_data",
704
+ code: "RWYLDG",
705
+ label: "Runway Landing Data",
706
+ value: `${data.runway}${variant2}: ${parts.join(", ")}`
707
+ });
708
+ }
709
+ static grossWeight(decodeResult, pounds3) {
710
+ if (isNaN(pounds3)) {
711
+ return;
712
+ }
713
+ decodeResult.raw.gross_weight = pounds3;
714
+ decodeResult.formatted.items.push({
715
+ type: "gross_weight",
716
+ code: "GWT",
717
+ label: "Gross Weight",
718
+ value: `${decodeResult.raw.gross_weight} pounds`
719
+ });
720
+ }
721
+ static arrivalGate(decodeResult, value) {
722
+ decodeResult.raw.arrival_gate = value;
723
+ decodeResult.formatted.items.push({
724
+ type: "gate",
725
+ code: "AGATE",
726
+ label: "Arrival Gate",
727
+ value: decodeResult.raw.arrival_gate
728
+ });
729
+ }
582
730
  static tail(decodeResult, value) {
583
731
  decodeResult.raw.tail = value;
584
732
  decodeResult.formatted.items.push({
@@ -898,6 +1046,14 @@ var ResultFormatter = class {
898
1046
  value: decodeResult.raw.sequence_response.toString()
899
1047
  });
900
1048
  }
1049
+ /**
1050
+ * A field with no specific helper: `key` is its name in `raw` and its item type. `rawValue`
1051
+ * is stored in `raw` when it differs from the text shown (a number, for example).
1052
+ */
1053
+ static raw(decodeResult, key, code, label, value, rawValue = value) {
1054
+ decodeResult.raw[key] = rawValue;
1055
+ decodeResult.formatted.items.push({ type: key, code, label, value });
1056
+ }
901
1057
  static unknown(decodeResult, value, sep = ",") {
902
1058
  if (!decodeResult.remaining.text) decodeResult.remaining.text = value;
903
1059
  else decodeResult.remaining.text += sep + value;
@@ -1243,13 +1399,13 @@ function addCompanyRoute(decodeResult, value) {
1243
1399
  const segments = value.split(".");
1244
1400
  const parens_idx = segments[0].indexOf("(");
1245
1401
  let name;
1246
- let runway;
1402
+ let runway2;
1247
1403
  if (parens_idx === -1) {
1248
1404
  name = segments[0];
1249
1405
  } else {
1250
1406
  name = segments[0].slice(0, parens_idx);
1251
1407
  const close_paren_idx = segments[0].indexOf(")");
1252
- runway = segments[0].slice(
1408
+ runway2 = segments[0].slice(
1253
1409
  parens_idx + 1,
1254
1410
  close_paren_idx === -1 ? void 0 : close_paren_idx
1255
1411
  );
@@ -1260,7 +1416,7 @@ function addCompanyRoute(decodeResult, value) {
1260
1416
  }
1261
1417
  decodeResult.raw.company_route = {
1262
1418
  name,
1263
- runway,
1419
+ runway: runway2,
1264
1420
  waypoints
1265
1421
  };
1266
1422
  decodeResult.formatted.items.push({
@@ -2096,7 +2252,54 @@ var Label_5Z_Slash = class extends DecoderPlugin {
2096
2252
  }
2097
2253
  };
2098
2254
 
2255
+ // lib/utils/swa_request.ts
2256
+ var AIRLINES = { "189": "Southwest" };
2257
+ function swaRequestHeader(decodeResult, airlineId, layout, software) {
2258
+ const airline = AIRLINES[airlineId];
2259
+ ResultFormatter.raw(
2260
+ decodeResult,
2261
+ "airline_id",
2262
+ "AIRLINE",
2263
+ "Airline ID",
2264
+ airline ? `${airlineId} (${airline})` : airlineId,
2265
+ airlineId
2266
+ );
2267
+ ResultFormatter.raw(
2268
+ decodeResult,
2269
+ "request_layout",
2270
+ "LAYOUT",
2271
+ "Request Layout",
2272
+ layout
2273
+ );
2274
+ ResultFormatter.raw(
2275
+ decodeResult,
2276
+ "software_version",
2277
+ "SWVER",
2278
+ "Software Version",
2279
+ software
2280
+ );
2281
+ }
2282
+
2099
2283
  // lib/plugins/Label_10_LDR.ts
2284
+ function pounds(value) {
2285
+ if (value.trim() === "") return void 0;
2286
+ const thousands = Number(value);
2287
+ return Number.isFinite(thousands) ? Math.round(thousands * 1e3) : void 0;
2288
+ }
2289
+ function runway(value) {
2290
+ const [rwy, suffix = ""] = value.trim().split("/");
2291
+ return suffix.trim() === "" ? rwy.trim() : `${rwy.trim()}/${suffix.trim()}`;
2292
+ }
2293
+ var RUNWAY_CONDITION = {
2294
+ 6: "dry",
2295
+ 5: "good",
2296
+ 4: "good to medium",
2297
+ 3: "medium",
2298
+ 2: "medium to poor",
2299
+ 1: "poor",
2300
+ 0: "nil"
2301
+ };
2302
+ var FLAPS = { "0": 30, "1": 40, "2": 15 };
2100
2303
  var Label_10_LDR = class extends DecoderPlugin {
2101
2304
  name = "label-10-ldr";
2102
2305
  qualifiers() {
@@ -2108,18 +2311,13 @@ var Label_10_LDR = class extends DecoderPlugin {
2108
2311
  decode(message2, options = {}) {
2109
2312
  const decodeResult = this.defaultResult();
2110
2313
  decodeResult.decoder.name = this.name;
2111
- decodeResult.formatted.description = "Position Report";
2314
+ decodeResult.formatted.description = "Landing Data Request";
2112
2315
  decodeResult.message = message2;
2113
2316
  const parts = message2.text.split(",");
2114
- if (parts.length < 17) {
2115
- if (options.debug) {
2116
- console.log(`Decoder: Unknown 10 message: ${message2.text}`);
2117
- }
2118
- ResultFormatter.unknown(decodeResult, message2.text);
2119
- decodeResult.decoded = false;
2120
- decodeResult.decoder.decodeLevel = "none";
2121
- return decodeResult;
2317
+ if (parts.length < 31) {
2318
+ return this.failUnknown(decodeResult, message2.text, options);
2122
2319
  }
2320
+ swaRequestHeader(decodeResult, parts[1], parts[2], parts[3]);
2123
2321
  const lat = parts[5];
2124
2322
  const lon = parts[6];
2125
2323
  const position = {
@@ -2127,25 +2325,209 @@ var Label_10_LDR = class extends DecoderPlugin {
2127
2325
  longitude: (lon[0] === "E" ? 1 : -1) * Number(lon.substring(1).trim())
2128
2326
  };
2129
2327
  ResultFormatter.position(decodeResult, position);
2130
- ResultFormatter.altitude(decodeResult, Number(parts[7]));
2328
+ if (parts[7].trim() !== "") {
2329
+ ResultFormatter.altitude(decodeResult, Number(parts[7]));
2330
+ }
2331
+ const fuel = pounds(parts[8]);
2332
+ if (fuel !== void 0) ResultFormatter.currentFuel(decodeResult, fuel);
2131
2333
  ResultFormatter.departureAirport(decodeResult, parts[9]);
2132
2334
  ResultFormatter.arrivalAirport(decodeResult, parts[10]);
2133
- ResultFormatter.alternateAirport(decodeResult, parts[11]);
2134
- ResultFormatter.arrivalRunway(decodeResult, parts[12].split("/")[0]);
2135
- const altRwy = [parts[13].split("/")[0], parts[14].split("/")[0]].filter((r) => r != "").join(",");
2335
+ ResultFormatter.landingAirport(decodeResult, parts[11]);
2336
+ ResultFormatter.arrivalRunway(decodeResult, runway(parts[12]));
2337
+ const altRwy = [runway(parts[13]), runway(parts[14])].filter((r) => r != "").join(",");
2136
2338
  if (altRwy != "") {
2137
2339
  ResultFormatter.alternateRunway(decodeResult, altRwy);
2138
2340
  }
2139
- ResultFormatter.unknownArr(decodeResult, [
2140
- ...parts.slice(0, 5),
2141
- ...parts.slice(15)
2142
- ]);
2341
+ const unknown = [parts[4]];
2342
+ const condition = parts[15].trim();
2343
+ if (/^[1-6]$/.test(condition)) {
2344
+ const code = 6 - Number(condition);
2345
+ ResultFormatter.raw(
2346
+ decodeResult,
2347
+ "runway_condition_code",
2348
+ "RCC",
2349
+ "Runway Condition Code",
2350
+ `${code} (${RUNWAY_CONDITION[code]})`,
2351
+ code
2352
+ );
2353
+ } else if (condition !== "0") {
2354
+ unknown.push(parts[15]);
2355
+ }
2356
+ const flaps = FLAPS[parts[16].trim()];
2357
+ if (flaps !== void 0) {
2358
+ ResultFormatter.raw(
2359
+ decodeResult,
2360
+ "landing_flaps",
2361
+ "FLAPS",
2362
+ "Landing Flaps",
2363
+ `${flaps}`,
2364
+ flaps
2365
+ );
2366
+ } else {
2367
+ unknown.push(parts[16]);
2368
+ }
2369
+ const [windDir, windSpeed, gust, temp, dew, altimeter] = parts.slice(17, 23).map((field) => {
2370
+ const value = field.trim();
2371
+ if (/^-?\d{1,4}$/.test(value)) return Number(value);
2372
+ if (value !== "") unknown.push(field);
2373
+ return void 0;
2374
+ });
2375
+ if (windDir !== void 0 || windSpeed !== void 0) {
2376
+ ResultFormatter.wind(decodeResult, windDir, windSpeed);
2377
+ }
2378
+ if (gust !== void 0) {
2379
+ ResultFormatter.raw(
2380
+ decodeResult,
2381
+ "wind_gust",
2382
+ "GUST",
2383
+ "Wind Gust",
2384
+ `${gust} knots`,
2385
+ gust
2386
+ );
2387
+ }
2388
+ ResultFormatter.temperature(
2389
+ decodeResult,
2390
+ temp === void 0 ? "" : `${temp}`
2391
+ );
2392
+ if (dew !== void 0) {
2393
+ ResultFormatter.raw(
2394
+ decodeResult,
2395
+ "dew_point",
2396
+ "DEWPT",
2397
+ "Dew Point (C)",
2398
+ `${dew} degrees`,
2399
+ dew
2400
+ );
2401
+ }
2402
+ if (altimeter !== void 0) {
2403
+ const inHg = altimeter / 100;
2404
+ ResultFormatter.raw(
2405
+ decodeResult,
2406
+ "altimeter",
2407
+ "ALTIM",
2408
+ "Altimeter",
2409
+ `${inHg.toFixed(2)} inHg`,
2410
+ inHg
2411
+ );
2412
+ }
2413
+ const antiIce = parts[23].trim();
2414
+ if (/^[1-9]$/.test(antiIce)) {
2415
+ ResultFormatter.raw(
2416
+ decodeResult,
2417
+ "anti_ice",
2418
+ "ANTIICE",
2419
+ "Anti-Ice",
2420
+ antiIce === "1" ? "engine" : antiIce,
2421
+ Number(antiIce)
2422
+ );
2423
+ } else if (antiIce !== "0") {
2424
+ unknown.push(parts[23]);
2425
+ }
2426
+ const flags = [
2427
+ [parts[24], "enroute_icing", "ICING", "En Route Icing"],
2428
+ // the card says HGS AIII: 113 of 113 pairs, 32 requests
2429
+ [parts[25], "hgs_approach", "HGS", "HGS Approach"]
2430
+ ];
2431
+ for (const [value, key, code, label] of flags) {
2432
+ if (value.trim() === "1") {
2433
+ ResultFormatter.raw(decodeResult, key, code, label, "yes", true);
2434
+ } else if (value.trim() !== "0") {
2435
+ unknown.push(value);
2436
+ }
2437
+ }
2438
+ const nonNormal = parts[26].trim();
2439
+ if (/^\d+$/.test(nonNormal) && Number(nonNormal) !== 0) {
2440
+ ResultFormatter.raw(
2441
+ decodeResult,
2442
+ "non_normal_configuration",
2443
+ "NNC",
2444
+ "Non-Normal Configuration",
2445
+ `${Number(nonNormal)}`,
2446
+ Number(nonNormal)
2447
+ );
2448
+ } else if (!/^0+$/.test(nonNormal)) {
2449
+ unknown.push(parts[26]);
2450
+ }
2451
+ if (parts[27].trim() !== "") unknown.push(parts[27]);
2452
+ const weights = [
2453
+ ["zero_fuel_weight", "ZFW", "Zero Fuel Weight", parts[28]],
2454
+ ["landing_fuel", "LFUEL", "Landing Fuel", parts[29]],
2455
+ ["landing_weight", "LDW", "Landing Weight", parts[30]]
2456
+ ];
2457
+ for (const [key, code, label, value] of weights) {
2458
+ const amount = pounds(value);
2459
+ if (amount === void 0) continue;
2460
+ ResultFormatter.raw(
2461
+ decodeResult,
2462
+ key,
2463
+ code,
2464
+ label,
2465
+ `${amount} pounds`,
2466
+ amount
2467
+ );
2468
+ }
2469
+ const gradient = (parts[31] ?? "").trim();
2470
+ if (/^\d+$/.test(gradient)) {
2471
+ ResultFormatter.raw(
2472
+ decodeResult,
2473
+ "missed_approach_gradient",
2474
+ "MAGRAD",
2475
+ "Missed Approach Climb Gradient",
2476
+ `${Number(gradient)} feet per nautical mile`,
2477
+ Number(gradient)
2478
+ );
2479
+ } else if (gradient !== "" && gradient !== "----.") {
2480
+ unknown.push(parts[31]);
2481
+ }
2482
+ const missedAltitude = (parts[32] ?? "").trim();
2483
+ if (/^\d+$/.test(missedAltitude)) {
2484
+ ResultFormatter.raw(
2485
+ decodeResult,
2486
+ "missed_approach_altitude",
2487
+ "MAALT",
2488
+ "Missed Approach Altitude",
2489
+ `${Number(missedAltitude)} feet`,
2490
+ Number(missedAltitude)
2491
+ );
2492
+ } else if (missedAltitude !== "") {
2493
+ unknown.push(parts[32]);
2494
+ }
2495
+ unknown.push(...parts.slice(33).filter((field) => field.trim() !== ""));
2496
+ ResultFormatter.unknownArr(decodeResult, unknown);
2143
2497
  decodeResult.decoded = true;
2144
2498
  decodeResult.decoder.decodeLevel = "partial";
2145
2499
  return decodeResult;
2146
2500
  }
2147
2501
  };
2148
2502
 
2503
+ // lib/plugins/Label_10_LRW.ts
2504
+ var Label_10_LRW = class extends DecoderPlugin {
2505
+ name = "label-10-lrw";
2506
+ qualifiers() {
2507
+ return {
2508
+ labels: ["10"],
2509
+ preambles: ["LRW01,"]
2510
+ };
2511
+ }
2512
+ decode(message2, options = {}) {
2513
+ const decodeResult = this.initResult(message2, "Landing Runways Request");
2514
+ const parts = message2.text.split(",");
2515
+ if (parts.length < 8 || parts[7].trim() === "") {
2516
+ return this.failUnknown(decodeResult, message2.text, options);
2517
+ }
2518
+ swaRequestHeader(decodeResult, parts[1], parts[2], parts[3]);
2519
+ ResultFormatter.departureAirport(decodeResult, parts[6]);
2520
+ ResultFormatter.arrivalAirport(decodeResult, parts[7]);
2521
+ ResultFormatter.unknownArr(decodeResult, [
2522
+ parts[4],
2523
+ parts[5],
2524
+ ...parts.slice(8).filter((field) => field.trim() !== "")
2525
+ ]);
2526
+ this.setDecodeLevel(decodeResult, true);
2527
+ return decodeResult;
2528
+ }
2529
+ };
2530
+
2149
2531
  // lib/plugins/Label_10_POS.ts
2150
2532
  var Label_10_POS = class extends DecoderPlugin {
2151
2533
  name = "label-10-pos";
@@ -2245,6 +2627,364 @@ var Label_10_Slash = class extends DecoderPlugin {
2245
2627
  }
2246
2628
  };
2247
2629
 
2630
+ // lib/utils/runway_utils.ts
2631
+ var SIDES = {
2632
+ N: "north",
2633
+ NE: "northeast",
2634
+ E: "east",
2635
+ SE: "southeast",
2636
+ S: "south",
2637
+ SW: "southwest",
2638
+ W: "west",
2639
+ NW: "northwest"
2640
+ };
2641
+ var RunwayUtils = class {
2642
+ /**
2643
+ * Describe the condition that names a runway variant in Southwest landing data, e.g.
2644
+ * `11/AIB -E1804F`: the runway, the variant's code and its condition.
2645
+ *
2646
+ * -E1804F is 1804 feet taken off the east end, -SWCBL the arresting cable at the southwest
2647
+ * end. Checked on the LRW01 runway lists of six airports, where every variant's length is
2648
+ * the runway's less the feet given, or less 1500 feet for a cable. LAHSO is sent as written.
2649
+ *
2650
+ * @param name - the runway as sent, with its variant
2651
+ * @returns a description of the condition, or undefined when the name has none
2652
+ */
2653
+ static variantDescription(name) {
2654
+ const text = name.trim();
2655
+ const cut = text.match(/-(NE|NW|SE|SW|N|E|S|W)(\d+)F$/);
2656
+ if (cut) {
2657
+ return `${Number(cut[2])} feet off the ${SIDES[cut[1]]} end`;
2658
+ }
2659
+ const cable = text.match(/-(NE|NW|SE|SW|N|E|S|W)CBL$/);
2660
+ if (cable) {
2661
+ return `arresting cable at the ${SIDES[cable[1]]} end`;
2662
+ }
2663
+ if (/(^|\s)LAHSO$/.test(text)) {
2664
+ return "land and hold short";
2665
+ }
2666
+ return void 0;
2667
+ }
2668
+ };
2669
+
2670
+ // lib/plugins/Label_11_LDR.ts
2671
+ function tod(hours, minutes) {
2672
+ const h = Number(hours);
2673
+ const m = Number(minutes);
2674
+ return h <= 23 && m <= 59 ? h * 3600 + m * 60 : void 0;
2675
+ }
2676
+ function pounds2(value) {
2677
+ return Math.round(Number(value) * 1e3);
2678
+ }
2679
+ function variant(runway2) {
2680
+ const description = RunwayUtils.variantDescription(runway2);
2681
+ return description === void 0 ? {} : { variant: description };
2682
+ }
2683
+ var HEADER = [
2684
+ /^LDR01,(\w{4}) LANDING DATA,FLT(?:\s+METAR-(\d{2})(\d{2})Z)?\s*$/,
2685
+ /^(\d{1,4})\s+WIND\s+LDW\s*$/,
2686
+ /^TIME\s+(\d{3}|VRB)\/(\d{2,3})G(\d{2,3}|--)\s+(\d+\.\d)\s*$/,
2687
+ /^(\d{2})(\d{2})Z\s+OAT\/DEW\s*(\S*)\s*$/,
2688
+ /^\s+(-?\d{2,3})\/(-?\d{2,3})\s*(\S*)\s*$/,
2689
+ /^\s+QNH\s*(\S*)\s*$/,
2690
+ // QNH in inches of mercury, or hectopascals outside the US
2691
+ /^\s+(\d{2}\.\d{2}|\d{4})\s*(\S*)\s*$/
2692
+ ];
2693
+ var RUNWAY = /^(\d{1,2}[LRC]?(?:\/\S+)?(?: -\S+)?)\s+(\d{1,5})\s*$/;
2694
+ var RUNWAY_START = /^\d{1,2}[LRC]?(?:\/\S+)?(?: -\S+)?(?:\s{2,}\d*)?\s*$/;
2695
+ var ALTERNATIVES = "--- ALTERNATIVE RWYS ---";
2696
+ var ALTERNATIVE = /^(?:(.+?)\s{2,})?(\d{1,2}[LRC]?(?:\/\S+)?)\s*$/;
2697
+ var BLOCK = [
2698
+ /^FLAPS (\d{2}|--)\s*$/,
2699
+ /^VREF\/VTGT\s+(?:(\d{2})([HT])\/(\d{2})([LR])|---\/---)\s*$/,
2700
+ /^\s*(?:(\d{3})\/(\d{3}) ([+-]\d+)|---\/---)\s+(AB\d)\s+(\d+|----)\s*$/,
2701
+ /^MRLW (\d+\.\d\/\S|-+\/-)\s+(AB\d)\s+(\d+|----)\s*$/,
2702
+ /^\s*LDW (\d+\.\d)\s+(ABM)\s+(\d+|----)\s*$/
2703
+ ];
2704
+ var Label_11_LDR = class extends DecoderPlugin {
2705
+ name = "label-11-ldr";
2706
+ qualifiers() {
2707
+ return {
2708
+ labels: ["11"],
2709
+ preambles: ["LDR01,"]
2710
+ };
2711
+ }
2712
+ decode(message2, options = {}) {
2713
+ const decodeResult = this.initResult(message2, "Landing Data");
2714
+ const nl = message2.text.includes("\r\n") ? "\r\n" : "\n";
2715
+ const lines = message2.text.split(/\r?\n/);
2716
+ const first = lines[0].match(HEADER[0]);
2717
+ if (!first) {
2718
+ return this.failUnknown(decodeResult, message2.text, options);
2719
+ }
2720
+ const unknown = [];
2721
+ ResultFormatter.landingAirport(decodeResult, first[1]);
2722
+ const metar = first[2] === void 0 ? void 0 : tod(first[2], first[3]);
2723
+ if (first[2] !== void 0 && metar === void 0) {
2724
+ unknown.push(`METAR-${first[2]}${first[3]}Z`);
2725
+ }
2726
+ if (metar !== void 0) {
2727
+ ResultFormatter.raw(
2728
+ decodeResult,
2729
+ "metar_time",
2730
+ "METAR",
2731
+ "METAR Time",
2732
+ DateTimeUtils.timestampToString(metar),
2733
+ metar
2734
+ );
2735
+ }
2736
+ const runways = [];
2737
+ let i = 1;
2738
+ for (; i < HEADER.length && i < lines.length; i++) {
2739
+ const m = lines[i].match(HEADER[i]);
2740
+ if (!m) break;
2741
+ if (i === 1) {
2742
+ ResultFormatter.flightNumber(decodeResult, m[1]);
2743
+ } else if (i === 2) {
2744
+ if (m[1] === "VRB") {
2745
+ ResultFormatter.variableWind(decodeResult, Number(m[2]));
2746
+ } else {
2747
+ ResultFormatter.wind(decodeResult, Number(m[1]), Number(m[2]));
2748
+ }
2749
+ if (m[3] !== "--") {
2750
+ ResultFormatter.raw(
2751
+ decodeResult,
2752
+ "wind_gust",
2753
+ "GUST",
2754
+ "Wind Gust",
2755
+ `${Number(m[3])} knots`,
2756
+ Number(m[3])
2757
+ );
2758
+ }
2759
+ ResultFormatter.raw(
2760
+ decodeResult,
2761
+ "landing_weight",
2762
+ "LDW",
2763
+ "Landing Weight",
2764
+ `${pounds2(m[4])} pounds`,
2765
+ pounds2(m[4])
2766
+ );
2767
+ } else if (i === 3) {
2768
+ const time = tod(m[1], m[2]);
2769
+ if (time === void 0) {
2770
+ unknown.push(`${m[1]}${m[2]}Z`);
2771
+ } else {
2772
+ ResultFormatter.timestamp(decodeResult, time);
2773
+ }
2774
+ runways.push(m[3]);
2775
+ } else if (i === 4) {
2776
+ ResultFormatter.temperature(decodeResult, `${Number(m[1])}`);
2777
+ ResultFormatter.raw(
2778
+ decodeResult,
2779
+ "dew_point",
2780
+ "DEWPT",
2781
+ "Dew Point (C)",
2782
+ `${Number(m[2])} degrees`,
2783
+ Number(m[2])
2784
+ );
2785
+ runways.push(m[3]);
2786
+ } else if (i === 5) {
2787
+ runways.push(m[1]);
2788
+ } else {
2789
+ ResultFormatter.raw(
2790
+ decodeResult,
2791
+ "altimeter",
2792
+ "ALTIM",
2793
+ "Altimeter",
2794
+ `${m[1]} ${m[1].includes(".") ? "inHg" : "hPa"}`,
2795
+ Number(m[1])
2796
+ );
2797
+ runways.push(m[2]);
2798
+ }
2799
+ }
2800
+ const listed = runways.filter((r) => r !== "");
2801
+ if (listed.length > 0) {
2802
+ ResultFormatter.raw(
2803
+ decodeResult,
2804
+ "runways",
2805
+ "RWYS",
2806
+ "Runways",
2807
+ listed.join(", "),
2808
+ listed
2809
+ );
2810
+ }
2811
+ const rest = lines.slice(i);
2812
+ let at = 0;
2813
+ const alternatives = [];
2814
+ if (rest[0]?.trim() === "REMARKS") {
2815
+ const remarks = [];
2816
+ for (at = 1; at < rest.length && !RUNWAY_START.test(rest[at]); at++) {
2817
+ if (rest[at].trim() === ALTERNATIVES) {
2818
+ at = this.alternatives(rest, at + 1, alternatives, unknown);
2819
+ break;
2820
+ }
2821
+ if (rest[at].trim() !== "") remarks.push(rest[at].trim());
2822
+ }
2823
+ if (remarks.length > 0) {
2824
+ ResultFormatter.raw(
2825
+ decodeResult,
2826
+ "remarks",
2827
+ "RMK",
2828
+ "Remarks",
2829
+ remarks.join(" ")
2830
+ );
2831
+ }
2832
+ }
2833
+ if (alternatives.length > 0) {
2834
+ ResultFormatter.raw(
2835
+ decodeResult,
2836
+ "alternative_runways",
2837
+ "ALTRWY",
2838
+ "Alternative Runways",
2839
+ alternatives.map((a) => a.variant ? `${a.runway} (${a.variant})` : a.runway).join(", "),
2840
+ alternatives
2841
+ );
2842
+ }
2843
+ const blocks = [];
2844
+ while (at < rest.length) {
2845
+ const block = this.runwayBlock(rest.slice(at, at + 1 + BLOCK.length));
2846
+ if (block) {
2847
+ blocks.push(block);
2848
+ at += 1 + BLOCK.length;
2849
+ continue;
2850
+ }
2851
+ const line = rest[at];
2852
+ const head = line.match(RUNWAY);
2853
+ const next = rest.slice(at + 1).find((l) => l.trim() !== "");
2854
+ if (head && next !== void 0 && !next.startsWith("FLAPS") && // a FLAPS line cut off by the end of the block
2855
+ !"FLAPS".startsWith(next.trim()) && !RUNWAY_START.test(next)) {
2856
+ blocks.push({
2857
+ runway: head[1],
2858
+ ...variant(head[1]),
2859
+ length: Number(head[2]),
2860
+ stoppingMargins: {}
2861
+ });
2862
+ at++;
2863
+ continue;
2864
+ }
2865
+ const last = blocks[blocks.length - 1];
2866
+ if (line.trim() !== "") {
2867
+ if (last && !RUNWAY_START.test(line)) {
2868
+ last.notes = [...last.notes ?? [], line.trim()];
2869
+ } else {
2870
+ unknown.push(...rest.slice(at).filter((l) => l.trim() !== ""));
2871
+ break;
2872
+ }
2873
+ }
2874
+ at++;
2875
+ }
2876
+ for (const block of blocks) {
2877
+ ResultFormatter.runwayLandingData(decodeResult, block);
2878
+ }
2879
+ if (unknown.length > 0) {
2880
+ ResultFormatter.unknown(decodeResult, unknown.join(nl), nl);
2881
+ }
2882
+ this.setDecodeLevel(
2883
+ decodeResult,
2884
+ true,
2885
+ i < HEADER.length ? "partial" : void 0
2886
+ );
2887
+ return decodeResult;
2888
+ }
2889
+ /** The six lines of one runway, or undefined if they are not a whole block. */
2890
+ runwayBlock(lines) {
2891
+ const head = lines[0]?.match(RUNWAY);
2892
+ if (!head || lines.length < 1 + BLOCK.length) return void 0;
2893
+ const m = BLOCK.map((re, n) => lines[n + 1].match(re));
2894
+ if (m.some((x) => !x)) return void 0;
2895
+ const [flaps, wind, speeds, mrlw, ldw] = m;
2896
+ const data = {
2897
+ runway: head[1],
2898
+ ...variant(head[1]),
2899
+ length: Number(head[2]),
2900
+ stoppingMargins: {}
2901
+ };
2902
+ if (flaps[1] !== "--") data.flaps = Number(flaps[1]);
2903
+ if (wind[1] !== void 0) {
2904
+ data.headwind = Number(wind[1]) * (wind[2] === "T" ? -1 : 1);
2905
+ data.crosswind = Number(wind[3]);
2906
+ data.crosswindSide = wind[4];
2907
+ }
2908
+ if (speeds[1] !== void 0) {
2909
+ data.vref = Number(speeds[1]);
2910
+ data.vtarget = Number(speeds[2]);
2911
+ data.additive = Number(speeds[3]);
2912
+ }
2913
+ if (!mrlw[1].startsWith("-")) data.maxLandingWeight = mrlw[1];
2914
+ data.landingWeight = pounds2(ldw[1]);
2915
+ for (const [setting, feet] of [
2916
+ [speeds[4], speeds[5]],
2917
+ [mrlw[2], mrlw[3]],
2918
+ [ldw[2], ldw[3]]
2919
+ ]) {
2920
+ if (feet !== "----") data.stoppingMargins[setting] = Number(feet);
2921
+ }
2922
+ return data;
2923
+ }
2924
+ /**
2925
+ * The runways listed after ALTERNATIVE RWYS, which run to the end of the card. Returns the
2926
+ * index of the line after them.
2927
+ */
2928
+ alternatives(lines, from, out, unknown) {
2929
+ for (let n = from; n < lines.length; n++) {
2930
+ const line = lines[n];
2931
+ if (line.trim() === "") continue;
2932
+ const m = line.match(ALTERNATIVE);
2933
+ if (!m || n === lines.length - 1) {
2934
+ unknown.push(line.trim());
2935
+ continue;
2936
+ }
2937
+ const name = m[1]?.trim();
2938
+ out.push({
2939
+ runway: m[2],
2940
+ ...name === void 0 ? {} : { name },
2941
+ ...variant(name ?? m[2])
2942
+ });
2943
+ }
2944
+ return lines.length;
2945
+ }
2946
+ };
2947
+
2948
+ // lib/plugins/Label_11_LRW.ts
2949
+ var RUNWAY2 = /^(\d{1,2}[LRC]?)\s?(?:\/(\S+))?\s+(?:(-\S+|LAHSO)\s+)?(\d{3,5})\s*$/;
2950
+ var Label_11_LRW = class extends DecoderPlugin {
2951
+ name = "label-11-lrw";
2952
+ qualifiers() {
2953
+ return {
2954
+ labels: ["11"],
2955
+ preambles: ["LRW01,"]
2956
+ };
2957
+ }
2958
+ decode(message2, options = {}) {
2959
+ const decodeResult = this.initResult(message2, "Landing Runways");
2960
+ const nl = message2.text.includes("\r\n") ? "\r\n" : "\n";
2961
+ const first = message2.text.match(/^LRW01,(\w{4}) LANDING RUNWAYS,/);
2962
+ if (!first) {
2963
+ return this.failUnknown(decodeResult, message2.text, options);
2964
+ }
2965
+ ResultFormatter.landingAirport(decodeResult, first[1]);
2966
+ const lines = message2.text.slice(first[0].length).split(/\r?\n/);
2967
+ for (const line of lines) {
2968
+ if (line.trim() === "") continue;
2969
+ const m = line.match(RUNWAY2);
2970
+ if (!m) {
2971
+ ResultFormatter.unknown(decodeResult, line.trim(), nl);
2972
+ continue;
2973
+ }
2974
+ const runway2 = [m[2] ? `${m[1]}/${m[2]}` : m[1], m[3]].filter((part) => part !== void 0).join(" ");
2975
+ const description = RunwayUtils.variantDescription(runway2);
2976
+ ResultFormatter.runwayLandingData(decodeResult, {
2977
+ runway: runway2,
2978
+ ...description === void 0 ? {} : { variant: description },
2979
+ length: Number(m[4]),
2980
+ stoppingMargins: {}
2981
+ });
2982
+ }
2983
+ this.setDecodeLevel(decodeResult, true);
2984
+ return decodeResult;
2985
+ }
2986
+ };
2987
+
2248
2988
  // lib/plugins/Label_12_N_Space.ts
2249
2989
  var Label_12_N_Space = class extends DecoderPlugin {
2250
2990
  name = "label-12-n-space";
@@ -3672,6 +4412,713 @@ var Label_30_Slash_EA = class extends DecoderPlugin {
3672
4412
  }
3673
4413
  };
3674
4414
 
4415
+ // lib/utils/swa_compressed.ts
4416
+ var SWA_ALPHABET = " (),-./0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ";
4417
+ var SWA_UNREAD = "\xA4";
4418
+ var SWA_LABEL_37_TABLES = {
4419
+ "01": "VIK86MRN5-0PZS4UYXACEHLOTW BGD.123Q(7J9:F)/,",
4420
+ "02": "HG.KZL97QY15WRVTN4JA06(D ,:3MPBICU/8EOFSX)2-",
4421
+ "03": "4GXE/QU5FT)20P8RSJ-169AWHLB.,37MNVCYODIKZ (:",
4422
+ "04": "1BEHMPSUY,048DG269JLOQTWZ (-/37:ACFIKNRVX).5",
4423
+ "05": "3579BDFHJLNPRTVXZ(,.02468:ACEGIKMOQSUWY )-/1",
4424
+ "06": "AGFEDCBRQPONMLKJIH:9876543210/.-,)( ZYXWVUTS",
4425
+ "07": ":.02468ACEGIKMOQSUWY )-/13579BDFHJLNPRTVXZ(,",
4426
+ "08": "1MDS/JQ.OWZ79LNR532-6(: ,APEFTVIKYB08H)U4CXG",
4427
+ "09": "6LQMSR UZYVX(7:3-5KPTACB)021,894NODGJEFIHW./"
4428
+ };
4429
+ var SWA_LABEL_40_TABLES = {
4430
+ "01": "IRXZ2MY3NOP71-S,UVAJBLCWKD(T)E.0F4Q/6G8 H:95",
4431
+ "02": "GEXHZ)QC3YJ:4D0R/IBMOFSU( NA,.K9TL16V8P75W2-",
4432
+ "03": "XY3KAJ-5B4L 0CM7DZEIQT,1(GU:VHNOS6.892/PF)RW",
4433
+ "04": "HIX2JYK3 8L4Z9M5:NO(P6)Q7,RASB-TC.DU/E0VFW1G",
4434
+ "05": "V:WAXBYCZD E(F)G,HI-J.K/L0M1N2O3P4Q5R6S7T8U9",
4435
+ "06": "RQPONMLKJIHGFEDCBA /.-,)(:9876543210ZYXWVUTS",
4436
+ "07": "CYDZE(F)G,H-I.J/K 0L1M2N3O4P5Q6R7S8T9U:VAWBX",
4437
+ "08": "FDUGB0-R A:W9C4S5EHQX)JKZTN.O6(71I/8,LVM2YP3",
4438
+ "09": "/5GK9YZHJI8N: 6LM7DFEQTURWVSA(,OPB).-C03X421"
4439
+ };
4440
+ var PASS_37 = { "*": "*", ">": "+" };
4441
+ var PASS_40 = {
4442
+ "*": "*",
4443
+ "?": "?",
4444
+ "'": "'",
4445
+ "#": "#",
4446
+ _: "_"
4447
+ };
4448
+ function lookup(table, c) {
4449
+ const i = SWA_ALPHABET.indexOf(c);
4450
+ return i < 0 ? void 0 : table[i];
4451
+ }
4452
+ function plain(table, body, pass) {
4453
+ let out = "";
4454
+ for (const c of body) {
4455
+ if (c === "\n") out += "|";
4456
+ else out += lookup(table, c) ?? pass[c] ?? SWA_UNREAD;
4457
+ }
4458
+ return out;
4459
+ }
4460
+ function decodeSwaLabel37(body) {
4461
+ const b = body.replace(/\r/g, "");
4462
+ const table = SWA_LABEL_37_TABLES[b.substring(0, 2)];
4463
+ if (!table) return void 0;
4464
+ let nl = b.indexOf("\n");
4465
+ if (nl !== 7 && b.length > 7 && lookup(table, b[7]) === ",") nl = 7;
4466
+ if (nl < 0 || nl > 8) return void 0;
4467
+ let type = "";
4468
+ for (const c of b.substring(2, Math.min(nl, 7))) {
4469
+ type += lookup(table, c) ?? SWA_UNREAD;
4470
+ }
4471
+ return { type, text: plain(table, b.substring(nl + 1), PASS_37) };
4472
+ }
4473
+ function decodeSwaLabel40(body) {
4474
+ const b = body.replace(/\r/g, "");
4475
+ if (b.startsWith("POS01,")) {
4476
+ return { type: "POS01", text: b.trim() };
4477
+ }
4478
+ const code = b[5] === "," ? b.substring(6, 8) : "";
4479
+ if (code === "00") {
4480
+ return {
4481
+ type: b.substring(0, 5),
4482
+ text: b.substring(8).replace(/\n/g, "|")
4483
+ };
4484
+ }
4485
+ const table = SWA_LABEL_40_TABLES[code];
4486
+ if (!table) return void 0;
4487
+ return {
4488
+ type: b.substring(0, 5),
4489
+ text: plain(table, b.substring(8), PASS_40)
4490
+ };
4491
+ }
4492
+ var SWA_TYPES = {
4493
+ "37": /* @__PURE__ */ new Set([
4494
+ "ADV01",
4495
+ "CLB02",
4496
+ "DIV02",
4497
+ "DVC01",
4498
+ "ETA03",
4499
+ "FCM01",
4500
+ "FCR03",
4501
+ "FFD01",
4502
+ "FHD04",
4503
+ "FLI01",
4504
+ "FTD02",
4505
+ "GAM02",
4506
+ "GSR02",
4507
+ "HGD03",
4508
+ "HLD01",
4509
+ "INN03",
4510
+ "INR04",
4511
+ "MXR03",
4512
+ "OFF02",
4513
+ "OFP01",
4514
+ "ONN02",
4515
+ "OUT03",
4516
+ "PFR03",
4517
+ "PFR04",
4518
+ "PJS01",
4519
+ "PNS01",
4520
+ "POS01",
4521
+ "POS02",
4522
+ "PRM01",
4523
+ "RRR01",
4524
+ "RTG03",
4525
+ "RTG04",
4526
+ "RTG05",
4527
+ "TAR02",
4528
+ "TDM01",
4529
+ "TOD03",
4530
+ "TRB02",
4531
+ "TXI02",
4532
+ "VXR02"
4533
+ ]),
4534
+ "40": /* @__PURE__ */ new Set([
4535
+ "ASR01",
4536
+ "DVP01",
4537
+ "FTX01",
4538
+ "GAR03",
4539
+ "GEN01",
4540
+ "PIC01",
4541
+ "POS01",
4542
+ "WFC01"
4543
+ ])
4544
+ };
4545
+ function isSwaType(type, label) {
4546
+ return SWA_TYPES[label].has(type);
4547
+ }
4548
+
4549
+ // lib/utils/swa_fields.ts
4550
+ var APT = "(?:[A-Z]{4}| {4}|)";
4551
+ var LAT = "(?:[NS] ?\\d{1,2}\\.\\d{3}|-{4}\\.-{3}|)";
4552
+ var LON = "(?:[EW] ?\\d{1,3}\\.\\d{3}|-{4}\\.-{3}|)";
4553
+ var T4 = "(?:\\d{4}|\\*{4}|-{4})";
4554
+ var FOB = "(?: ?\\d{1,2}\\.\\d|\\*\\*\\.\\*)";
4555
+ var INT = "(?: *-? *\\d+| *|-+|\\*+)";
4556
+ var MACH = "\\.\\d{3}";
4557
+ var HMS = "\\d{6}";
4558
+ var OC = "[OC]";
4559
+ var POS = [
4560
+ APT,
4561
+ APT,
4562
+ LAT,
4563
+ LON,
4564
+ INT,
4565
+ INT,
4566
+ INT,
4567
+ MACH,
4568
+ FOB,
4569
+ T4,
4570
+ INT,
4571
+ INT,
4572
+ INT,
4573
+ INT
4574
+ ];
4575
+ var ASR = " *-?\\d+(?:\\.[\\d-])?|XX\\.X";
4576
+ var SWA_LAYOUTS = {
4577
+ ASR01: [[ASR, ASR, ASR, ASR, ASR, ASR, ASR, ASR, "", ""]],
4578
+ CLB02: [[APT, APT, LAT, "", LON, FOB, T4]],
4579
+ ETA03: [[APT, APT, T4, FOB, LAT, LON]],
4580
+ FCM01: [["L[\\d-]", "R[\\d-]", "A[\\d-]", "B[\\d-]", "\\d+", T4]],
4581
+ FHD04: [
4582
+ [
4583
+ ...POS,
4584
+ "(?:OUT|OFF|ON|IN|RET IN)",
4585
+ "X",
4586
+ INT,
4587
+ " *\\d*",
4588
+ HMS,
4589
+ "(?:\\d{6}|-{6})",
4590
+ "\\d{5}"
4591
+ ]
4592
+ ],
4593
+ FLI01: [[APT, APT, T4, FOB, LAT, LON]],
4594
+ GAM02: [[APT, APT, T4, LAT, LON]],
4595
+ GAR03: [
4596
+ [
4597
+ "[A-Z0-9]{1,4} *",
4598
+ "[YN]",
4599
+ "[YN]",
4600
+ "[YN]",
4601
+ "[YN]",
4602
+ "[YN]",
4603
+ "(?:1[23]\\d\\.\\d{1,3}|999\\.999)",
4604
+ "",
4605
+ ""
4606
+ ]
4607
+ ],
4608
+ HGD03: [[...POS, INT]],
4609
+ INN03: [[APT, APT, T4, T4, FOB, OC, OC, OC, OC]],
4610
+ INR04: [[APT, APT, T4, T4, FOB, LAT, LON]],
4611
+ OUT03: [[APT, APT, T4, FOB, FOB, T4]],
4612
+ POS01: [
4613
+ [...POS, INT],
4614
+ [...POS, INT, HMS]
4615
+ ],
4616
+ POS02: [[...POS, INT, HMS]],
4617
+ RTG04: [[APT, APT, T4, T4, T4, FOB, OC, OC, OC, OC]],
4618
+ RTG05: [[APT, APT, T4, T4, T4, FOB, OC, OC, OC, OC]],
4619
+ TDM01: [[APT, APT, LAT, LON, "(?:L1|L2|CR|SV)", OC, T4, HMS]],
4620
+ TXI02: [[APT, APT, T4, FOB]],
4621
+ VXR02: [["[A-Z]{3}"]]
4622
+ };
4623
+ var SWA_PREFIX_LAYOUTS = {
4624
+ MXR03: [APT, "(?:\\d{4}|)", "[0-3]"]
4625
+ };
4626
+ function swaFields(text) {
4627
+ return text.replace(/\|/g, ",").split(",");
4628
+ }
4629
+ function swaMatchesLayout(fields, layouts) {
4630
+ return layouts.some(
4631
+ (layout) => layout.length === fields.length && layout.every((rx, i) => new RegExp(`^(?:${rx})$`).test(fields[i]))
4632
+ );
4633
+ }
4634
+ var SwaFieldWriter = class {
4635
+ constructor(decodeResult, fields) {
4636
+ this.decodeResult = decodeResult;
4637
+ this.fields = fields;
4638
+ }
4639
+ decodeResult;
4640
+ fields;
4641
+ used = /* @__PURE__ */ new Set();
4642
+ take(i) {
4643
+ this.used.add(i);
4644
+ return this.fields[i] ?? "";
4645
+ }
4646
+ /** A field with no specific ResultFormatter helper; `key` is its name in `raw`. */
4647
+ item(i, key, code, label, value) {
4648
+ this.used.add(i);
4649
+ ResultFormatter.raw(this.decodeResult, key, code, label, value);
4650
+ }
4651
+ /** Marks a field as read without writing it (it was written together with another). */
4652
+ use(i) {
4653
+ return this.take(i);
4654
+ }
4655
+ departure(i) {
4656
+ const v = this.take(i);
4657
+ if (/^[A-Z]{4}$/.test(v))
4658
+ ResultFormatter.departureAirport(this.decodeResult, v);
4659
+ }
4660
+ arrival(i) {
4661
+ const v = this.take(i);
4662
+ if (/^[A-Z]{4}$/.test(v))
4663
+ ResultFormatter.arrivalAirport(this.decodeResult, v);
4664
+ }
4665
+ /**
4666
+ * Writes the position when both coordinates are present and in range. Otherwise the
4667
+ * placeholders are consumed and any coordinate that was sent stays for `remaining`.
4668
+ */
4669
+ position(latIndex, lonIndex) {
4670
+ const lat = this.fields[latIndex] ?? "";
4671
+ const lon = this.fields[lonIndex] ?? "";
4672
+ const latitude = /^[NS] ?\d/.test(lat) ? CoordinateUtils.getDirection(lat) * Number(lat.substring(1).trim()) : NaN;
4673
+ const longitude = /^[EW] ?\d/.test(lon) ? CoordinateUtils.getDirection(lon) * Number(lon.substring(1).trim()) : NaN;
4674
+ if (Math.abs(latitude) <= 90 && Math.abs(longitude) <= 180) {
4675
+ this.used.add(latIndex);
4676
+ this.used.add(lonIndex);
4677
+ ResultFormatter.position(this.decodeResult, { latitude, longitude });
4678
+ return;
4679
+ }
4680
+ if (!/^[NS] ?\d/.test(lat)) this.used.add(latIndex);
4681
+ if (!/^[EW] ?\d/.test(lon)) this.used.add(lonIndex);
4682
+ }
4683
+ /** A whole number, or undefined for a placeholder. */
4684
+ int(i) {
4685
+ const v = this.take(i).replace(/ /g, "");
4686
+ return /^-?\d+$/.test(v) ? Number(v) : void 0;
4687
+ }
4688
+ altitude(i) {
4689
+ const v = this.int(i);
4690
+ if (v !== void 0) ResultFormatter.altitude(this.decodeResult, v);
4691
+ }
4692
+ /** Fuel on board, sent in thousands of pounds; written in pounds. */
4693
+ fuel(i) {
4694
+ const v = this.take(i).trim();
4695
+ if (/^\d+\.\d$/.test(v)) {
4696
+ ResultFormatter.currentFuel(
4697
+ this.decodeResult,
4698
+ Math.round(Number(v) * 1e3)
4699
+ );
4700
+ }
4701
+ }
4702
+ /**
4703
+ * HHMM or HHMMSS as seconds of the day. A placeholder is consumed; a time out of range is
4704
+ * left for `remaining`.
4705
+ */
4706
+ tod(i) {
4707
+ const v = this.fields[i] ?? "";
4708
+ if (/^(?:[01]\d|2[0-3])[0-5]\d(?:[0-5]\d)?$/.test(v)) {
4709
+ this.used.add(i);
4710
+ return DateTimeUtils.convertHHMMSSToTod(v);
4711
+ }
4712
+ if (!/^\d+$/.test(v)) this.used.add(i);
4713
+ return void 0;
4714
+ }
4715
+ /** Writes a time through a ResultFormatter helper such as `out`, `in` or `timestamp`. */
4716
+ time(i, write) {
4717
+ const t = this.tod(i);
4718
+ if (t !== void 0) write(this.decodeResult, t);
4719
+ }
4720
+ /** A time with no ResultFormatter helper, written as HH:MM:SS. */
4721
+ timeItem(i, key, code, label) {
4722
+ const t = this.tod(i);
4723
+ if (t !== void 0) {
4724
+ this.item(i, key, code, label, DateTimeUtils.timestampToString(t));
4725
+ }
4726
+ }
4727
+ eta(i) {
4728
+ this.time(i, (r, t) => ResultFormatter.eta(r, t));
4729
+ }
4730
+ /** Fields not written above, non-empty, to `remaining`. */
4731
+ finish() {
4732
+ const rest = this.fields.filter(
4733
+ (v, i) => !this.used.has(i) && v.trim() !== ""
4734
+ );
4735
+ if (rest.length > 0) ResultFormatter.unknownArr(this.decodeResult, rest);
4736
+ return rest.length;
4737
+ }
4738
+ };
4739
+
4740
+ // lib/plugins/Label_37_SWA.ts
4741
+ var DESCRIPTIONS = {
4742
+ ADV01: "Ride Conditions Report",
4743
+ CLB02: "Climb Report",
4744
+ DVC01: "Divert Plan (Crew Copy)",
4745
+ ETA03: "ETA Report",
4746
+ FCM01: "Engine and Fuel State",
4747
+ FCR03: "Runway Condition Report",
4748
+ FHD04: "Frequency Handoff Report",
4749
+ FLI01: "Flight Initialization",
4750
+ FTD02: "Free Text to Dispatch",
4751
+ GAM02: "Gate Assignment Request",
4752
+ HGD03: "HowGozit Report",
4753
+ HLD01: "Holding Report",
4754
+ INN03: "In Report",
4755
+ INR04: "In Range Report",
4756
+ MXR03: "Maintenance Report",
4757
+ OFF02: "Off Report",
4758
+ ONN02: "On Report",
4759
+ OUT03: "Out Report",
4760
+ PFR03: "Post Flight Report",
4761
+ POS01: "Position Report",
4762
+ POS02: "Position Report",
4763
+ RRR01: "Reroute Report",
4764
+ RTG04: "Return to Gate Report",
4765
+ RTG05: "Return to Gate Report",
4766
+ TDM01: "Door Report",
4767
+ TRB02: "Turbulence Report",
4768
+ TXI02: "Taxi Report",
4769
+ VXR02: "Voice Call Request"
4770
+ };
4771
+ var DOORS = {
4772
+ L1: "L1 (forward entry)",
4773
+ L2: "L2 (aft entry)",
4774
+ CR: "Cargo",
4775
+ SV: "Service"
4776
+ };
4777
+ var MXR_CODES = {
4778
+ "0": "Nothing to report",
4779
+ "1": "Status option",
4780
+ "2": "Category and text",
4781
+ "3": "Free text"
4782
+ };
4783
+ var Label_37_SWA = class extends DecoderPlugin {
4784
+ name = "label-37-swa";
4785
+ qualifiers() {
4786
+ return {
4787
+ labels: ["37"],
4788
+ preambles: ["01", "02", "03", "04", "05", "06", "07", "08", "09"]
4789
+ };
4790
+ }
4791
+ decode(message2, options = {}) {
4792
+ const decodeResult = this.defaultResult();
4793
+ decodeResult.decoder.name = this.name;
4794
+ decodeResult.message = message2;
4795
+ const d = decodeSwaLabel37(message2.text);
4796
+ if (!d || !isSwaType(d.type, "37")) {
4797
+ if (options.debug) {
4798
+ console.log(`Decoder: Unknown 37 message: ${message2.text}`);
4799
+ }
4800
+ ResultFormatter.unknown(decodeResult, message2.text);
4801
+ decodeResult.formatted.description = "Unknown";
4802
+ return decodeResult;
4803
+ }
4804
+ decodeResult.decoded = true;
4805
+ decodeResult.formatted.description = DESCRIPTIONS[d.type] ?? "Southwest Message";
4806
+ decodeResult.raw.preamble = d.type;
4807
+ const text = d.text;
4808
+ const fields = swaFields(text);
4809
+ const layouts = SWA_LAYOUTS[d.type];
4810
+ const prefix = SWA_PREFIX_LAYOUTS[d.type];
4811
+ const clean = !text.includes(SWA_UNREAD);
4812
+ if (clean && layouts && swaMatchesLayout(fields, layouts)) {
4813
+ const rest = this.writeFields(decodeResult, d.type, fields);
4814
+ decodeResult.decoder.decodeLevel = rest === 0 ? "full" : "partial";
4815
+ return decodeResult;
4816
+ }
4817
+ if (clean && prefix && swaMatchesLayout(fields.slice(0, prefix.length), [prefix])) {
4818
+ const rest = this.writeMaintenance(decodeResult, text, fields);
4819
+ decodeResult.decoder.decodeLevel = rest === 0 ? "full" : "partial";
4820
+ return decodeResult;
4821
+ }
4822
+ if (clean && !layouts && !prefix) {
4823
+ const lines = text.split("|");
4824
+ const route = lines[0].match(/^([A-Z]{4}),([A-Z]{4})$/);
4825
+ if (route && d.type === "FTD02") {
4826
+ ResultFormatter.departureAirport(decodeResult, route[1]);
4827
+ ResultFormatter.arrivalAirport(decodeResult, route[2]);
4828
+ lines.shift();
4829
+ }
4830
+ ResultFormatter.text(decodeResult, lines.join("\n"));
4831
+ decodeResult.decoder.decodeLevel = "full";
4832
+ return decodeResult;
4833
+ }
4834
+ ResultFormatter.text(decodeResult, text.split("|").join("\n"));
4835
+ decodeResult.decoder.decodeLevel = "partial";
4836
+ return decodeResult;
4837
+ }
4838
+ /** Writes a laid-out type's fields; returns how many went to `remaining`. */
4839
+ writeFields(decodeResult, type, fields) {
4840
+ const w = new SwaFieldWriter(decodeResult, fields);
4841
+ switch (type) {
4842
+ case "POS01":
4843
+ case "POS02":
4844
+ case "HGD03":
4845
+ case "FHD04": {
4846
+ w.departure(0);
4847
+ w.arrival(1);
4848
+ w.position(2, 3);
4849
+ w.altitude(4);
4850
+ const ias = w.int(5);
4851
+ if (ias !== void 0)
4852
+ ResultFormatter.indicatedAirspeed(decodeResult, ias);
4853
+ const tas = w.int(6);
4854
+ if (tas !== void 0) ResultFormatter.airspeed(decodeResult, tas);
4855
+ ResultFormatter.mach(decodeResult, Number(w.use(7)));
4856
+ w.fuel(8);
4857
+ w.eta(9);
4858
+ const track = w.int(10);
4859
+ if (track !== void 0) ResultFormatter.track(decodeResult, track);
4860
+ ResultFormatter.wind(decodeResult, w.int(11), w.int(12));
4861
+ const sat = w.int(13);
4862
+ if (sat !== void 0)
4863
+ ResultFormatter.temperature(decodeResult, `${sat}`);
4864
+ const gwIndex = type === "FHD04" ? 16 : 14;
4865
+ const gw = w.int(gwIndex);
4866
+ if (gw !== void 0) ResultFormatter.grossWeight(decodeResult, gw);
4867
+ if (type !== "FHD04" && fields.length === 16) {
4868
+ w.time(15, (r, t) => ResultFormatter.timestamp(r, t));
4869
+ }
4870
+ if (type === "FHD04") {
4871
+ w.item(14, "event", "EVT", "Event", fields[14]);
4872
+ w.use(15);
4873
+ const freq = w.use(19);
4874
+ if (/^1\d{5}$/.test(freq)) {
4875
+ w.item(
4876
+ 19,
4877
+ "frequency",
4878
+ "FREQ",
4879
+ "ACARS Frequency",
4880
+ `${freq.substring(0, 3)}.${freq.substring(3)} MHz`
4881
+ );
4882
+ }
4883
+ }
4884
+ break;
4885
+ }
4886
+ case "GAM02":
4887
+ w.departure(0);
4888
+ w.arrival(1);
4889
+ w.eta(2);
4890
+ w.position(3, 4);
4891
+ break;
4892
+ case "CLB02":
4893
+ w.departure(0);
4894
+ w.arrival(1);
4895
+ w.position(2, 4);
4896
+ w.fuel(5);
4897
+ w.eta(6);
4898
+ break;
4899
+ case "ETA03":
4900
+ w.departure(0);
4901
+ w.arrival(1);
4902
+ w.eta(2);
4903
+ w.fuel(3);
4904
+ w.position(4, 5);
4905
+ break;
4906
+ case "INR04":
4907
+ w.departure(0);
4908
+ w.arrival(1);
4909
+ w.eta(2);
4910
+ w.time(3, (r, t) => ResultFormatter.timestamp(r, t));
4911
+ w.fuel(4);
4912
+ w.position(5, 6);
4913
+ break;
4914
+ case "FLI01":
4915
+ w.departure(0);
4916
+ w.arrival(1);
4917
+ w.time(2, (r, t) => ResultFormatter.timestamp(r, t));
4918
+ w.fuel(3);
4919
+ w.position(4, 5);
4920
+ break;
4921
+ case "OUT03":
4922
+ w.departure(0);
4923
+ w.arrival(1);
4924
+ w.time(2, (r, t) => ResultFormatter.out(r, t));
4925
+ w.fuel(3);
4926
+ w.timeItem(5, "front_door_closed_time", "L1CLS", "Front Door Closed");
4927
+ break;
4928
+ case "TXI02":
4929
+ w.departure(0);
4930
+ w.arrival(1);
4931
+ w.timeItem(2, "taxi_start_time", "TAXI", "Taxi Start Time");
4932
+ w.fuel(3);
4933
+ break;
4934
+ case "INN03":
4935
+ w.departure(0);
4936
+ w.arrival(1);
4937
+ w.time(2, (r, t) => ResultFormatter.in(r, t));
4938
+ w.timeItem(3, "front_door_open_time", "L1OPN", "Front Door Opened");
4939
+ w.fuel(4);
4940
+ break;
4941
+ case "RTG04":
4942
+ case "RTG05":
4943
+ w.departure(0);
4944
+ w.arrival(1);
4945
+ w.time(2, (r, t) => ResultFormatter.out(r, t));
4946
+ w.time(3, (r, t) => ResultFormatter.in(r, t));
4947
+ w.time(4, (r, t) => ResultFormatter.timestamp(r, t));
4948
+ w.fuel(5);
4949
+ break;
4950
+ case "TDM01": {
4951
+ w.departure(0);
4952
+ w.arrival(1);
4953
+ w.position(2, 3);
4954
+ ResultFormatter.door_event(
4955
+ decodeResult,
4956
+ DOORS[w.use(4)],
4957
+ w.use(5) === "O" ? "open" : "closed"
4958
+ );
4959
+ const md = fields[6];
4960
+ if (/^(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])$/.test(md)) {
4961
+ w.use(6);
4962
+ ResultFormatter.month(decodeResult, Number(md.substring(0, 2)));
4963
+ ResultFormatter.day(decodeResult, Number(md.substring(2)));
4964
+ } else if (!/^\d+$/.test(md)) {
4965
+ w.use(6);
4966
+ }
4967
+ w.time(7, (r, t) => ResultFormatter.timestamp(r, t));
4968
+ break;
4969
+ }
4970
+ case "FCM01": {
4971
+ const names = {
4972
+ L: "Left Engine",
4973
+ R: "Right Engine",
4974
+ A: "APU"
4975
+ };
4976
+ for (let i = 0; i < 3; i++) {
4977
+ const v = fields[i];
4978
+ if (v[1] === "0" || v[1] === "1") {
4979
+ w.item(
4980
+ i,
4981
+ `${names[v[0]].toLowerCase().replace(" ", "_")}`,
4982
+ v[0],
4983
+ names[v[0]],
4984
+ v[1] === "1" ? "running" : "off"
4985
+ );
4986
+ }
4987
+ }
4988
+ ResultFormatter.currentFuel(decodeResult, Number(w.use(4)) * 100);
4989
+ w.time(5, (r, t) => ResultFormatter.timestamp(r, t));
4990
+ break;
4991
+ }
4992
+ case "VXR02":
4993
+ w.item(0, "station_iata", "STN", "Station", fields[0]);
4994
+ break;
4995
+ }
4996
+ return w.finish();
4997
+ }
4998
+ /** MXR03: destination, ETA, code, then the category and text; returns how many header
4999
+ * fields went to `remaining`. */
5000
+ writeMaintenance(decodeResult, text, fields) {
5001
+ const w = new SwaFieldWriter(decodeResult, fields.slice(0, 3));
5002
+ w.arrival(0);
5003
+ w.eta(1);
5004
+ w.item(
5005
+ 2,
5006
+ "maintenance_code",
5007
+ "MXCODE",
5008
+ "Report Code",
5009
+ `${fields[2]} (${MXR_CODES[fields[2]]})`
5010
+ );
5011
+ const header = text.match(/^[^,|]*[,|][^,|]*[,|][^,|]*[,|]?/);
5012
+ const body = text.substring(header ? header[0].length : 0);
5013
+ if (body.trim() !== "") {
5014
+ ResultFormatter.text(decodeResult, body.split("|").join("\n"));
5015
+ }
5016
+ return w.finish();
5017
+ }
5018
+ };
5019
+
5020
+ // lib/plugins/Label_40_SWA.ts
5021
+ var DESCRIPTIONS2 = {
5022
+ ASR01: "Approach Stability Report",
5023
+ DVP01: "Divert Plan",
5024
+ FTX01: "Dispatch Free Text",
5025
+ GAR03: "Gate Assignment Response",
5026
+ GEN01: "Company Message",
5027
+ PIC01: "Part 117 Duty Extension Request",
5028
+ POS01: "Position Report Request",
5029
+ WFC01: "Field Condition Report"
5030
+ };
5031
+ var ASR01 = [
5032
+ [
5033
+ "reverser_distance",
5034
+ "REVDIST",
5035
+ "Threshold to Thrust Reverser Deployment (feet)"
5036
+ ],
5037
+ ["max_ias", "MAXIAS", "Highest IAS, 1000 feet to Touchdown (knots)"],
5038
+ ["min_ias", "MINIAS", "Lowest IAS, to Touchdown (knots)"],
5039
+ ["touchdown_distance", "TDDIST", "Threshold to Touchdown (feet)"],
5040
+ ["glideslope_above", "GSHIGH", "Largest Deviation Above Glideslope (dots)"],
5041
+ ["glideslope_below", "GSLOW", "Largest Deviation Below Glideslope (dots)"],
5042
+ ["max_pitch", "MAXPITCH", "Highest Pitch, Flare and Landing (degrees)"],
5043
+ ["tailstrike_pitch", "TSPITCH", "Tail Strike Pitch Limit (degrees)"]
5044
+ ];
5045
+ var Label_40_SWA = class extends DecoderPlugin {
5046
+ name = "label-40-swa";
5047
+ qualifiers() {
5048
+ return {
5049
+ labels: ["40"]
5050
+ };
5051
+ }
5052
+ decode(message2, options = {}) {
5053
+ const decodeResult = this.defaultResult();
5054
+ decodeResult.decoder.name = this.name;
5055
+ decodeResult.message = message2;
5056
+ const d = decodeSwaLabel40(message2.text);
5057
+ if (!d || !isSwaType(d.type, "40")) {
5058
+ if (options.debug) {
5059
+ console.log(`Decoder: Unknown 40 message: ${message2.text}`);
5060
+ }
5061
+ ResultFormatter.unknown(decodeResult, message2.text);
5062
+ decodeResult.formatted.description = "Unknown";
5063
+ return decodeResult;
5064
+ }
5065
+ decodeResult.decoded = true;
5066
+ decodeResult.formatted.description = DESCRIPTIONS2[d.type] ?? "Southwest Message";
5067
+ decodeResult.raw.preamble = d.type;
5068
+ if (d.type === "POS01") {
5069
+ const m = d.text.match(/^POS01,Y,(\d{2})$/);
5070
+ if (m) {
5071
+ ResultFormatter.raw(
5072
+ decodeResult,
5073
+ "report_interval",
5074
+ "RPTINT",
5075
+ "Position Report Interval",
5076
+ `${Number(m[1])} minutes`,
5077
+ Number(m[1])
5078
+ );
5079
+ decodeResult.decoder.decodeLevel = "full";
5080
+ } else {
5081
+ ResultFormatter.unknown(decodeResult, d.text);
5082
+ decodeResult.decoder.decodeLevel = "partial";
5083
+ }
5084
+ return decodeResult;
5085
+ }
5086
+ const text = d.text;
5087
+ const fields = swaFields(text);
5088
+ const layouts = SWA_LAYOUTS[d.type];
5089
+ const clean = !text.includes(SWA_UNREAD);
5090
+ if (clean && layouts && swaMatchesLayout(fields, layouts)) {
5091
+ const w = new SwaFieldWriter(decodeResult, fields);
5092
+ if (d.type === "ASR01") {
5093
+ ASR01.forEach(([key, code, label], i) => {
5094
+ const v = fields[i].trim();
5095
+ if (v !== "XX.X") w.item(i, key, code, label, v);
5096
+ else w.use(i);
5097
+ });
5098
+ } else if (d.type === "GAR03") {
5099
+ ResultFormatter.arrivalGate(decodeResult, w.use(0).trim());
5100
+ if (fields[6] !== "999.999") {
5101
+ w.item(
5102
+ 6,
5103
+ "ramp_frequency",
5104
+ "RAMPFREQ",
5105
+ "Ramp Frequency",
5106
+ `${fields[6]} MHz`
5107
+ );
5108
+ } else {
5109
+ w.use(6);
5110
+ }
5111
+ }
5112
+ const rest = w.finish();
5113
+ decodeResult.decoder.decodeLevel = rest === 0 ? "full" : "partial";
5114
+ return decodeResult;
5115
+ }
5116
+ ResultFormatter.text(decodeResult, text.split("|").join("\n"));
5117
+ decodeResult.decoder.decodeLevel = clean && !layouts ? "full" : "partial";
5118
+ return decodeResult;
5119
+ }
5120
+ };
5121
+
3675
5122
  // lib/plugins/Label_44_ETA.ts
3676
5123
  var Label_44_ETA = class extends DecoderPlugin {
3677
5124
  name = "label-44-eta";
@@ -4710,7 +6157,7 @@ var Label_H1_76401 = class extends DecoderPlugin {
4710
6157
  );
4711
6158
  if (position) {
4712
6159
  waypoints.push({
4713
- name: CoordinateUtils.coordinateString(position),
6160
+ name: line.substring(0, 13),
4714
6161
  latitude: position.latitude,
4715
6162
  longitude: position.longitude,
4716
6163
  time: DateTimeUtils.convertHHMMSSToTod(line.substring(13, 19))
@@ -4957,11 +6404,11 @@ var Label_H1_M_POS = class extends DecoderPlugin {
4957
6404
  const timestamp = fields[2].trim();
4958
6405
  if (timestamp.length === 6) {
4959
6406
  const day = Number(timestamp.substring(0, 2));
4960
- const tod = DateTimeUtils.convertHHMMSSToTod(
6407
+ const tod2 = DateTimeUtils.convertHHMMSSToTod(
4961
6408
  timestamp.substring(2) + "00"
4962
6409
  );
4963
6410
  ResultFormatter.day(decodeResult, day);
4964
- ResultFormatter.timestamp(decodeResult, tod);
6411
+ ResultFormatter.timestamp(decodeResult, tod2);
4965
6412
  }
4966
6413
  const lat = parseFloat(fields[3].replace(/\s/g, ""));
4967
6414
  const lon = parseFloat(fields[4].replace(/\s/g, ""));
@@ -5355,6 +6802,135 @@ var Label_H1_StarPOS = class extends DecoderPlugin {
5355
6802
  }
5356
6803
  };
5357
6804
 
6805
+ // lib/plugins/Label_H1_SWA_Track.ts
6806
+ var PHASES = {
6807
+ TO: "Takeoff",
6808
+ IC: "Initial Climb",
6809
+ CL: "Climb",
6810
+ CR: "Cruise",
6811
+ ER: "En Route",
6812
+ DC: "Descent",
6813
+ AP: "Approach",
6814
+ RO: "Rollout"
6815
+ };
6816
+ 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]),$/;
6817
+ var Label_H1_SWA_Track = class extends DecoderPlugin {
6818
+ name = "label-h1-swa-track";
6819
+ qualifiers() {
6820
+ return {
6821
+ labels: ["H1"],
6822
+ preambles: ["++76502,", "++86501,", "#DFB++76502,", "#DFB++86501,"]
6823
+ };
6824
+ }
6825
+ decode(message2, options = {}) {
6826
+ const decodeResult = this.initResult(message2, "Track Report");
6827
+ const nl = message2.text.includes("\r\n") ? "\r\n" : "\n";
6828
+ const lines = message2.text.replace(/#DFB/g, "").split(/\r?\n/);
6829
+ const header = lines[0].replace(/^\+\+/, "").split(",");
6830
+ if (header.length !== 9 || !/^\d{6}$/.test(header[3])) {
6831
+ return this.failUnknown(decodeResult, message2.text, options);
6832
+ }
6833
+ const unknown = [];
6834
+ if (/^N\w+$/.test(header[1])) {
6835
+ ResultFormatter.tail(decodeResult, header[1]);
6836
+ } else if (/^\d{1,4}$/.test(header[1])) {
6837
+ ResultFormatter.raw(
6838
+ decodeResult,
6839
+ "ship_number",
6840
+ "SHIP",
6841
+ "Ship Number",
6842
+ header[1]
6843
+ );
6844
+ } else if (header[1] !== "XXX") {
6845
+ unknown.push(header[1]);
6846
+ }
6847
+ ResultFormatter.raw(
6848
+ decodeResult,
6849
+ "aircraft_type",
6850
+ "ACTYPE",
6851
+ "Aircraft Type",
6852
+ header[2]
6853
+ );
6854
+ const d = header[3];
6855
+ ResultFormatter.raw(
6856
+ decodeResult,
6857
+ "report_date",
6858
+ "DATE",
6859
+ "Report Date",
6860
+ `20${d.slice(0, 2)}-${d.slice(2, 4)}-${d.slice(4, 6)}`
6861
+ );
6862
+ ResultFormatter.flightNumber(decodeResult, header[4]);
6863
+ ResultFormatter.departureAirport(decodeResult, header[5]);
6864
+ ResultFormatter.arrivalAirport(decodeResult, header[6]);
6865
+ if (/^\d{1,15}$/.test(header[7])) {
6866
+ ResultFormatter.raw(
6867
+ decodeResult,
6868
+ "leg_counter",
6869
+ "LEGS",
6870
+ "Leg Counter",
6871
+ `${Number(header[7])}`,
6872
+ Number(header[7])
6873
+ );
6874
+ } else {
6875
+ unknown.push(header[7]);
6876
+ }
6877
+ ResultFormatter.raw(
6878
+ decodeResult,
6879
+ "software_version",
6880
+ "SWVER",
6881
+ "Software Version",
6882
+ header[8]
6883
+ );
6884
+ if (unknown.length > 0) ResultFormatter.unknownArr(decodeResult, unknown);
6885
+ const count = lines[1]?.match(/^(\d+),?$/)?.[1];
6886
+ if (lines[1] !== void 0 && count === void 0) {
6887
+ ResultFormatter.unknown(decodeResult, lines[1], nl);
6888
+ }
6889
+ for (const line of lines.slice(2)) {
6890
+ if (line === "" || line === ":") continue;
6891
+ const m = line.match(POINT);
6892
+ 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) {
6893
+ ResultFormatter.unknown(decodeResult, line, nl);
6894
+ continue;
6895
+ }
6896
+ const position = CoordinateUtils.decodeStringCoordinatesDecimalMinutes(
6897
+ `${m[1]}${m[2]}${m[3]}${m[4]}`
6898
+ );
6899
+ if (!position || Math.abs(position.latitude) > 90 || Math.abs(position.longitude) > 180) {
6900
+ ResultFormatter.unknown(decodeResult, line, nl);
6901
+ continue;
6902
+ }
6903
+ ResultFormatter.trackPoint(decodeResult, {
6904
+ name: `${m[1]}.${m[2]}${m[3]}.${m[4]}`,
6905
+ ...position,
6906
+ day: Number(m[5]),
6907
+ time: Number(m[6]) * 3600 + Number(m[7]) * 60,
6908
+ altitude: Number(m[8]),
6909
+ // -00.0 is sent; keep it as 0
6910
+ temperature: Number(m[9]) || 0,
6911
+ windDirection: Number(m[10]),
6912
+ windSpeed: Number(m[11]),
6913
+ phase: PHASES[m[12]] ?? m[12],
6914
+ humiditySensor: m[14] === "1",
6915
+ ...m[14] === "1" && Number(m[13]) > 0 ? { waterVapor: Number(m[13]) } : {}
6916
+ });
6917
+ }
6918
+ const points = decodeResult.raw.route?.waypoints ?? [];
6919
+ const last = points[points.length - 1];
6920
+ if (last) {
6921
+ ResultFormatter.position(decodeResult, {
6922
+ latitude: last.latitude,
6923
+ longitude: last.longitude
6924
+ });
6925
+ ResultFormatter.altitude(decodeResult, last.altitude);
6926
+ ResultFormatter.timestamp(decodeResult, last.time);
6927
+ }
6928
+ const counted = count !== void 0 && Number(count) === points.length;
6929
+ this.setDecodeLevel(decodeResult, true, counted ? void 0 : "partial");
6930
+ return decodeResult;
6931
+ }
6932
+ };
6933
+
5358
6934
  // lib/plugins/Label_H1_WRN.ts
5359
6935
  var Label_H1_WRN = class extends DecoderPlugin {
5360
6936
  name = "label-h1-wrn";
@@ -5453,7 +7029,7 @@ var Label_H2_02E = class extends DecoderPlugin {
5453
7029
  if (text.length !== 32 || !pos) {
5454
7030
  return null;
5455
7031
  }
5456
- const tod = DateTimeUtils.convertHHMMSSToTod(text.substring(13, 17));
7032
+ const tod2 = DateTimeUtils.convertHHMMSSToTod(text.substring(13, 17));
5457
7033
  const flightLevel = parseInt(text.substring(17, 20), 10);
5458
7034
  const tempSign = text[21] === "M" ? -1 : 1;
5459
7035
  const tempDegreesRaw = parseInt(text.substring(22, 25), 10);
@@ -5468,7 +7044,7 @@ var Label_H2_02E = class extends DecoderPlugin {
5468
7044
  name: posString,
5469
7045
  latitude: pos.latitude,
5470
7046
  longitude: pos.longitude,
5471
- time: tod
7047
+ time: tod2
5472
7048
  },
5473
7049
  flightLevel,
5474
7050
  windDirection,
@@ -6776,8 +8352,11 @@ var pluginClasses = [
6776
8352
  Label_ColonComma,
6777
8353
  Label_5Z_Slash,
6778
8354
  Label_10_LDR,
8355
+ Label_10_LRW,
6779
8356
  Label_10_POS,
6780
8357
  Label_10_Slash,
8358
+ Label_11_LDR,
8359
+ Label_11_LRW,
6781
8360
  Label_12_N_Space,
6782
8361
  Label_12_POS,
6783
8362
  Label_13Through18_Slash,
@@ -6801,6 +8380,8 @@ var pluginClasses = [
6801
8380
  Label_2P_FM4,
6802
8381
  Label_2P_FM5,
6803
8382
  Label_30_Slash_EA,
8383
+ Label_37_SWA,
8384
+ Label_40_SWA,
6804
8385
  Label_44_ETA,
6805
8386
  Label_44_IN,
6806
8387
  Label_44_OFF,
@@ -6826,6 +8407,7 @@ var pluginClasses = [
6826
8407
  Label_H1_Paren,
6827
8408
  Label_H1_WRN,
6828
8409
  Label_H1_StarPOS,
8410
+ Label_H1_SWA_Track,
6829
8411
  Label_H2_02E,
6830
8412
  Label_HX,
6831
8413
  Label_58,