@squawk/airspace 0.7.1 → 0.8.6

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/README.md CHANGED
@@ -67,7 +67,7 @@ The `/browser` entry is identical to the main entry; the separate subpath exists
67
67
  ## How it works
68
68
 
69
69
  `createAirspaceResolver` parses the GeoJSON FeatureCollection at initialization and
70
- returns a resolver object with three methods:
70
+ returns a resolver object with the following methods:
71
71
 
72
72
  - `query(AirspaceQuery)` - returns features containing the given position and
73
73
  altitude, via a ray casting point-in-polygon test combined with a vertical
@@ -79,6 +79,19 @@ returns a resolver object with three methods:
79
79
  - `byArtcc(identifier, stratum?)` - returns every ARTCC feature for the given
80
80
  3-letter center code (e.g. `"ZNY"`), optionally filtered to a single
81
81
  stratum (`LOW`, `HIGH`, `UTA`, `CTA`, `FIR`, or `CTA/FIR`).
82
+ - `byIdentifier(identifier, options?)` - type-agnostic identifier lookup that
83
+ spans both partitions in one call, with an optional `types` inclusion
84
+ filter and `includeArtcc` toggle.
85
+ - `byCentroid({ lon, lat, toleranceDeg? })` - returns features whose polygon
86
+ centroid lies within tolerance of the query coordinates. Useful for
87
+ resolving features whose `identifier` is empty (some Class E5 surfaces),
88
+ where the centroid is the only stable handle.
89
+ - `withinBbox(bbox)` - returns features whose pre-indexed bounding box
90
+ overlaps the query bbox. Reuses the bounding box cached at init time
91
+ rather than recomputing per call.
92
+ - `forEachIndexed(callback)` - read-only iteration over the indexed corpus
93
+ with positional `(feature, ring, boundingBox)` arguments, for callers
94
+ that need to filter the corpus without reparsing the source GeoJSON.
82
95
 
83
96
  All matching features are returned as `AirspaceFeature` objects (from `@squawk/types`),
84
97
  including the full polygon boundary coordinates.
@@ -116,7 +129,9 @@ Creates a resolver from a GeoJSON dataset.
116
129
  - `options.data` - a GeoJSON `FeatureCollection` with airspace features
117
130
 
118
131
  **Returns:** `AirspaceResolver` - an object exposing `query(AirspaceQuery)`,
119
- `byAirport(identifier, types?)`, and `byArtcc(identifier, stratum?)` methods.
132
+ `byAirport(identifier, types?)`, `byArtcc(identifier, stratum?)`,
133
+ `byIdentifier(identifier, options?)`, `byCentroid(query)`, `withinBbox(bbox)`,
134
+ and `forEachIndexed(callback)` methods.
120
135
 
121
136
  ### `AirspaceQuery`
122
137
 
@@ -169,6 +184,81 @@ const zny = resolver.byArtcc('ZNY');
169
184
  const zbwHigh = resolver.byArtcc('ZBW', 'HIGH');
170
185
  ```
171
186
 
187
+ ### `resolver.byIdentifier(identifier, options?)`
188
+
189
+ Type-agnostic identifier lookup. Returns every feature whose `identifier`
190
+ matches, spanning both the ARTCC and non-ARTCC partitions in one call.
191
+ Reach for this when the airspace type is not known up-front (e.g. parsed
192
+ from a URL); for ergonomic shortcuts, the partition-specific `byAirport` /
193
+ `byArtcc` wrappers stay available.
194
+
195
+ | Option | Type | Description |
196
+ | -------------- | -------------------------- | ----------------------------------------------------------------------------------------------- |
197
+ | `types` | ReadonlySet\<AirspaceType> | Optional. When provided, acts as the authoritative inclusion filter; `includeArtcc` is ignored. |
198
+ | `includeArtcc` | boolean | Defaults to `true`. When `false` and `types` is omitted, ARTCC features are excluded. |
199
+
200
+ ```typescript
201
+ // Every feature for the identifier, ARTCC included
202
+ const all = resolver.byIdentifier('ZNY');
203
+
204
+ // Only the non-ARTCC partition
205
+ const nonArtcc = resolver.byIdentifier('ZNY', { includeArtcc: false });
206
+
207
+ // Only matching types
208
+ const onlyClassB = resolver.byIdentifier('JFK', { types: new Set(['CLASS_B']) });
209
+ ```
210
+
211
+ ### `resolver.byCentroid(query)`
212
+
213
+ Returns every feature whose polygon centroid is within `toleranceDeg` of
214
+ `(lon, lat)`. The centroid is computed per call (not cached) so this is
215
+ O(n) over the corpus - suitable for occasional URL-driven lookups, not
216
+ hot loops. Useful for resolving features with an empty `identifier` (some
217
+ Class E5 surfaces), where the centroid is the fallback URL handle.
218
+
219
+ | Property | Type | Description |
220
+ | -------------- | ------ | -------------------------------------------------------------------------------------------------------- |
221
+ | `lon` | number | Longitude in decimal degrees (WGS84) |
222
+ | `lat` | number | Latitude in decimal degrees (WGS84) |
223
+ | `toleranceDeg` | number | Optional. Centroid match tolerance in degrees, applied independently to lon and lat. Defaults to 0.0001. |
224
+
225
+ ```typescript
226
+ const matches = resolver.byCentroid({ lon: -118.4081, lat: 33.9425 });
227
+ ```
228
+
229
+ ### `resolver.withinBbox(bbox)`
230
+
231
+ Returns every feature whose pre-indexed bounding box overlaps the query
232
+ bbox. Reuses the bounding box cached at init time, so this is suitable
233
+ for tight loops over the corpus. Bounding-box overlap is a coarse spatial
234
+ filter: callers that need true polygon-polygon intersection should follow
235
+ up with their own geometry test on the returned features.
236
+
237
+ ```typescript
238
+ const overlapping = resolver.withinBbox({
239
+ minLon: -119,
240
+ minLat: 33,
241
+ maxLon: -118,
242
+ maxLat: 35,
243
+ });
244
+ ```
245
+
246
+ ### `resolver.forEachIndexed(callback)`
247
+
248
+ Read-only iteration over the indexed corpus, invoking `callback` once per
249
+ feature with positional `(feature, ring, boundingBox)`. Exposes the
250
+ resolver's pre-parsed shape so callers that need to filter the corpus
251
+ themselves do not have to reparse the source GeoJSON or recompute geometry
252
+ per call. The `ring` and `boundingBox` arguments are the resolver's
253
+ internal caches and must not be mutated.
254
+
255
+ ```typescript
256
+ resolver.forEachIndexed((feature, ring, boundingBox) => {
257
+ // ring is the parsed exterior ring (number[][]).
258
+ // boundingBox is the pre-computed axis-aligned bbox.
259
+ });
260
+ ```
261
+
172
262
  ### ARTCC altitude bounds
173
263
 
174
264
  ARTCC features carry stratum-aligned floor/ceiling values for use with
package/dist/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Pure logic library for point-in-airspace queries against US airspace polygons.
4
+ */
1
5
  export { createAirspaceResolver } from './resolver.js';
2
- export type { AirspaceResolver, AirspaceResolverOptions, AirspaceQuery } from './resolver.js';
6
+ export type { AirspaceResolver, AirspaceResolverOptions, AirspaceQuery, AirspaceCentroidQuery, AirspaceByIdentifierOptions, } from './resolver.js';
3
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACvD,YAAY,EACV,gBAAgB,EAChB,uBAAuB,EACvB,aAAa,EACb,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1 +1,5 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Pure logic library for point-in-airspace queries against US airspace polygons.
4
+ */
1
5
  export { createAirspaceResolver } from './resolver.js';
@@ -1,4 +1,5 @@
1
1
  import type { FeatureCollection } from 'geojson';
2
+ import { type BoundingBox } from '@squawk/geo';
2
3
  import type { AirspaceFeature, AirspaceType, ArtccStratum } from '@squawk/types';
3
4
  /**
4
5
  * A query describing a geographic position and altitude to resolve
@@ -21,6 +22,48 @@ export interface AirspaceQuery {
21
22
  */
22
23
  types?: ReadonlySet<AirspaceType>;
23
24
  }
25
+ /**
26
+ * A query describing a geographic position and tolerance for a centroid-based
27
+ * airspace lookup.
28
+ */
29
+ export interface AirspaceCentroidQuery {
30
+ /** Longitude in decimal degrees (WGS84). */
31
+ lon: number;
32
+ /** Latitude in decimal degrees (WGS84). */
33
+ lat: number;
34
+ /**
35
+ * Optional tolerance in degrees for the centroid match. A feature is
36
+ * returned when both `|centroidLon - lon|` and `|centroidLat - lat|`
37
+ * fall below this value. Defaults to `0.0001` (~11 m), generous enough to
38
+ * absorb floating-point round-trips through URL parsing for centroids
39
+ * encoded to ~5 decimal places.
40
+ */
41
+ toleranceDeg?: number;
42
+ }
43
+ /**
44
+ * Options accepted by {@link AirspaceResolver.byIdentifier}.
45
+ */
46
+ export interface AirspaceByIdentifierOptions {
47
+ /**
48
+ * Optional set of airspace types to include in the results. When provided,
49
+ * acts as an inclusion filter and overrides the partition between ARTCC and
50
+ * non-ARTCC features: callers who want ARTCC results in addition to the
51
+ * usual partition include `'ARTCC'` in this set explicitly, and callers who
52
+ * want only non-ARTCC results pass a set of the non-ARTCC types. When
53
+ * omitted, every type is eligible (subject to {@link includeArtcc}).
54
+ */
55
+ types?: ReadonlySet<AirspaceType>;
56
+ /**
57
+ * When `true` (the default), ARTCC features for the identifier are included
58
+ * alongside the airport-associated and SUA features. When `false`, ARTCC
59
+ * features are excluded - useful when you only want the non-ARTCC partition
60
+ * for an identifier without enumerating every non-ARTCC type yourself.
61
+ *
62
+ * Ignored when {@link types} is provided: `types` is the authoritative
63
+ * inclusion list in that case.
64
+ */
65
+ includeArtcc?: boolean;
66
+ }
24
67
  /**
25
68
  * Options for creating an airspace resolver.
26
69
  */
@@ -77,6 +120,76 @@ export interface AirspaceResolver {
77
120
  * @returns All matching ARTCC features, or an empty array.
78
121
  */
79
122
  byArtcc(identifier: string, stratum?: ArtccStratum): AirspaceFeature[];
123
+ /**
124
+ * Returns every airspace feature whose polygon centroid lies within the
125
+ * given tolerance of the query coordinates. Useful for resolving features
126
+ * that have an empty `identifier` (some Class E5 surfaces) and therefore
127
+ * have no stable identifier-keyed lookup - the polygon centroid is the
128
+ * fallback handle.
129
+ *
130
+ * Reach for this when you have a centroid encoded into a URL or other
131
+ * external string and want to recover the original feature(s); for
132
+ * identifier-keyed lookups, prefer {@link byIdentifier} (or the more
133
+ * specific {@link byAirport} / {@link byArtcc}). Centroid is computed
134
+ * per call - no internal caching - so this is O(n) over the indexed
135
+ * corpus, suitable for occasional URL-driven lookups but not for
136
+ * tight loops.
137
+ *
138
+ * @param query - Query coordinates and optional tolerance.
139
+ * @returns All features whose centroid is within tolerance, in dataset order.
140
+ */
141
+ byCentroid(query: AirspaceCentroidQuery): AirspaceFeature[];
142
+ /**
143
+ * Returns every airspace feature for the given identifier across both the
144
+ * ARTCC and non-ARTCC partitions, independent of position or altitude.
145
+ * Lookup is case-insensitive.
146
+ *
147
+ * Reach for this when you have an identifier whose airspace type is not
148
+ * known up-front (e.g. parsed from a URL) and you want a single call that
149
+ * returns the matching feature(s) regardless of partition. For ergonomic
150
+ * shortcuts when the partition is known, prefer {@link byAirport} (returns
151
+ * non-ARTCC features only) or {@link byArtcc} (returns ARTCC features
152
+ * only) - those wrappers encode the common "shells for this airport" /
153
+ * "stratums for this center" questions and stay available alongside this
154
+ * type-agnostic form.
155
+ *
156
+ * @param identifier - FAA identifier, NASR designator, or ARTCC code.
157
+ * @param options - Optional `types` inclusion filter and `includeArtcc`
158
+ * toggle. See {@link AirspaceByIdentifierOptions}.
159
+ * @returns All matching features, or an empty array.
160
+ */
161
+ byIdentifier(identifier: string, options?: AirspaceByIdentifierOptions): AirspaceFeature[];
162
+ /**
163
+ * Returns every airspace feature whose pre-indexed bounding box overlaps
164
+ * the given bounding box. Reuses the bounding box computed once at
165
+ * resolver creation time rather than recomputing per call, so this is
166
+ * suitable for tight loops over the corpus (e.g. a chip rebuild against
167
+ * a selection footprint).
168
+ *
169
+ * Bounding-box overlap is a coarse spatial filter: it matches any feature
170
+ * whose axis-aligned rectangle intersects the query rectangle, including
171
+ * features whose actual polygon does not. Callers that need true
172
+ * polygon-polygon intersection should follow up with their own geometry
173
+ * test on the returned features.
174
+ *
175
+ * @param bbox - Query bounding box.
176
+ * @returns All features whose pre-indexed bounding box overlaps, in dataset order.
177
+ */
178
+ withinBbox(bbox: BoundingBox): AirspaceFeature[];
179
+ /**
180
+ * Iterates the indexed corpus in dataset order, invoking `callback` once
181
+ * per feature with the parsed feature, its exterior ring, and its
182
+ * pre-computed bounding box. Exposes the resolver's pre-parsed shape so
183
+ * callers that need to filter the corpus themselves do not have to
184
+ * reparse the source GeoJSON or recompute geometry per call.
185
+ *
186
+ * The `ring` and `boundingBox` arguments are the resolver's internal
187
+ * caches and must not be mutated by the callback - copy them first if a
188
+ * mutation is needed.
189
+ *
190
+ * @param callback - Function invoked once per indexed feature.
191
+ */
192
+ forEachIndexed(callback: (feature: AirspaceFeature, ring: readonly number[][], boundingBox: BoundingBox) => void): void;
80
193
  }
81
194
  /**
82
195
  * Creates a stateless airspace resolver. The resolver accepts a GeoJSON
@@ -103,6 +216,9 @@ export interface AirspaceResolver {
103
216
  * const overhead = resolver.query({ lat: 33.9425, lon: -118.4081, altitudeFt: 3000 });
104
217
  * const laxShells = resolver.byAirport('LAX');
105
218
  * const newYorkArtcc = resolver.byArtcc('ZNY');
219
+ * const anyZnyFeature = resolver.byIdentifier('ZNY');
220
+ * const nearbyByCentroid = resolver.byCentroid({ lon: -118.4, lat: 33.9 });
221
+ * const overlapping = resolver.withinBbox({ minLon: -119, minLat: 33, maxLon: -118, maxLat: 35 });
106
222
  * ```
107
223
  */
108
224
  export declare function createAirspaceResolver(options: AirspaceResolverOptions): AirspaceResolver;
@@ -1 +1 @@
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"}
1
+ {"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"}
package/dist/resolver.js CHANGED
@@ -1,4 +1,4 @@
1
- import { polygon } from '@squawk/geo';
1
+ import { polygon, polygonGeoJson } from '@squawk/geo';
2
2
  import { altitudeMatches } from './vertical-filter.js';
3
3
  /**
4
4
  * Parses a GeoJSON Feature into an IndexedFeature, extracting the
@@ -57,6 +57,9 @@ function parseFeature(geoFeature) {
57
57
  * const overhead = resolver.query({ lat: 33.9425, lon: -118.4081, altitudeFt: 3000 });
58
58
  * const laxShells = resolver.byAirport('LAX');
59
59
  * const newYorkArtcc = resolver.byArtcc('ZNY');
60
+ * const anyZnyFeature = resolver.byIdentifier('ZNY');
61
+ * const nearbyByCentroid = resolver.byCentroid({ lon: -118.4, lat: 33.9 });
62
+ * const overlapping = resolver.withinBbox({ minLon: -119, minLat: 33, maxLon: -118, maxLat: 35 });
60
63
  * ```
61
64
  */
62
65
  export function createAirspaceResolver(options) {
@@ -120,5 +123,49 @@ export function createAirspaceResolver(options) {
120
123
  }
121
124
  return artccFeatures.filter((f) => f.artccStratum === stratum);
122
125
  },
126
+ byCentroid(query) {
127
+ const tolerance = query.toleranceDeg ?? 0.0001;
128
+ const results = [];
129
+ for (const { feature } of indexed) {
130
+ const centroid = polygonGeoJson.polygonCentroid(feature.boundary);
131
+ if (centroid === undefined) {
132
+ continue;
133
+ }
134
+ if (Math.abs(centroid[0] - query.lon) < tolerance &&
135
+ Math.abs(centroid[1] - query.lat) < tolerance) {
136
+ results.push(feature);
137
+ }
138
+ }
139
+ return results;
140
+ },
141
+ byIdentifier(identifier, options) {
142
+ const bucket = byIdentifierMap.get(identifier.toUpperCase());
143
+ if (bucket === undefined) {
144
+ return [];
145
+ }
146
+ const types = options?.types;
147
+ if (types !== undefined) {
148
+ return bucket.filter((f) => types.has(f.type));
149
+ }
150
+ const includeArtcc = options?.includeArtcc ?? true;
151
+ if (includeArtcc) {
152
+ return bucket.slice();
153
+ }
154
+ return bucket.filter((f) => f.type !== 'ARTCC');
155
+ },
156
+ withinBbox(bbox) {
157
+ const results = [];
158
+ for (const { feature, boundingBox } of indexed) {
159
+ if (polygonGeoJson.boundingBoxesOverlap(bbox, boundingBox)) {
160
+ results.push(feature);
161
+ }
162
+ }
163
+ return results;
164
+ },
165
+ forEachIndexed(callback) {
166
+ for (const { feature, ring, boundingBox } of indexed) {
167
+ callback(feature, ring, boundingBox);
168
+ }
169
+ },
123
170
  };
124
171
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squawk/airspace",
3
- "version": "0.7.1",
3
+ "version": "0.8.6",
4
4
  "type": "module",
5
5
  "description": "Pure logic library for querying US airspace geometry by position and altitude",
6
6
  "author": "Neil Cochran",
@@ -36,7 +36,9 @@
36
36
  "test": "vitest run",
37
37
  "test:coverage": "vitest run --coverage",
38
38
  "lint": "tsc --noEmit && eslint src",
39
- "lint:pack": "publint && attw --pack . --profile esm-only"
39
+ "lint:pack": "publint && attw --pack . --profile esm-only",
40
+ "api:check": "api-extractor run --verbose",
41
+ "api:report": "api-extractor run --local --verbose"
40
42
  },
41
43
  "dependencies": {
42
44
  "@squawk/geo": "^0.4.3",
@@ -45,7 +47,7 @@
45
47
  },
46
48
  "devDependencies": {
47
49
  "@squawk/airspace-data": "^0.5.2",
48
- "@types/node": "^25.6.0"
50
+ "@types/node": "^25.9.3"
49
51
  },
50
52
  "keywords": [
51
53
  "aviation",