@squawk/airspace
Advanced tools
+1
-1
| export { createAirspaceResolver } from './resolver.js'; | ||
| export type { AirspaceResolver, AirspaceResolverOptions, AirspaceQuery } from './resolver.js'; | ||
| export type { AirspaceResolver, AirspaceResolverOptions, AirspaceQuery, AirspaceCentroidQuery, AirspaceByIdentifierOptions, } from './resolver.js'; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACvD,YAAY,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACvD,YAAY,EACV,gBAAgB,EAChB,uBAAuB,EACvB,aAAa,EACb,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,eAAe,CAAC"} |
+116
-0
| import type { FeatureCollection } from 'geojson'; | ||
| import { type BoundingBox } from '@squawk/geo'; | ||
| import type { AirspaceFeature, AirspaceType, ArtccStratum } from '@squawk/types'; | ||
@@ -25,2 +26,44 @@ /** | ||
| /** | ||
| * A query describing a geographic position and tolerance for a centroid-based | ||
| * airspace lookup. | ||
| */ | ||
| export interface AirspaceCentroidQuery { | ||
| /** Longitude in decimal degrees (WGS84). */ | ||
| lon: number; | ||
| /** Latitude in decimal degrees (WGS84). */ | ||
| lat: number; | ||
| /** | ||
| * Optional tolerance in degrees for the centroid match. A feature is | ||
| * returned when both `|centroidLon - lon|` and `|centroidLat - lat|` | ||
| * fall below this value. Defaults to `0.0001` (~11 m), generous enough to | ||
| * absorb floating-point round-trips through URL parsing for centroids | ||
| * encoded to ~5 decimal places. | ||
| */ | ||
| toleranceDeg?: number; | ||
| } | ||
| /** | ||
| * Options accepted by {@link AirspaceResolver.byIdentifier}. | ||
| */ | ||
| export interface AirspaceByIdentifierOptions { | ||
| /** | ||
| * Optional set of airspace types to include in the results. When provided, | ||
| * acts as an inclusion filter and overrides the partition between ARTCC and | ||
| * non-ARTCC features: callers who want ARTCC results in addition to the | ||
| * usual partition include `'ARTCC'` in this set explicitly, and callers who | ||
| * want only non-ARTCC results pass a set of the non-ARTCC types. When | ||
| * omitted, every type is eligible (subject to {@link includeArtcc}). | ||
| */ | ||
| types?: ReadonlySet<AirspaceType>; | ||
| /** | ||
| * When `true` (the default), ARTCC features for the identifier are included | ||
| * alongside the airport-associated and SUA features. When `false`, ARTCC | ||
| * features are excluded - useful when you only want the non-ARTCC partition | ||
| * for an identifier without enumerating every non-ARTCC type yourself. | ||
| * | ||
| * Ignored when {@link types} is provided: `types` is the authoritative | ||
| * inclusion list in that case. | ||
| */ | ||
| includeArtcc?: boolean; | ||
| } | ||
| /** | ||
| * Options for creating an airspace resolver. | ||
@@ -81,2 +124,72 @@ */ | ||
| byArtcc(identifier: string, stratum?: ArtccStratum): AirspaceFeature[]; | ||
| /** | ||
| * Returns every airspace feature whose polygon centroid lies within the | ||
| * given tolerance of the query coordinates. Useful for resolving features | ||
| * that have an empty `identifier` (some Class E5 surfaces) and therefore | ||
| * have no stable identifier-keyed lookup - the polygon centroid is the | ||
| * fallback handle. | ||
| * | ||
| * Reach for this when you have a centroid encoded into a URL or other | ||
| * external string and want to recover the original feature(s); for | ||
| * identifier-keyed lookups, prefer {@link byIdentifier} (or the more | ||
| * specific {@link byAirport} / {@link byArtcc}). Centroid is computed | ||
| * per call - no internal caching - so this is O(n) over the indexed | ||
| * corpus, suitable for occasional URL-driven lookups but not for | ||
| * tight loops. | ||
| * | ||
| * @param query - Query coordinates and optional tolerance. | ||
| * @returns All features whose centroid is within tolerance, in dataset order. | ||
| */ | ||
| byCentroid(query: AirspaceCentroidQuery): AirspaceFeature[]; | ||
| /** | ||
| * Returns every airspace feature for the given identifier across both the | ||
| * ARTCC and non-ARTCC partitions, independent of position or altitude. | ||
| * Lookup is case-insensitive. | ||
| * | ||
| * Reach for this when you have an identifier whose airspace type is not | ||
| * known up-front (e.g. parsed from a URL) and you want a single call that | ||
| * returns the matching feature(s) regardless of partition. For ergonomic | ||
| * shortcuts when the partition is known, prefer {@link byAirport} (returns | ||
| * non-ARTCC features only) or {@link byArtcc} (returns ARTCC features | ||
| * only) - those wrappers encode the common "shells for this airport" / | ||
| * "stratums for this center" questions and stay available alongside this | ||
| * type-agnostic form. | ||
| * | ||
| * @param identifier - FAA identifier, NASR designator, or ARTCC code. | ||
| * @param options - Optional `types` inclusion filter and `includeArtcc` | ||
| * toggle. See {@link AirspaceByIdentifierOptions}. | ||
| * @returns All matching features, or an empty array. | ||
| */ | ||
| byIdentifier(identifier: string, options?: AirspaceByIdentifierOptions): AirspaceFeature[]; | ||
| /** | ||
| * Returns every airspace feature whose pre-indexed bounding box overlaps | ||
| * the given bounding box. Reuses the bounding box computed once at | ||
| * resolver creation time rather than recomputing per call, so this is | ||
| * suitable for tight loops over the corpus (e.g. a chip rebuild against | ||
| * a selection footprint). | ||
| * | ||
| * Bounding-box overlap is a coarse spatial filter: it matches any feature | ||
| * whose axis-aligned rectangle intersects the query rectangle, including | ||
| * features whose actual polygon does not. Callers that need true | ||
| * polygon-polygon intersection should follow up with their own geometry | ||
| * test on the returned features. | ||
| * | ||
| * @param bbox - Query bounding box. | ||
| * @returns All features whose pre-indexed bounding box overlaps, in dataset order. | ||
| */ | ||
| withinBbox(bbox: BoundingBox): AirspaceFeature[]; | ||
| /** | ||
| * Iterates the indexed corpus in dataset order, invoking `callback` once | ||
| * per feature with the parsed feature, its exterior ring, and its | ||
| * pre-computed bounding box. Exposes the resolver's pre-parsed shape so | ||
| * callers that need to filter the corpus themselves do not have to | ||
| * reparse the source GeoJSON or recompute geometry per call. | ||
| * | ||
| * The `ring` and `boundingBox` arguments are the resolver's internal | ||
| * caches and must not be mutated by the callback - copy them first if a | ||
| * mutation is needed. | ||
| * | ||
| * @param callback - Function invoked once per indexed feature. | ||
| */ | ||
| forEachIndexed(callback: (feature: AirspaceFeature, ring: readonly number[][], boundingBox: BoundingBox) => void): void; | ||
| } | ||
@@ -108,2 +221,5 @@ /** | ||
| * const newYorkArtcc = resolver.byArtcc('ZNY'); | ||
| * const anyZnyFeature = resolver.byIdentifier('ZNY'); | ||
| * const nearbyByCentroid = resolver.byCentroid({ lon: -118.4, lat: 33.9 }); | ||
| * const overlapping = resolver.withinBbox({ minLon: -119, minLat: 33, maxLon: -118, maxLat: 35 }); | ||
| * ``` | ||
@@ -110,0 +226,0 @@ */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAW,MAAM,SAAS,CAAC;AAG1D,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAiB,YAAY,EAAE,MAAM,eAAe,CAAC;AAIhG;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,8DAA8D;IAC9D,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,EAAE,aAAa,GAAG,eAAe,EAAE,CAAC;IAE/C;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,GAAG,eAAe,EAAE,CAAC;IAEpF;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,eAAe,EAAE,CAAC;CACxE;AAoDD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,CAmEzF"} | ||
| {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAW,MAAM,SAAS,CAAC;AAE1D,OAAO,EAA2B,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAiB,YAAY,EAAE,MAAM,eAAe,CAAC;AAIhG;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC;IAClC;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,8DAA8D;IAC9D,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,EAAE,aAAa,GAAG,eAAe,EAAE,CAAC;IAE/C;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,GAAG,eAAe,EAAE,CAAC;IAEpF;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,eAAe,EAAE,CAAC;IAEvE;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,KAAK,EAAE,qBAAqB,GAAG,eAAe,EAAE,CAAC;IAE5D;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,2BAA2B,GAAG,eAAe,EAAE,CAAC;IAE3F;;;;;;;;;;;;;;;OAeG;IACH,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,EAAE,CAAC;IAEjD;;;;;;;;;;;;OAYG;IACH,cAAc,CACZ,QAAQ,EAAE,CACR,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,EACzB,WAAW,EAAE,WAAW,KACrB,IAAI,GACR,IAAI,CAAC;CACT;AAoDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,CA2HzF"} |
+48
-1
@@ -1,2 +0,2 @@ | ||
| import { polygon } from '@squawk/geo'; | ||
| import { polygon, polygonGeoJson } from '@squawk/geo'; | ||
| import { altitudeMatches } from './vertical-filter.js'; | ||
@@ -60,2 +60,5 @@ /** | ||
| * const newYorkArtcc = resolver.byArtcc('ZNY'); | ||
| * const anyZnyFeature = resolver.byIdentifier('ZNY'); | ||
| * const nearbyByCentroid = resolver.byCentroid({ lon: -118.4, lat: 33.9 }); | ||
| * const overlapping = resolver.withinBbox({ minLon: -119, minLat: 33, maxLon: -118, maxLat: 35 }); | ||
| * ``` | ||
@@ -124,3 +127,47 @@ */ | ||
| }, | ||
| byCentroid(query) { | ||
| const tolerance = query.toleranceDeg ?? 0.0001; | ||
| const results = []; | ||
| for (const { feature } of indexed) { | ||
| const centroid = polygonGeoJson.polygonCentroid(feature.boundary); | ||
| if (centroid === undefined) { | ||
| continue; | ||
| } | ||
| if (Math.abs(centroid[0] - query.lon) < tolerance && | ||
| Math.abs(centroid[1] - query.lat) < tolerance) { | ||
| results.push(feature); | ||
| } | ||
| } | ||
| return results; | ||
| }, | ||
| byIdentifier(identifier, options) { | ||
| const bucket = byIdentifierMap.get(identifier.toUpperCase()); | ||
| if (bucket === undefined) { | ||
| return []; | ||
| } | ||
| const types = options?.types; | ||
| if (types !== undefined) { | ||
| return bucket.filter((f) => types.has(f.type)); | ||
| } | ||
| const includeArtcc = options?.includeArtcc ?? true; | ||
| if (includeArtcc) { | ||
| return bucket.slice(); | ||
| } | ||
| return bucket.filter((f) => f.type !== 'ARTCC'); | ||
| }, | ||
| withinBbox(bbox) { | ||
| const results = []; | ||
| for (const { feature, boundingBox } of indexed) { | ||
| if (polygonGeoJson.boundingBoxesOverlap(bbox, boundingBox)) { | ||
| results.push(feature); | ||
| } | ||
| } | ||
| return results; | ||
| }, | ||
| forEachIndexed(callback) { | ||
| for (const { feature, ring, boundingBox } of indexed) { | ||
| callback(feature, ring, boundingBox); | ||
| } | ||
| }, | ||
| }; | ||
| } |
+1
-1
| { | ||
| "name": "@squawk/airspace", | ||
| "version": "0.7.1", | ||
| "version": "0.8.0", | ||
| "type": "module", | ||
@@ -5,0 +5,0 @@ "description": "Pure logic library for querying US airspace geometry by position and altitude", |
+92
-2
@@ -70,3 +70,3 @@ <h1><img src="../../../assets/squawk-logo.svg" alt="squawk logo" width="48" height="48" style="vertical-align: middle"> @squawk/airspace</h1> | ||
| `createAirspaceResolver` parses the GeoJSON FeatureCollection at initialization and | ||
| returns a resolver object with three methods: | ||
| returns a resolver object with the following methods: | ||
@@ -83,2 +83,15 @@ - `query(AirspaceQuery)` - returns features containing the given position and | ||
| stratum (`LOW`, `HIGH`, `UTA`, `CTA`, `FIR`, or `CTA/FIR`). | ||
| - `byIdentifier(identifier, options?)` - type-agnostic identifier lookup that | ||
| spans both partitions in one call, with an optional `types` inclusion | ||
| filter and `includeArtcc` toggle. | ||
| - `byCentroid({ lon, lat, toleranceDeg? })` - returns features whose polygon | ||
| centroid lies within tolerance of the query coordinates. Useful for | ||
| resolving features whose `identifier` is empty (some Class E5 surfaces), | ||
| where the centroid is the only stable handle. | ||
| - `withinBbox(bbox)` - returns features whose pre-indexed bounding box | ||
| overlaps the query bbox. Reuses the bounding box cached at init time | ||
| rather than recomputing per call. | ||
| - `forEachIndexed(callback)` - read-only iteration over the indexed corpus | ||
| with positional `(feature, ring, boundingBox)` arguments, for callers | ||
| that need to filter the corpus without reparsing the source GeoJSON. | ||
@@ -121,3 +134,5 @@ All matching features are returned as `AirspaceFeature` objects (from `@squawk/types`), | ||
| **Returns:** `AirspaceResolver` - an object exposing `query(AirspaceQuery)`, | ||
| `byAirport(identifier, types?)`, and `byArtcc(identifier, stratum?)` methods. | ||
| `byAirport(identifier, types?)`, `byArtcc(identifier, stratum?)`, | ||
| `byIdentifier(identifier, options?)`, `byCentroid(query)`, `withinBbox(bbox)`, | ||
| and `forEachIndexed(callback)` methods. | ||
@@ -175,2 +190,77 @@ ### `AirspaceQuery` | ||
| ### `resolver.byIdentifier(identifier, options?)` | ||
| Type-agnostic identifier lookup. Returns every feature whose `identifier` | ||
| matches, spanning both the ARTCC and non-ARTCC partitions in one call. | ||
| Reach for this when the airspace type is not known up-front (e.g. parsed | ||
| from a URL); for ergonomic shortcuts, the partition-specific `byAirport` / | ||
| `byArtcc` wrappers stay available. | ||
| | Option | Type | Description | | ||
| | -------------- | -------------------------- | ----------------------------------------------------------------------------------------------- | | ||
| | `types` | ReadonlySet\<AirspaceType> | Optional. When provided, acts as the authoritative inclusion filter; `includeArtcc` is ignored. | | ||
| | `includeArtcc` | boolean | Defaults to `true`. When `false` and `types` is omitted, ARTCC features are excluded. | | ||
| ```typescript | ||
| // Every feature for the identifier, ARTCC included | ||
| const all = resolver.byIdentifier('ZNY'); | ||
| // Only the non-ARTCC partition | ||
| const nonArtcc = resolver.byIdentifier('ZNY', { includeArtcc: false }); | ||
| // Only matching types | ||
| const onlyClassB = resolver.byIdentifier('JFK', { types: new Set(['CLASS_B']) }); | ||
| ``` | ||
| ### `resolver.byCentroid(query)` | ||
| Returns every feature whose polygon centroid is within `toleranceDeg` of | ||
| `(lon, lat)`. The centroid is computed per call (not cached) so this is | ||
| O(n) over the corpus - suitable for occasional URL-driven lookups, not | ||
| hot loops. Useful for resolving features with an empty `identifier` (some | ||
| Class E5 surfaces), where the centroid is the fallback URL handle. | ||
| | Property | Type | Description | | ||
| | -------------- | ------ | -------------------------------------------------------------------------------------------------------- | | ||
| | `lon` | number | Longitude in decimal degrees (WGS84) | | ||
| | `lat` | number | Latitude in decimal degrees (WGS84) | | ||
| | `toleranceDeg` | number | Optional. Centroid match tolerance in degrees, applied independently to lon and lat. Defaults to 0.0001. | | ||
| ```typescript | ||
| const matches = resolver.byCentroid({ lon: -118.4081, lat: 33.9425 }); | ||
| ``` | ||
| ### `resolver.withinBbox(bbox)` | ||
| Returns every feature whose pre-indexed bounding box overlaps the query | ||
| bbox. Reuses the bounding box cached at init time, so this is suitable | ||
| for tight loops over the corpus. Bounding-box overlap is a coarse spatial | ||
| filter: callers that need true polygon-polygon intersection should follow | ||
| up with their own geometry test on the returned features. | ||
| ```typescript | ||
| const overlapping = resolver.withinBbox({ | ||
| minLon: -119, | ||
| minLat: 33, | ||
| maxLon: -118, | ||
| maxLat: 35, | ||
| }); | ||
| ``` | ||
| ### `resolver.forEachIndexed(callback)` | ||
| Read-only iteration over the indexed corpus, invoking `callback` once per | ||
| feature with positional `(feature, ring, boundingBox)`. Exposes the | ||
| resolver's pre-parsed shape so callers that need to filter the corpus | ||
| themselves do not have to reparse the source GeoJSON or recompute geometry | ||
| per call. The `ring` and `boundingBox` arguments are the resolver's | ||
| internal caches and must not be mutated. | ||
| ```typescript | ||
| resolver.forEachIndexed((feature, ring, boundingBox) => { | ||
| // ring is the parsed exterior ring (number[][]). | ||
| // boundingBox is the pre-computed axis-aligned bbox. | ||
| }); | ||
| ``` | ||
| ### ARTCC altitude bounds | ||
@@ -177,0 +267,0 @@ |
37397
54.82%468
53.44%287
45.69%