@squawk/flightplan
Advanced tools
| /** | ||
| * Route distance and estimated time enroute computation for parsed flight | ||
| * plan routes. Extracts the ordered geographic point sequence from a | ||
| * {@link ParsedRoute} and sums great-circle leg distances. | ||
| */ | ||
| import type { ParsedRoute, RouteElement } from './resolver.js'; | ||
| /** | ||
| * A single leg between two consecutive geographic points in a parsed route. | ||
| */ | ||
| export interface RouteLeg { | ||
| /** Identifier or raw token of the starting point. */ | ||
| from: string; | ||
| /** Identifier or raw token of the ending point. */ | ||
| to: string; | ||
| /** Great-circle distance of this leg in nautical miles. */ | ||
| distanceNm: number; | ||
| /** Cumulative distance from the route start through the end of this leg in nautical miles. */ | ||
| cumulativeDistanceNm: number; | ||
| } | ||
| /** | ||
| * Result of computing route distance and estimated time enroute from a | ||
| * parsed flight plan route. | ||
| */ | ||
| export interface RouteDistanceResult { | ||
| /** Ordered legs between consecutive geographic points. */ | ||
| legs: RouteLeg[]; | ||
| /** Total great-circle route distance in nautical miles. */ | ||
| totalDistanceNm: number; | ||
| /** Estimated time enroute in hours, or `undefined` if no ground speed was provided. */ | ||
| estimatedTimeEnrouteHrs: number | undefined; | ||
| /** | ||
| * Route elements of type `unresolved` that could not contribute coordinates. | ||
| * When these appear between geographic points the distance bridges the gap, | ||
| * so the total may be approximate. | ||
| */ | ||
| unresolvedElements: RouteElement[]; | ||
| } | ||
| /** | ||
| * Computes the total great-circle route distance and optional estimated time | ||
| * enroute for a parsed flight plan route. | ||
| * | ||
| * Extracts the ordered sequence of geographic points from the route elements, | ||
| * sums leg distances, and divides by the given ground speed for ETE. | ||
| * | ||
| * Elements without coordinates (DCT, speed/altitude groups) are silently | ||
| * skipped. Unresolved tokens are collected in `unresolvedElements` to | ||
| * indicate which parts of the route could not contribute to the distance | ||
| * calculation. | ||
| * | ||
| * Airway segments use the FAA-published `distanceToNextNm` values when | ||
| * available, falling back to great-circle computation otherwise. | ||
| * | ||
| * ```typescript | ||
| * import { createFlightplanResolver, computeRouteDistance } from '@squawk/flightplan'; | ||
| * | ||
| * const resolver = createFlightplanResolver({ airports, navaids, fixes, airways }); | ||
| * const route = resolver.parse('KJFK DCT MERIT J60 MARTN DCT KLAX'); | ||
| * const result = computeRouteDistance(route, 450); | ||
| * console.log(result.totalDistanceNm, result.estimatedTimeEnrouteHrs); | ||
| * ``` | ||
| * | ||
| * @param route - A parsed route from {@link FlightplanResolver.parse}. | ||
| * @param groundSpeedKt - Ground speed in knots for ETE calculation. Omit to | ||
| * skip ETE computation. | ||
| * @returns Route distance breakdown with optional ETE. | ||
| */ | ||
| export declare function computeRouteDistance(route: ParsedRoute, groundSpeedKt?: number): RouteDistanceResult; | ||
| //# sourceMappingURL=route-distance.d.ts.map |
| {"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"} |
| /** | ||
| * Route distance and estimated time enroute computation for parsed flight | ||
| * plan routes. Extracts the ordered geographic point sequence from a | ||
| * {@link ParsedRoute} and sums great-circle leg distances. | ||
| */ | ||
| import { distance } from '@squawk/units'; | ||
| // --------------------------------------------------------------------------- | ||
| // Internal helpers | ||
| // --------------------------------------------------------------------------- | ||
| /** Epsilon for comparing coordinates to detect duplicate points. */ | ||
| const COORD_EPSILON = 1e-9; | ||
| /** | ||
| * Returns true if two points share the same coordinates (within epsilon). | ||
| */ | ||
| function samePosition(a, b) { | ||
| return Math.abs(a.lat - b.lat) < COORD_EPSILON && Math.abs(a.lon - b.lon) < COORD_EPSILON; | ||
| } | ||
| /** | ||
| * Walks the route elements and extracts an ordered array of geographic | ||
| * points. Duplicate consecutive points (e.g. an airway entry fix that | ||
| * matches the preceding waypoint) are suppressed. | ||
| * | ||
| * Also collects all `unresolved` elements encountered during the walk. | ||
| */ | ||
| function extractGeoPoints(elements) { | ||
| const points = []; | ||
| const unresolvedElements = []; | ||
| function emit(point) { | ||
| if (points.length > 0 && samePosition(points[points.length - 1], point)) { | ||
| // When the duplicate carries a precomputed distance that the existing | ||
| // point lacks, adopt it. This happens when an airway's entry fix | ||
| // overlaps the preceding waypoint -- the airway waypoint has the | ||
| // published segment distance that would otherwise be lost. | ||
| const last = points[points.length - 1]; | ||
| if (point.precomputedDistanceToNextNm !== undefined && | ||
| last.precomputedDistanceToNextNm === undefined) { | ||
| last.precomputedDistanceToNextNm = point.precomputedDistanceToNextNm; | ||
| } | ||
| return; | ||
| } | ||
| points.push(point); | ||
| } | ||
| for (const el of elements) { | ||
| switch (el.type) { | ||
| case 'airport': | ||
| emit({ label: el.raw, lat: el.airport.lat, lon: el.airport.lon }); | ||
| break; | ||
| case 'waypoint': | ||
| emit({ label: el.raw, lat: el.lat, lon: el.lon }); | ||
| break; | ||
| case 'coordinate': | ||
| emit({ label: el.raw, lat: el.lat, lon: el.lon }); | ||
| break; | ||
| case 'airway': | ||
| for (let i = 0; i < el.waypoints.length; i++) { | ||
| const wp = el.waypoints[i]; | ||
| const isLast = i === el.waypoints.length - 1; | ||
| const point = { | ||
| label: wp.identifier ?? wp.name, | ||
| lat: wp.lat, | ||
| lon: wp.lon, | ||
| }; | ||
| // Only carry precomputed distance for non-last waypoints (the | ||
| // last waypoint's distanceToNextNm points beyond this segment). | ||
| if (!isLast && wp.distanceToNextNm !== undefined) { | ||
| point.precomputedDistanceToNextNm = wp.distanceToNextNm; | ||
| } | ||
| emit(point); | ||
| } | ||
| break; | ||
| case 'sid': | ||
| case 'star': | ||
| for (const wp of el.waypoints) { | ||
| emit({ label: wp.fixIdentifier, lat: wp.lat, lon: wp.lon }); | ||
| } | ||
| break; | ||
| case 'unresolved': | ||
| unresolvedElements.push(el); | ||
| break; | ||
| // 'direct' and 'speedAltitude' are expected non-geographic markers | ||
| // and are silently skipped. | ||
| case 'direct': | ||
| case 'speedAltitude': | ||
| break; | ||
| } | ||
| } | ||
| return { points, unresolvedElements }; | ||
| } | ||
| // --------------------------------------------------------------------------- | ||
| // Public API | ||
| // --------------------------------------------------------------------------- | ||
| /** | ||
| * Computes the total great-circle route distance and optional estimated time | ||
| * enroute for a parsed flight plan route. | ||
| * | ||
| * Extracts the ordered sequence of geographic points from the route elements, | ||
| * sums leg distances, and divides by the given ground speed for ETE. | ||
| * | ||
| * Elements without coordinates (DCT, speed/altitude groups) are silently | ||
| * skipped. Unresolved tokens are collected in `unresolvedElements` to | ||
| * indicate which parts of the route could not contribute to the distance | ||
| * calculation. | ||
| * | ||
| * Airway segments use the FAA-published `distanceToNextNm` values when | ||
| * available, falling back to great-circle computation otherwise. | ||
| * | ||
| * ```typescript | ||
| * import { createFlightplanResolver, computeRouteDistance } from '@squawk/flightplan'; | ||
| * | ||
| * const resolver = createFlightplanResolver({ airports, navaids, fixes, airways }); | ||
| * const route = resolver.parse('KJFK DCT MERIT J60 MARTN DCT KLAX'); | ||
| * const result = computeRouteDistance(route, 450); | ||
| * console.log(result.totalDistanceNm, result.estimatedTimeEnrouteHrs); | ||
| * ``` | ||
| * | ||
| * @param route - A parsed route from {@link FlightplanResolver.parse}. | ||
| * @param groundSpeedKt - Ground speed in knots for ETE calculation. Omit to | ||
| * skip ETE computation. | ||
| * @returns Route distance breakdown with optional ETE. | ||
| */ | ||
| export function computeRouteDistance(route, groundSpeedKt) { | ||
| const { points, unresolvedElements } = extractGeoPoints(route.elements); | ||
| const legs = []; | ||
| let totalDistanceNm = 0; | ||
| for (let i = 0; i < points.length - 1; i++) { | ||
| const from = points[i]; | ||
| const to = points[i + 1]; | ||
| const legDistanceNm = from.precomputedDistanceToNextNm !== undefined | ||
| ? from.precomputedDistanceToNextNm | ||
| : distance.greatCircleDistanceNm(from.lat, from.lon, to.lat, to.lon); | ||
| totalDistanceNm += legDistanceNm; | ||
| legs.push({ | ||
| from: from.label, | ||
| to: to.label, | ||
| distanceNm: legDistanceNm, | ||
| cumulativeDistanceNm: totalDistanceNm, | ||
| }); | ||
| } | ||
| const estimatedTimeEnrouteHrs = groundSpeedKt !== undefined && groundSpeedKt > 0 ? totalDistanceNm / groundSpeedKt : undefined; | ||
| return { | ||
| legs, | ||
| totalDistanceNm, | ||
| estimatedTimeEnrouteHrs, | ||
| unresolvedElements, | ||
| }; | ||
| } |
+2
-0
@@ -9,2 +9,4 @@ /** | ||
| export type { FlightplanResolver, FlightplanResolverOptions, FlightplanAirportLookup, FlightplanNavaidLookup, FlightplanFixLookup, FlightplanAirwayLookup, FlightplanProcedureLookup, ParsedRoute, RouteElement, AirportRouteElement, SidRouteElement, StarRouteElement, AirwayRouteElement, DirectRouteElement, WaypointRouteElement, CoordinateRouteElement, SpeedAltitudeRouteElement, UnresolvedRouteElement, } from './resolver.js'; | ||
| export { computeRouteDistance } from './route-distance.js'; | ||
| export type { RouteLeg, RouteDistanceResult } from './route-distance.js'; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,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"} | ||
| {"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"} |
+1
-0
@@ -8,1 +8,2 @@ /** | ||
| export { createFlightplanResolver } from './resolver.js'; | ||
| export { computeRouteDistance } from './route-distance.js'; |
+3
-2
| { | ||
| "name": "@squawk/flightplan", | ||
| "version": "0.2.2", | ||
| "version": "0.3.0", | ||
| "type": "module", | ||
@@ -36,3 +36,4 @@ "description": "Flight plan route string parsing and resolution using composed navigation resolvers", | ||
| "dependencies": { | ||
| "@squawk/types": "*" | ||
| "@squawk/types": "*", | ||
| "@squawk/units": "*" | ||
| }, | ||
@@ -39,0 +40,0 @@ "devDependencies": { |
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
44479
29.57%11
37.5%800
36.99%2
100%3
50%+ Added
+ Added