@squawk/procedures
Advanced tools
| /** | ||
| * Drawable geometry extraction for expanded instrument-procedure leg | ||
| * sequences. Walks the ordered {@link ProcedureLeg} array returned by | ||
| * {@link ProcedureResolver.expand} into the subset of legs that terminate at | ||
| * a known fix and exposes that sequence both as plain | ||
| * {@link ProcedureLegPoint} values and as a GeoJSON `LineString` for map | ||
| * rendering. | ||
| * | ||
| * Many ARINC 424 path terminators end at an altitude, DME distance, radial, | ||
| * intercept, or manual event rather than at a fix (for example `CA`, `FA`, | ||
| * `VA`, `CD`, `FD`, `VD`, `CR`, `VR`, `CI`, `VI`, `FM`, `VM`, and the `HA` / | ||
| * `HM` holds). Those legs carry no `lat` / `lon` and therefore contribute no | ||
| * point, so a path drawn through the result is not a precise flyable track: | ||
| * it omits the non-positional legs and breaks across them. | ||
| */ | ||
| import type { LineString } from 'geojson'; | ||
| import type { ProcedureLeg } from '@squawk/types'; | ||
| /** | ||
| * A single drawable geographic point along an expanded procedure, in leg | ||
| * order. | ||
| */ | ||
| export interface ProcedureLegPoint { | ||
| /** Fix identifier at the leg termination. */ | ||
| label: string; | ||
| /** Latitude in decimal degrees, positive north. */ | ||
| lat: number; | ||
| /** Longitude in decimal degrees, positive east. */ | ||
| lon: number; | ||
| } | ||
| /** | ||
| * Extracts the ordered sequence of drawable geographic points from an | ||
| * expanded procedure leg sequence (the `legs` array of a | ||
| * {@link ProcedureExpansionResult}). | ||
| * | ||
| * Only legs that terminate at a known fix contribute a point: a leg is | ||
| * drawable when it carries a `fixIdentifier`, `lat`, and `lon`. Legs whose | ||
| * path terminator ends at an altitude, DME distance, radial, intercept, or | ||
| * manual event (`CA`, `FA`, `VA`, `CD`, `FD`, `VD`, `CR`, `VR`, `CI`, `VI`, | ||
| * `FM`, `VM`, and the `HA` / `HM` holds) have no coordinate and are skipped. | ||
| * Because a skipped leg leaves a gap, the returned points are not guaranteed | ||
| * to be contiguous: a non-positional leg in the middle of a sequence creates | ||
| * a break in any line drawn through the points, and such legs cannot be | ||
| * rendered from this data alone (they need a flyable geometry computed from | ||
| * the aircraft state, navaid, or altitude). | ||
| * | ||
| * Consecutive duplicate points (within a small epsilon) are suppressed, so a | ||
| * hold or fix repeated back-to-back yields a single point. | ||
| * | ||
| * ```typescript | ||
| * import { createProcedureResolver, extractLegPoints } from '@squawk/procedures'; | ||
| * | ||
| * const resolver = createProcedureResolver({ data: procedures }); | ||
| * const expansion = resolver.expand('KDEN', 'AALLE4'); | ||
| * const points = expansion ? extractLegPoints(expansion.legs) : []; | ||
| * ``` | ||
| * | ||
| * @param legs - Ordered procedure legs, e.g. `expand(...).legs`. | ||
| * @returns Ordered drawable points along the procedure. | ||
| */ | ||
| export declare function extractLegPoints(legs: ProcedureLeg[]): ProcedureLegPoint[]; | ||
| /** | ||
| * Builds a GeoJSON `LineString` from an expanded procedure leg sequence, | ||
| * ready to render as a polyline on a map (e.g. MapLibre, Leaflet). | ||
| * | ||
| * Coordinates follow the GeoJSON `[lon, lat]` ordering. Returns `undefined` | ||
| * when the legs yield fewer than two drawable points, since a `LineString` | ||
| * requires at least two positions to be valid. | ||
| * | ||
| * Non-positional legs are skipped (see {@link extractLegPoints}), so the | ||
| * line connects only the fix-terminated legs in order. When a procedure | ||
| * mixes positional and non-positional legs the line is an approximation that | ||
| * omits the non-drawable segments rather than a precise flyable track. | ||
| * | ||
| * ```typescript | ||
| * import { createProcedureResolver, expansionToLineString } from '@squawk/procedures'; | ||
| * | ||
| * const resolver = createProcedureResolver({ data: procedures }); | ||
| * const expansion = resolver.expand('KDEN', 'AALLE4'); | ||
| * const line = expansion ? expansionToLineString(expansion.legs) : undefined; | ||
| * if (line) { | ||
| * map.addSource('procedure', { type: 'geojson', data: line }); | ||
| * } | ||
| * ``` | ||
| * | ||
| * @param legs - Ordered procedure legs, e.g. `expand(...).legs`. | ||
| * @returns A GeoJSON `LineString`, or `undefined` if fewer than two points. | ||
| */ | ||
| export declare function expansionToLineString(legs: ProcedureLeg[]): LineString | undefined; | ||
| //# sourceMappingURL=leg-geometry.d.ts.map |
| {"version":3,"file":"leg-geometry.d.ts","sourceRoot":"","sources":["../src/leg-geometry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;CACb;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,iBAAiB,EAAE,CAa1E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,UAAU,GAAG,SAAS,CASlF"} |
| /** | ||
| * Drawable geometry extraction for expanded instrument-procedure leg | ||
| * sequences. Walks the ordered {@link ProcedureLeg} array returned by | ||
| * {@link ProcedureResolver.expand} into the subset of legs that terminate at | ||
| * a known fix and exposes that sequence both as plain | ||
| * {@link ProcedureLegPoint} values and as a GeoJSON `LineString` for map | ||
| * rendering. | ||
| * | ||
| * Many ARINC 424 path terminators end at an altitude, DME distance, radial, | ||
| * intercept, or manual event rather than at a fix (for example `CA`, `FA`, | ||
| * `VA`, `CD`, `FD`, `VD`, `CR`, `VR`, `CI`, `VI`, `FM`, `VM`, and the `HA` / | ||
| * `HM` holds). Those legs carry no `lat` / `lon` and therefore contribute no | ||
| * point, so a path drawn through the result is not a precise flyable track: | ||
| * it omits the non-positional legs and breaks across them. | ||
| */ | ||
| /** 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; | ||
| } | ||
| /** | ||
| * Extracts the ordered sequence of drawable geographic points from an | ||
| * expanded procedure leg sequence (the `legs` array of a | ||
| * {@link ProcedureExpansionResult}). | ||
| * | ||
| * Only legs that terminate at a known fix contribute a point: a leg is | ||
| * drawable when it carries a `fixIdentifier`, `lat`, and `lon`. Legs whose | ||
| * path terminator ends at an altitude, DME distance, radial, intercept, or | ||
| * manual event (`CA`, `FA`, `VA`, `CD`, `FD`, `VD`, `CR`, `VR`, `CI`, `VI`, | ||
| * `FM`, `VM`, and the `HA` / `HM` holds) have no coordinate and are skipped. | ||
| * Because a skipped leg leaves a gap, the returned points are not guaranteed | ||
| * to be contiguous: a non-positional leg in the middle of a sequence creates | ||
| * a break in any line drawn through the points, and such legs cannot be | ||
| * rendered from this data alone (they need a flyable geometry computed from | ||
| * the aircraft state, navaid, or altitude). | ||
| * | ||
| * Consecutive duplicate points (within a small epsilon) are suppressed, so a | ||
| * hold or fix repeated back-to-back yields a single point. | ||
| * | ||
| * ```typescript | ||
| * import { createProcedureResolver, extractLegPoints } from '@squawk/procedures'; | ||
| * | ||
| * const resolver = createProcedureResolver({ data: procedures }); | ||
| * const expansion = resolver.expand('KDEN', 'AALLE4'); | ||
| * const points = expansion ? extractLegPoints(expansion.legs) : []; | ||
| * ``` | ||
| * | ||
| * @param legs - Ordered procedure legs, e.g. `expand(...).legs`. | ||
| * @returns Ordered drawable points along the procedure. | ||
| */ | ||
| export function extractLegPoints(legs) { | ||
| const points = []; | ||
| for (const leg of legs) { | ||
| if (leg.fixIdentifier === undefined || leg.lat === undefined || leg.lon === undefined) { | ||
| continue; | ||
| } | ||
| const point = { label: leg.fixIdentifier, lat: leg.lat, lon: leg.lon }; | ||
| if (points.length > 0 && samePosition(points[points.length - 1], point)) { | ||
| continue; | ||
| } | ||
| points.push(point); | ||
| } | ||
| return points; | ||
| } | ||
| /** | ||
| * Builds a GeoJSON `LineString` from an expanded procedure leg sequence, | ||
| * ready to render as a polyline on a map (e.g. MapLibre, Leaflet). | ||
| * | ||
| * Coordinates follow the GeoJSON `[lon, lat]` ordering. Returns `undefined` | ||
| * when the legs yield fewer than two drawable points, since a `LineString` | ||
| * requires at least two positions to be valid. | ||
| * | ||
| * Non-positional legs are skipped (see {@link extractLegPoints}), so the | ||
| * line connects only the fix-terminated legs in order. When a procedure | ||
| * mixes positional and non-positional legs the line is an approximation that | ||
| * omits the non-drawable segments rather than a precise flyable track. | ||
| * | ||
| * ```typescript | ||
| * import { createProcedureResolver, expansionToLineString } from '@squawk/procedures'; | ||
| * | ||
| * const resolver = createProcedureResolver({ data: procedures }); | ||
| * const expansion = resolver.expand('KDEN', 'AALLE4'); | ||
| * const line = expansion ? expansionToLineString(expansion.legs) : undefined; | ||
| * if (line) { | ||
| * map.addSource('procedure', { type: 'geojson', data: line }); | ||
| * } | ||
| * ``` | ||
| * | ||
| * @param legs - Ordered procedure legs, e.g. `expand(...).legs`. | ||
| * @returns A GeoJSON `LineString`, or `undefined` if fewer than two points. | ||
| */ | ||
| export function expansionToLineString(legs) { | ||
| const points = extractLegPoints(legs); | ||
| if (points.length < 2) { | ||
| return undefined; | ||
| } | ||
| return { | ||
| type: 'LineString', | ||
| coordinates: points.map((p) => [p.lon, p.lat]), | ||
| }; | ||
| } |
+2
-0
@@ -9,2 +9,4 @@ /** | ||
| export type { ProcedureResolver, ProcedureResolverOptions, ProcedureExpansionResult, ProcedureSearchField, ProcedureSearchQuery, ProcedureSearchResult, } from './resolver.js'; | ||
| export { extractLegPoints, expansionToLineString } from './leg-geometry.js'; | ||
| export type { ProcedureLegPoint } from './leg-geometry.js'; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,YAAY,EACV,iBAAiB,EACjB,wBAAwB,EACxB,wBAAwB,EACxB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,eAAe,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,YAAY,EACV,iBAAiB,EACjB,wBAAwB,EACxB,wBAAwB,EACxB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC"} |
+1
-0
| export { createProcedureResolver } from './resolver.js'; | ||
| export { extractLegPoints, expansionToLineString } from './leg-geometry.js'; |
+3
-2
| { | ||
| "name": "@squawk/procedures", | ||
| "version": "0.6.0", | ||
| "version": "0.7.0", | ||
| "type": "module", | ||
@@ -45,3 +45,4 @@ "description": "Instrument procedure lookup and expansion for SIDs, STARs, and Instrument Approach Procedures (IAPs)", | ||
| "@squawk/search": "^0.1.0", | ||
| "@squawk/types": "^0.8.0" | ||
| "@squawk/types": "^0.8.0", | ||
| "@types/geojson": "^7946.0.16" | ||
| }, | ||
@@ -48,0 +49,0 @@ "devDependencies": { |
+33
-1
@@ -19,3 +19,7 @@ <h1><img src="../../../assets/squawk-logo.svg" alt="squawk logo" width="48" height="48" style="vertical-align: middle"> @squawk/procedures</h1> | ||
| import { usBundledProcedures } from '@squawk/procedure-data'; | ||
| import { createProcedureResolver } from '@squawk/procedures'; | ||
| import { | ||
| createProcedureResolver, | ||
| expansionToLineString, | ||
| extractLegPoints, | ||
| } from '@squawk/procedures'; | ||
@@ -55,2 +59,9 @@ const resolver = createProcedureResolver({ data: usBundledProcedures.records }); | ||
| console.log(results[0]?.procedure.identifier, results[0]?.score); | ||
| // Turn an expansion into renderable map geometry (non-positional legs are skipped) | ||
| if (withTransition) { | ||
| const points = extractLegPoints(withTransition.legs); | ||
| const line = expansionToLineString(withTransition.legs); | ||
| // `line` is a GeoJSON LineString, or undefined when fewer than two fixes are drawable | ||
| } | ||
| ``` | ||
@@ -175,1 +186,22 @@ | ||
| ``` | ||
| ### `extractLegPoints(legs)` | ||
| Extracts the ordered drawable points from an expanded leg sequence (the `legs` | ||
| array of a `ProcedureExpansionResult`). Only legs that terminate at a known fix | ||
| contribute a point; legs whose ARINC 424 path terminator ends at an altitude, | ||
| DME distance, radial, intercept, or manual event (`CA`, `FA`, `VA`, `CD`, `FD`, | ||
| `VD`, `CR`, `VR`, `CI`, `VI`, `FM`, `VM`, and the `HA`/`HM` holds) carry no | ||
| coordinate and are skipped, so a non-positional leg in the middle of a sequence | ||
| leaves a gap. Consecutive duplicate points are suppressed. Returns | ||
| `ProcedureLegPoint[]`, each with `label`, `lat`, and `lon`. | ||
| ### `expansionToLineString(legs)` | ||
| Builds a GeoJSON `LineString` from an expanded leg sequence, ready to render as a | ||
| polyline on a map (for example MapLibre or Leaflet). Coordinates follow the | ||
| GeoJSON `[lon, lat]` ordering. Because non-positional legs are skipped (see | ||
| `extractLegPoints`), the line connects only the fix-terminated legs and is an | ||
| approximation that omits the non-drawable segments rather than a precise flyable | ||
| track. Returns `LineString | undefined` - `undefined` when the legs yield fewer | ||
| than two drawable points, since a `LineString` requires at least two positions. |
37234
43.22%11
37.5%550
54.93%205
18.5%3
50%+ Added