Sign In

@squawk/airspace

Package Overview
Dependencies
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@squawk/airspace - npm Package Compare versions

Comparing version
0.7.1
to
0.8.0
+1
-1
dist/index.d.ts
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"}
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"}

@@ -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);
}
},
};
}
{
"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",

@@ -70,3 +70,3 @@ <h1><img src="../../../assets/squawk-logo.svg" alt="squawk logo" width="48" height="48" style="vertical-align: middle">&nbsp; @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 @@