@squawk/flightplan 0.2.1 → 0.3.0

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.d.ts CHANGED
@@ -6,4 +6,6 @@
6
6
  */
7
7
  export { createFlightplanResolver } from './resolver.js';
8
8
  export type { FlightplanResolver, FlightplanResolverOptions, FlightplanAirportLookup, FlightplanNavaidLookup, FlightplanFixLookup, FlightplanAirwayLookup, FlightplanProcedureLookup, ParsedRoute, RouteElement, AirportRouteElement, SidRouteElement, StarRouteElement, AirwayRouteElement, DirectRouteElement, WaypointRouteElement, CoordinateRouteElement, SpeedAltitudeRouteElement, UnresolvedRouteElement, } from './resolver.js';
9
+ export { computeRouteDistance } from './route-distance.js';
10
+ export type { RouteLeg, RouteDistanceResult } from './route-distance.js';
9
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,YAAY,EACV,kBAAkB,EAClB,yBAAyB,EACzB,uBAAuB,EACvB,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,yBAAyB,EACzB,WAAW,EACX,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,YAAY,EACV,kBAAkB,EAClB,yBAAyB,EACzB,uBAAuB,EACvB,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,yBAAyB,EACzB,WAAW,EACX,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,YAAY,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -5,3 +5,4 @@
5
5
  * coordinate-resolved route elements.
6
6
  */
7
7
  export { createFlightplanResolver } from './resolver.js';
8
+ export { computeRouteDistance } from './route-distance.js';
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Route distance and estimated time enroute computation for parsed flight
3
+ * plan routes. Extracts the ordered geographic point sequence from a
4
+ * {@link ParsedRoute} and sums great-circle leg distances.
5
+ */
6
+ import type { ParsedRoute, RouteElement } from './resolver.js';
7
+ /**
8
+ * A single leg between two consecutive geographic points in a parsed route.
9
+ */
10
+ export interface RouteLeg {
11
+ /** Identifier or raw token of the starting point. */
12
+ from: string;
13
+ /** Identifier or raw token of the ending point. */
14
+ to: string;
15
+ /** Great-circle distance of this leg in nautical miles. */
16
+ distanceNm: number;
17
+ /** Cumulative distance from the route start through the end of this leg in nautical miles. */
18
+ cumulativeDistanceNm: number;
19
+ }
20
+ /**
21
+ * Result of computing route distance and estimated time enroute from a
22
+ * parsed flight plan route.
23
+ */
24
+ export interface RouteDistanceResult {
25
+ /** Ordered legs between consecutive geographic points. */
26
+ legs: RouteLeg[];
27
+ /** Total great-circle route distance in nautical miles. */
28
+ totalDistanceNm: number;
29
+ /** Estimated time enroute in hours, or `undefined` if no ground speed was provided. */
30
+ estimatedTimeEnrouteHrs: number | undefined;
31
+ /**
32
+ * Route elements of type `unresolved` that could not contribute coordinates.
33
+ * When these appear between geographic points the distance bridges the gap,
34
+ * so the total may be approximate.
35
+ */
36
+ unresolvedElements: RouteElement[];
37
+ }
38
+ /**
39
+ * Computes the total great-circle route distance and optional estimated time
40
+ * enroute for a parsed flight plan route.
41
+ *
42
+ * Extracts the ordered sequence of geographic points from the route elements,
43
+ * sums leg distances, and divides by the given ground speed for ETE.
44
+ *
45
+ * Elements without coordinates (DCT, speed/altitude groups) are silently
46
+ * skipped. Unresolved tokens are collected in `unresolvedElements` to
47
+ * indicate which parts of the route could not contribute to the distance
48
+ * calculation.
49
+ *
50
+ * Airway segments use the FAA-published `distanceToNextNm` values when
51
+ * available, falling back to great-circle computation otherwise.
52
+ *
53
+ * ```typescript
54
+ * import { createFlightplanResolver, computeRouteDistance } from '@squawk/flightplan';
55
+ *
56
+ * const resolver = createFlightplanResolver({ airports, navaids, fixes, airways });
57
+ * const route = resolver.parse('KJFK DCT MERIT J60 MARTN DCT KLAX');
58
+ * const result = computeRouteDistance(route, 450);
59
+ * console.log(result.totalDistanceNm, result.estimatedTimeEnrouteHrs);
60
+ * ```
61
+ *
62
+ * @param route - A parsed route from {@link FlightplanResolver.parse}.
63
+ * @param groundSpeedKt - Ground speed in knots for ETE calculation. Omit to
64
+ * skip ETE computation.
65
+ * @returns Route distance breakdown with optional ETE.
66
+ */
67
+ export declare function computeRouteDistance(route: ParsedRoute, groundSpeedKt?: number): RouteDistanceResult;
68
+ //# sourceMappingURL=route-distance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-distance.d.ts","sourceRoot":"","sources":["../src/route-distance.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAM/D;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,8FAA8F;IAC9F,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,2DAA2D;IAC3D,eAAe,EAAE,MAAM,CAAC;IACxB,uFAAuF;IACvF,uBAAuB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;;;;OAIG;IACH,kBAAkB,EAAE,YAAY,EAAE,CAAC;CACpC;AA6HD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,WAAW,EAClB,aAAa,CAAC,EAAE,MAAM,GACrB,mBAAmB,CAkCrB"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Route distance and estimated time enroute computation for parsed flight
3
+ * plan routes. Extracts the ordered geographic point sequence from a
4
+ * {@link ParsedRoute} and sums great-circle leg distances.
5
+ */
6
+ import { distance } from '@squawk/units';
7
+ // ---------------------------------------------------------------------------
8
+ // Internal helpers
9
+ // ---------------------------------------------------------------------------
10
+ /** Epsilon for comparing coordinates to detect duplicate points. */
11
+ const COORD_EPSILON = 1e-9;
12
+ /**
13
+ * Returns true if two points share the same coordinates (within epsilon).
14
+ */
15
+ function samePosition(a, b) {
16
+ return Math.abs(a.lat - b.lat) < COORD_EPSILON && Math.abs(a.lon - b.lon) < COORD_EPSILON;
17
+ }
18
+ /**
19
+ * Walks the route elements and extracts an ordered array of geographic
20
+ * points. Duplicate consecutive points (e.g. an airway entry fix that
21
+ * matches the preceding waypoint) are suppressed.
22
+ *
23
+ * Also collects all `unresolved` elements encountered during the walk.
24
+ */
25
+ function extractGeoPoints(elements) {
26
+ const points = [];
27
+ const unresolvedElements = [];
28
+ function emit(point) {
29
+ if (points.length > 0 && samePosition(points[points.length - 1], point)) {
30
+ // When the duplicate carries a precomputed distance that the existing
31
+ // point lacks, adopt it. This happens when an airway's entry fix
32
+ // overlaps the preceding waypoint -- the airway waypoint has the
33
+ // published segment distance that would otherwise be lost.
34
+ const last = points[points.length - 1];
35
+ if (point.precomputedDistanceToNextNm !== undefined &&
36
+ last.precomputedDistanceToNextNm === undefined) {
37
+ last.precomputedDistanceToNextNm = point.precomputedDistanceToNextNm;
38
+ }
39
+ return;
40
+ }
41
+ points.push(point);
42
+ }
43
+ for (const el of elements) {
44
+ switch (el.type) {
45
+ case 'airport':
46
+ emit({ label: el.raw, lat: el.airport.lat, lon: el.airport.lon });
47
+ break;
48
+ case 'waypoint':
49
+ emit({ label: el.raw, lat: el.lat, lon: el.lon });
50
+ break;
51
+ case 'coordinate':
52
+ emit({ label: el.raw, lat: el.lat, lon: el.lon });
53
+ break;
54
+ case 'airway':
55
+ for (let i = 0; i < el.waypoints.length; i++) {
56
+ const wp = el.waypoints[i];
57
+ const isLast = i === el.waypoints.length - 1;
58
+ const point = {
59
+ label: wp.identifier ?? wp.name,
60
+ lat: wp.lat,
61
+ lon: wp.lon,
62
+ };
63
+ // Only carry precomputed distance for non-last waypoints (the
64
+ // last waypoint's distanceToNextNm points beyond this segment).
65
+ if (!isLast && wp.distanceToNextNm !== undefined) {
66
+ point.precomputedDistanceToNextNm = wp.distanceToNextNm;
67
+ }
68
+ emit(point);
69
+ }
70
+ break;
71
+ case 'sid':
72
+ case 'star':
73
+ for (const wp of el.waypoints) {
74
+ emit({ label: wp.fixIdentifier, lat: wp.lat, lon: wp.lon });
75
+ }
76
+ break;
77
+ case 'unresolved':
78
+ unresolvedElements.push(el);
79
+ break;
80
+ // 'direct' and 'speedAltitude' are expected non-geographic markers
81
+ // and are silently skipped.
82
+ case 'direct':
83
+ case 'speedAltitude':
84
+ break;
85
+ }
86
+ }
87
+ return { points, unresolvedElements };
88
+ }
89
+ // ---------------------------------------------------------------------------
90
+ // Public API
91
+ // ---------------------------------------------------------------------------
92
+ /**
93
+ * Computes the total great-circle route distance and optional estimated time
94
+ * enroute for a parsed flight plan route.
95
+ *
96
+ * Extracts the ordered sequence of geographic points from the route elements,
97
+ * sums leg distances, and divides by the given ground speed for ETE.
98
+ *
99
+ * Elements without coordinates (DCT, speed/altitude groups) are silently
100
+ * skipped. Unresolved tokens are collected in `unresolvedElements` to
101
+ * indicate which parts of the route could not contribute to the distance
102
+ * calculation.
103
+ *
104
+ * Airway segments use the FAA-published `distanceToNextNm` values when
105
+ * available, falling back to great-circle computation otherwise.
106
+ *
107
+ * ```typescript
108
+ * import { createFlightplanResolver, computeRouteDistance } from '@squawk/flightplan';
109
+ *
110
+ * const resolver = createFlightplanResolver({ airports, navaids, fixes, airways });
111
+ * const route = resolver.parse('KJFK DCT MERIT J60 MARTN DCT KLAX');
112
+ * const result = computeRouteDistance(route, 450);
113
+ * console.log(result.totalDistanceNm, result.estimatedTimeEnrouteHrs);
114
+ * ```
115
+ *
116
+ * @param route - A parsed route from {@link FlightplanResolver.parse}.
117
+ * @param groundSpeedKt - Ground speed in knots for ETE calculation. Omit to
118
+ * skip ETE computation.
119
+ * @returns Route distance breakdown with optional ETE.
120
+ */
121
+ export function computeRouteDistance(route, groundSpeedKt) {
122
+ const { points, unresolvedElements } = extractGeoPoints(route.elements);
123
+ const legs = [];
124
+ let totalDistanceNm = 0;
125
+ for (let i = 0; i < points.length - 1; i++) {
126
+ const from = points[i];
127
+ const to = points[i + 1];
128
+ const legDistanceNm = from.precomputedDistanceToNextNm !== undefined
129
+ ? from.precomputedDistanceToNextNm
130
+ : distance.greatCircleDistanceNm(from.lat, from.lon, to.lat, to.lon);
131
+ totalDistanceNm += legDistanceNm;
132
+ legs.push({
133
+ from: from.label,
134
+ to: to.label,
135
+ distanceNm: legDistanceNm,
136
+ cumulativeDistanceNm: totalDistanceNm,
137
+ });
138
+ }
139
+ const estimatedTimeEnrouteHrs = groundSpeedKt !== undefined && groundSpeedKt > 0 ? totalDistanceNm / groundSpeedKt : undefined;
140
+ return {
141
+ legs,
142
+ totalDistanceNm,
143
+ estimatedTimeEnrouteHrs,
144
+ unresolvedElements,
145
+ };
146
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squawk/flightplan",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Flight plan route string parsing and resolution using composed navigation resolvers",
6
6
  "author": "Neil Cochran",
@@ -33,7 +33,8 @@
33
33
  "lint": "tsc --noEmit && eslint src"
34
34
  },
35
35
  "dependencies": {
36
- "@squawk/types": "*"
36
+ "@squawk/types": "*",
37
+ "@squawk/units": "*"
37
38
  },
38
39
  "devDependencies": {
39
40
  "@squawk/airport-data": "*",
@@ -46,7 +47,7 @@
46
47
  "@squawk/navaids": "*",
47
48
  "@squawk/procedure-data": "*",
48
49
  "@squawk/procedures": "*",
49
- "@types/node": "^25.5.2"
50
+ "@types/node": "^25.6.0"
50
51
  },
51
52
  "keywords": [
52
53
  "aviation",