Skip to content

Commit

Permalink
more FPN parsing
Browse files Browse the repository at this point in the history
- adding procedures
- adding company route
- making routes in H1_M1BPOS and H1_POS consistent
  • Loading branch information
makrsmark committed Feb 16, 2024
1 parent 07ae445 commit dd63040
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 37 deletions.
45 changes: 30 additions & 15 deletions lib/plugins/Label_H1_FPN.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,28 @@ test('decodes Label H1 Preamble FPN landing', () => {
console.log(JSON.stringify(decodeResult, null, 2));

expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
expect(decodeResult.decoder.decodeLevel).toBe('full');
expect(decodeResult.decoder.name).toBe('label-h1-fpn');
expect(decodeResult.formatted.description).toBe('Flight Plan');
expect(decodeResult.formatted.items.length).toBe(6);
expect(decodeResult.formatted.items.length).toBe(9);
expect(decodeResult.formatted.items[0].label).toBe('Route Status');
expect(decodeResult.formatted.items[0].value).toBe('Route Inactive');
expect(decodeResult.formatted.items[1].label).toBe('Origin');
expect(decodeResult.formatted.items[1].value).toBe('KEWR');
expect(decodeResult.formatted.items[2].label).toBe('Destination');
expect(decodeResult.formatted.items[2].value).toBe('KDFW');
expect(decodeResult.formatted.items[3].label).toBe('Aircraft Route');
expect(decodeResult.formatted.items[3].value).toBe('VECTOR >> DISCO >> RIVET');
expect(decodeResult.formatted.items[4].label).toBe('Aircraft Route');
expect(decodeResult.formatted.items[4].value).toBe('TACKE');
expect(decodeResult.formatted.items[5].label).toBe('Message Checksum');
expect(decodeResult.formatted.items[5].value).toBe('0xc8b5');
expect(decodeResult.formatted.items[3].label).toBe('Company Route');
expect(decodeResult.formatted.items[3].value).toBe('EWRDFW01(17L): >> SAAME > J6 > HVQ > Q68 > LITTR >> MEEOW >> FEWWW');
expect(decodeResult.formatted.items[4].label).toBe('Arrival Procedure');
expect(decodeResult.formatted.items[4].value).toBe('SEEVR4 starting at FEWWW');
expect(decodeResult.formatted.items[5].label).toBe('Aircraft Route');
expect(decodeResult.formatted.items[5].value).toBe('VECTOR >> DISCO >> RIVET');
expect(decodeResult.formatted.items[6].label).toBe('Approach Procedure');
expect(decodeResult.formatted.items[6].value).toBe('ILS 17L starting at RIVET');
expect(decodeResult.formatted.items[7].label).toBe('Aircraft Route');
expect(decodeResult.formatted.items[7].value).toBe('TACKE');
expect(decodeResult.formatted.items[8].label).toBe('Message Checksum');
expect(decodeResult.formatted.items[8].value).toBe('0xc8b5');
});
test('decodes Label H1 Preamble FPN full flight', () => {
const decoder = new MessageDecoder();
Expand All @@ -53,21 +59,30 @@ test('decodes Label H1 Preamble FPN full flight', () => {
console.log(JSON.stringify(decodeResult, null, 2));

expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
expect(decodeResult.decoder.decodeLevel).toBe('full');
expect(decodeResult.decoder.name).toBe('label-h1-fpn');
expect(decodeResult.raw.flight_number).toBe('AAL1956')
expect(decodeResult.raw.flight_number).toBe('AAL1956');
expect(decodeResult.raw.company_route.waypoints).toBeUndefined();
expect(decodeResult.formatted.description).toBe('Flight Plan');
expect(decodeResult.formatted.items.length).toBe(5);
expect(decodeResult.formatted.items.length).toBe(9);
expect(decodeResult.formatted.items[0].label).toBe('Route Status');
expect(decodeResult.formatted.items[0].value).toBe('Route Planned');
expect(decodeResult.formatted.items[1].label).toBe('Origin');
expect(decodeResult.formatted.items[1].value).toBe('KPHL');
expect(decodeResult.formatted.items[2].label).toBe('Destination');
expect(decodeResult.formatted.items[2].value).toBe('KPHX');
expect(decodeResult.formatted.items[3].label).toBe('Runway');
expect(decodeResult.formatted.items[3].value).toBe('27L(26O)');
expect(decodeResult.formatted.items[4].label).toBe('Message Checksum');
expect(decodeResult.formatted.items[4].value).toBe('0x293b');
expect(decodeResult.formatted.items[3].label).toBe('Company Route');
expect(decodeResult.formatted.items[3].value).toBe('PHLPHX61');
expect(decodeResult.formatted.items[4].label).toBe('Runway');
expect(decodeResult.formatted.items[4].value).toBe('27L(26O)');
expect(decodeResult.formatted.items[5].label).toBe('Departure Procedure');
expect(decodeResult.formatted.items[5].value).toBe('PHL3');
expect(decodeResult.formatted.items[6].label).toBe('Arrival Procedure');
expect(decodeResult.formatted.items[6].value).toBe('EAGUL6 starting at ZUN');
expect(decodeResult.formatted.items[7].label).toBe('Approach Procedure');
expect(decodeResult.formatted.items[7].value).toBe('ILS26: >> AIR(40.01 N, 80.49 W) > J110 > BOWRR >> VLA(39.056 N, 89.097 W) >> STL(38.516 N, 90.289 W) >> GIBSN(38.43 N, 92.244 W) >> TYGER(38.41 N, 94.05 W) >> GCK(37.551 N, 100.435 W) >> DIXAN(36.169 N, 105.573 W) >> ZUN(34.579 N, 109.093 W)');
expect(decodeResult.formatted.items[8].label).toBe('Message Checksum');
expect(decodeResult.formatted.items[8].value).toBe('0x293b');
});

test('decodes Label H1 Preamble FPN in-flight', () => {
Expand Down
70 changes: 63 additions & 7 deletions lib/plugins/Label_H1_FPN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,21 @@ export class Label_H1_FPN extends DecoderPlugin {
const value = data[i+1];
// TODO: discuss how store commented out bits as both raw and formatted
switch(key) {
// case 'A': // Arrival Procedure (?)
// break;
case 'A': // Arrival Procedure (?)
addProcedure(decodeResult, value, 'arrival');
break;
case 'AA':
addArrivalAirport(decodeResult, value);
break;
// case 'CR': // Current Route (?)
// break;
// case 'D': // Departure Procedure
// break;
case 'AP':
addProcedure(decodeResult, value, 'approach');
break;
case 'CR':
addCompanyRoute(decodeResult, value);
break;
case 'D': // Departure Procedure
addProcedure(decodeResult, value, 'departure');
break;
case 'DA':
addDepartureAirport(decodeResult, value);
break;
Expand All @@ -47,6 +53,7 @@ export class Label_H1_FPN extends DecoderPlugin {
break;
case 'R':
addDepartureRunway(decodeResult, value);
break;
default:
if(allKnownFields) {
decodeResult.remaining.text = '';
Expand Down Expand Up @@ -124,6 +131,55 @@ function addArrivalAirport(decodeResult: any, value: string) {
});
};

function addProcedure(decodeResult: any, value: string, type: string) {
if(decodeResult.raw.procedures === undefined) {
decodeResult.raw.procedures = [];
}
const data = value.split('.');
let waypoints;
if(data.length>1) {
waypoints = data.slice(1).map((leg)=> RouteUtils.getWaypoint(leg));
}
const route = {name: data[0], waypoints: waypoints};
decodeResult.raw.procedures.push({type: type, route: route});
const procedureName = type.substring(0,1).toUpperCase() + type.slice(1);
let procedureValue = route.name;
decodeResult.formatted.items.push({
type: `procedure`,
code: 'proc',
label: `${procedureName} Procedure`,
value: RouteUtils.routeToString(route),
});
};

function addCompanyRoute(decodeResult: any, value: string) {
const segments = value.split('.');
const parens_idx = segments[0].indexOf('(');
let name;
let runway;
if(parens_idx === -1) {
name = segments[0];
} else {
name = segments[0].slice(0, parens_idx);
runway = segments[0].slice(parens_idx+1, segments[0].indexOf(')'));
}
let waypoints;
if(segments.length > 1) {
waypoints = segments.slice(1).map((leg) => RouteUtils.getWaypoint(leg));
}
decodeResult.raw.company_route = {
name: name,
runway: runway,
waypoints: waypoints,
};
decodeResult.formatted.items.push({
type: 'company_route',
code: 'CR',
label: 'Company Route',
value: RouteUtils.routeToString(decodeResult.raw.company_route),
});
};

function addDepartureAirport(decodeResult: any, value: string) {
decodeResult.raw.departure_icao = value;
decodeResult.formatted.items.push({
Expand All @@ -145,7 +201,7 @@ function addDepartureRunway(decodeResult: any, value: string) {

function addRoute(decodeResult: any, value: string) {
const route = value.split('.');
decodeResult.raw.route = route.map((leg)=> RouteUtils.getWaypoint(leg));
decodeResult.raw.route = {waypoints: route.map((leg)=> RouteUtils.getWaypoint(leg))};
decodeResult.formatted.items.push({
type: 'aircraft_route',
code: 'ROUTE',
Expand Down
7 changes: 4 additions & 3 deletions lib/plugins/Label_H1_M1BPOS.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DecoderPlugin } from '../DecoderPlugin';
import { CoordinateUtils } from '../utils/coordinate_utils';
import { RouteUtils } from '../utils/route_utils';

export class Label_H1_M1BPOS extends DecoderPlugin { // eslint-disable-line camelcase
name = 'label-h1-m1bpos';
Expand Down Expand Up @@ -33,9 +34,9 @@ export class Label_H1_M1BPOS extends DecoderPlugin { // eslint-disable-line came
});
}

let route = items.slice(1).filter((part: any) => !/^\d(.+)$/.test(part));
route = route.map((hop: any) => hop || '?');
decodeResult.raw.route = route;
const route = items.slice(1).filter((part: any) => !/^\d(.+)$/.test(part));
const waypoints = route.map((hop: any) => RouteUtils.getWaypoint(hop || '?'));
decodeResult.raw.route = {waypoints: waypoints};

decodeResult.formatted.description = 'Position Report';

Expand Down
18 changes: 10 additions & 8 deletions lib/plugins/Label_H1_POS.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DateTimeUtils } from '../DateTimeUtils';
import { DecoderPlugin } from '../DecoderPlugin';
import { Waypoint } from '../types/waypoint';
import { RouteUtils } from '../utils/route_utils';

export class Label_H1_POS extends DecoderPlugin {
Expand All @@ -24,7 +25,7 @@ export class Label_H1_POS extends DecoderPlugin {
const fields = data.split(',');

if(fields.length==1 && data.startsWith('/RF')) {
decodeResult.raw.route = data.substring(3,data.length).split('.').map((leg: string) => {return {name: leg}});
decodeResult.raw.route = {waypoints: data.substring(3,data.length).split('.').map((leg: string) => RouteUtils.getWaypoint(leg))};
decodeResult.formatted.items.push({
type: 'aircraft_route',
code: 'ROUTE',
Expand Down Expand Up @@ -126,16 +127,17 @@ export class Label_H1_POS extends DecoderPlugin {
longitude: decodeResult.raw.longitude * (decodeResult.raw.longitude_direction === 'W' ? -1 : 1),
};

let waypoints : Waypoint[];
if(fields.length == 11) {//variant 1
decodeResult.raw.route = [{name: fields[1] || '?,', time: convertDateTimeToEpoch(fields[2], fields[10]), timeFormat: 'epoch'},
{name: fields[4] || '?', time: convertDateTimeToEpoch(fields[5], fields[10]), timeFormat: 'epoch'},
{name: fields[6] || '?'}];
waypoints = [{name: fields[1] || '?,', time: convertDateTimeToEpoch(fields[2], fields[10]), timeFormat: 'epoch'},
{name: fields[4] || '?', time: convertDateTimeToEpoch(fields[5], fields[10]), timeFormat: 'epoch'},
{name: fields[6] || '?'}]
} else {
decodeResult.raw.route = [{name: fields[1] || '?,', time: convertHHMMSSToTod(fields[2]), timeFormat: 'tod'},
{name: fields[4] || '?', time: convertHHMMSSToTod(fields[5]), timeFormat: 'tod'},
{name: fields[6] || '?'}];
waypoints = [{name: fields[1] || '?,', time: convertHHMMSSToTod(fields[2]), timeFormat: 'tod'},
{name: fields[4] || '?', time: convertHHMMSSToTod(fields[5]), timeFormat: 'tod'},
{name: fields[6] || '?'}];
}

decodeResult.raw.route = {waypoints: waypoints};
decodeResult.raw.outside_air_temperature = Number(fields[7].substring(1)) * (fields[7].charAt(0) === 'M' ? -1 : 1);

decodeResult.formatted.items.push({
Expand Down
17 changes: 17 additions & 0 deletions lib/types/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Waypoint } from "./waypoint";

/**
* Representation of a route
*
* Typically a list of waypoints, this can also be a named company route.
*/
export interface Route {
/** optional name. If not set, `waypoints` is required */
name?: string,

/** optional runway */
runway?: string,

/** optional list of waypoints. If not set, `name` is required */
waypoints?: Waypoint[],
}
34 changes: 30 additions & 4 deletions lib/utils/route_utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
import { Route } from "../types/route";
import { Waypoint } from "../types/waypoint";
import { CoordinateUtils } from "./coordinate_utils";

export class RouteUtils {

public static routeToString(route: Waypoint[]): string {
return route.map((x) => RouteUtils.waypointToString(x)).join( ' > ').replaceAll('> >', '>>');
public static routeToString(route: Route): string {
let str = '';
if(route.name) {
str += route.name;
}
if(route.runway) {
str += `(${route.runway})`;
}
if(str.length!==0 && route.waypoints && route.waypoints.length === 1) {
str += ' starting at '
}
else if(str.length!==0 && route.waypoints) {
str += ': ';
}

if(route.waypoints) {
str += RouteUtils.waypointsToString(route.waypoints);
}

return str;
}

public static waypointToString(waypoint: Waypoint): string {
Expand Down Expand Up @@ -33,11 +52,18 @@ export class RouteUtils {

// move out if we want public
private static timestampToString(time: number, format: 'tod' | 'epoch'): string {
const date = new Date(time * 1000);
if(format == 'tod') {
const date = new Date(time * 1000); if(format == 'tod') {
return date.toISOString().slice(11, 19);
}
//strip off millis
return date.toISOString().slice(0,-5)+"Z";
}

private static waypointsToString(waypoints: Waypoint[]): string {
let str = waypoints.map((x) => RouteUtils.waypointToString(x)).join( ' > ').replaceAll('> >', '>>');
if(str.startsWith(' > ')) {
str = '>>' + str.slice(2);
}
return str;
}
}

0 comments on commit dd63040

Please sign in to comment.