@chrtco/sdk 0.9.1 → 0.10.0

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/dist/index.d.cts CHANGED
@@ -123,6 +123,100 @@ interface FeatureCollection<P = Record<string, unknown>> {
123
123
  limit?: number;
124
124
  offset?: number;
125
125
  }
126
+ interface AheadOptions {
127
+ /** [longitude, latitude] of the craft or fixed origin */
128
+ point: [number, number];
129
+ /**
130
+ * Degrees true, 0–360 — a craft's COG/heading, or any fixed input.
131
+ * Values outside 0–360 are normalised (370 → 10).
132
+ */
133
+ bearing: number;
134
+ /**
135
+ * Full sector width in degrees, centred on `bearing` (default 90). So `arc: 60`
136
+ * sweeps ±30° either side. `arc: 360` degenerates to a full circle, making this a
137
+ * generalisation of {@link QueryOptions | query}.
138
+ */
139
+ arc?: number;
140
+ /** Range in metres (default 5000, max 50000). Mutually exclusive with `rangeNm`. */
141
+ range?: number;
142
+ /** Range in nautical miles — convenience for `range` (1 nm = 1852 m). */
143
+ rangeNm?: number;
144
+ /**
145
+ * Your safety depth in metres — typically vessel draft plus a margin, and the same
146
+ * number you'd set as the style's safety contour.
147
+ *
148
+ * Filters out only what is *known* to be deeper than this. Features with no charted
149
+ * depth are always kept: a wreck with no sounded value is still a wreck.
150
+ */
151
+ safetyDepth?: number;
152
+ /** Comma-separated S-57 class codes. Defaults to CHRT's significant-feature set. */
153
+ layer?: string;
154
+ limit?: number;
155
+ /** AbortSignal to cancel the request */
156
+ signal?: AbortSignal;
157
+ }
158
+ /** Per-feature facts returned by {@link AheadResponse}. CHRT ranks nothing — you sort. */
159
+ interface AheadFeatureProperties {
160
+ layer: string;
161
+ cell_id: string;
162
+ /** Geodesic metres to the nearest point of the feature. */
163
+ distance_m: number;
164
+ /**
165
+ * Degrees true to the nearest point of the feature, or `null` when the origin lies
166
+ * on/inside it (e.g. your boat is inside a depth area).
167
+ *
168
+ * May fall *outside* the requested arc: membership is decided by whether the feature
169
+ * intersects the sector, while this reports where its nearest point lies — a large
170
+ * polygon can clip the sector edge from outside it.
171
+ */
172
+ bearing_deg: number | null;
173
+ /** Least charted depth in metres (VALSOU / DEPTH / DRVAL1), or `null` if unknown. */
174
+ least_depth_m: number | null;
175
+ /**
176
+ * `least_depth_m − safetyDepth`. Negative means shallower than you declared safe.
177
+ * `null` when either value is unknown. A fact, not a severity — see the class docs.
178
+ */
179
+ depth_clearance_m: number | null;
180
+ [key: string]: unknown;
181
+ }
182
+ /** An ENC cell covering some part of the swept sector, with its currency. */
183
+ interface AheadCoverageCell {
184
+ cell_id: string;
185
+ title: string | null;
186
+ chart_source: string;
187
+ scale: number | null;
188
+ scale_band: string | null;
189
+ edition: number | null;
190
+ update_number: number | null;
191
+ issue_date: string | null;
192
+ }
193
+ interface AheadResponse extends FeatureCollection<AheadFeatureProperties> {
194
+ query: {
195
+ point: [number, number];
196
+ bearing_deg: number;
197
+ arc_deg: number;
198
+ range_m: number;
199
+ safety_depth_m: number | null;
200
+ layers: string[];
201
+ };
202
+ /**
203
+ * Coverage of the swept sector — the reason an empty `features` array is safe to
204
+ * return. Zero features in covered water and zero features because CHRT has no cells
205
+ * there are different answers; **only this block tells them apart.**
206
+ */
207
+ coverage: {
208
+ covered: boolean;
209
+ /** 0–1: how much of the sector any ENC cell covers. */
210
+ covered_fraction: number;
211
+ cells: AheadCoverageCell[];
212
+ };
213
+ /** Issuing-office attribution. Always present; never strip it from a UI. */
214
+ attribution: string;
215
+ /** The non-SOLAS disclaimer. Always present. */
216
+ disclaimer: string;
217
+ /** Why an empty result is not an all-clear. */
218
+ notice: string;
219
+ }
126
220
  interface FeaturesOptions {
127
221
  /** Bounding box as [west, south, east, north] */
128
222
  bbox: [number, number, number, number];
@@ -138,20 +232,61 @@ interface FeaturesOptions {
138
232
  interface QueryOptions {
139
233
  /** [longitude, latitude] */
140
234
  point: [number, number];
141
- /** Search radius in metres (default 1000) */
235
+ /** Search radius in metres (default 500, clamped 1–50 000) */
142
236
  radius?: number;
143
- /** Comma-separated layer codes */
237
+ /** Comma-separated layer codes. Unfiltered by default — see {@link AheadOptions.layer}. */
144
238
  layer?: string;
239
+ /** Max features to return (default 50, clamped to 1–500). */
145
240
  limit?: number;
146
241
  /** AbortSignal to cancel the request */
147
242
  signal?: AbortSignal;
148
243
  }
244
+ /** Per-feature facts returned by {@link QueryResponse}. */
245
+ interface QueryFeatureProperties {
246
+ layer: string;
247
+ cell_id: string;
248
+ /** Geodesic metres from the queried point to the nearest point of the feature. */
249
+ distance_m: number;
250
+ [key: string]: unknown;
251
+ }
252
+ /**
253
+ * Response from {@link Chrt.query}. `/v1/query` is not paginated — it returns
254
+ * `limit` but no `offset`, so page with a wider `radius` rather than an offset.
255
+ */
256
+ interface QueryResponse extends FeatureCollection<QueryFeatureProperties> {
257
+ /** The point and radius the server actually applied, after clamping. */
258
+ query: {
259
+ point: [number, number];
260
+ radius_m: number;
261
+ };
262
+ }
263
+ /**
264
+ * S-57 usage band, derived by CHRT from the cell's compilation scale — the
265
+ * navigational purpose the chart was drawn for, from `Overview` (>1:1.5M) down to
266
+ * `Harbor` (<1:22k). A closed set: CHRT assigns these itself rather than taking them
267
+ * from the issuing office.
268
+ */
269
+ type ScaleBand$1 = "Overview" | "General" | "Coastal" | "Approach" | "Harbor";
149
270
  interface CellsOptions {
150
- /** Filter by chart source, e.g. "NOAA" */
271
+ /**
272
+ * Filter by chart-source **id**, e.g. `"NOAA_ENC"` — not the agency name
273
+ * (`"NOAA"`), which matches no cell and returns an empty list rather than an
274
+ * error. `chrt.coverage.list()` enumerates the valid ids.
275
+ */
151
276
  source?: string;
152
- /** Filter by status */
277
+ /** Filter by status, e.g. "CURRENT". Case-insensitive — the server upper-cases it. */
153
278
  status?: string;
279
+ /** Filter by scale band. Matched exactly, and **case-sensitively** — `"harbor"` matches nothing. */
280
+ scaleBand?: ScaleBand$1;
281
+ /**
282
+ * Only cells whose bounds intersect this box, as [west, south, east, north]
283
+ * in decimal degrees. Intersection, not containment — a cell that merely
284
+ * overlaps an edge is included.
285
+ */
286
+ bbox?: [number, number, number, number];
287
+ /** Max cells to return (default 100, clamped to 1–1000). */
154
288
  limit?: number;
289
+ /** Pagination offset. Page with `offset` against `total_count`. */
155
290
  offset?: number;
156
291
  /** AbortSignal to cancel the request */
157
292
  signal?: AbortSignal;
@@ -162,14 +297,31 @@ interface CellDetail {
162
297
  chart_source: string;
163
298
  status: string;
164
299
  scale: number | null;
300
+ /**
301
+ * In practice a {@link ScaleBand}, but typed as `string` deliberately: this is
302
+ * whatever is stored for the cell, and narrowing a response to a union the
303
+ * database does not enforce would be a promise the wire cannot keep.
304
+ */
165
305
  scale_band: string | null;
166
306
  edition: number | null;
167
307
  update_number: number | null;
168
308
  issue_date: string | null;
309
+ /**
310
+ * Bounds in decimal degrees (WGS 84), or `null` when the issuing office recorded
311
+ * none. Written as a group by every ingest path, so in practice the four are all
312
+ * set or all `null` — but check the one you read rather than inferring it from
313
+ * another. Same flat shape as {@link CoverageRouteCell} on `/v1/coverage/route`.
314
+ */
169
315
  bbox_north: number | null;
170
316
  bbox_south: number | null;
171
317
  bbox_east: number | null;
172
318
  bbox_west: number | null;
319
+ /**
320
+ * ISO 8601 timestamp of when CHRT last checked the issuing office's catalogue
321
+ * for this cell — freshness of the *check*, not of the chart. Compare
322
+ * `edition` / `update_number` / `issue_date` for chart currency.
323
+ */
324
+ last_checked_at: string | null;
173
325
  }
174
326
  interface CellsResponse {
175
327
  cells: CellDetail[];
@@ -177,58 +329,124 @@ interface CellsResponse {
177
329
  limit: number;
178
330
  offset: number;
179
331
  }
180
- interface RouteQueryOptions {
181
- /** Array of [longitude, latitude] waypoints forming the route */
332
+ interface AlongRouteOptions {
333
+ /** The passage as `[longitude, latitude]` waypoints. At least 2. */
182
334
  route: [number, number][];
183
- /** Buffer distance in metres (default 1000, max 50000) */
335
+ /** Corridor half-width in metres either side of the track (default 500, max 50000). */
184
336
  buffer?: number;
185
- /** Filter by chart source, e.g. "NOAA" */
186
- source?: string;
187
- /** Filter by scale band, e.g. "4" */
188
- scaleBand?: string;
189
- /** Max cells to return (default 100) */
337
+ /** Corridor half-width in nautical miles — convenience for `buffer`. */
338
+ bufferNm?: number;
339
+ /**
340
+ * Your safety depth in metres — typically draft plus a margin.
341
+ *
342
+ * Filters out only what is *known* to be deeper. Features with no charted depth are
343
+ * always kept: a wreck with no sounded value is still a wreck.
344
+ */
345
+ safetyDepth?: number;
346
+ /** Comma-separated S-57 class codes. Defaults to CHRT's significant-feature set. */
347
+ layer?: string;
190
348
  limit?: number;
349
+ offset?: number;
191
350
  /** AbortSignal to cancel the request */
192
351
  signal?: AbortSignal;
193
352
  }
194
- interface RouteQueryResponse {
195
- cells: CellDetail[];
353
+ /** Per-feature facts from {@link AlongRouteResponse}. CHRT ranks nothing — you sort. */
354
+ interface AlongRouteFeatureProperties {
355
+ layer: string;
356
+ cell_id: string;
357
+ /** Geodesic metres from the track to the nearest point of the feature. */
358
+ distance_m: number;
359
+ /**
360
+ * Metres along the route to the point of closest approach — i.e. how far into the
361
+ * passage you meet this. Results are ordered by it, because that is the order you
362
+ * actually encounter things in. `null` if the route is not a simple LineString.
363
+ */
364
+ along_m: number | null;
365
+ /** Least charted depth in metres (VALSOU / DEPTH / DRVAL1), or `null` if unknown. */
366
+ least_depth_m: number | null;
367
+ /**
368
+ * `least_depth_m − safetyDepth`. Negative means shallower than you declared safe.
369
+ * A fact, not a severity.
370
+ */
371
+ depth_clearance_m: number | null;
372
+ [key: string]: unknown;
373
+ }
374
+ interface AlongRouteResponse extends FeatureCollection<AlongRouteFeatureProperties> {
196
375
  query: {
376
+ route_points: number;
377
+ route_length_m: number | null;
197
378
  buffer_m: number;
198
- cell_count: number;
379
+ safety_depth_m: number | null;
380
+ layers: string[];
199
381
  };
200
- total_count: number;
382
+ /**
383
+ * Which ENC cells cover the corridor. As with {@link AheadResponse}, this is what
384
+ * makes an empty `features` array safe to return: no features in charted water and
385
+ * no features because there is no chart are different answers.
386
+ */
387
+ coverage: {
388
+ covered: boolean;
389
+ cells: AheadCoverageCell[];
390
+ };
391
+ attribution: string;
392
+ disclaimer: string;
393
+ notice: string;
201
394
  }
202
- interface RouteFeatureQueryOptions {
203
- /** Array of [longitude, latitude] waypoints forming the route */
395
+ interface CoverageRouteOptions {
396
+ /** The passage as `[longitude, latitude]` waypoints. At least 2. */
204
397
  route: [number, number][];
205
- /** Buffer distance in metres (default 1000, max 50000) */
398
+ /** Corridor half-width in metres (default 1000, max 50000). */
206
399
  buffer?: number;
207
- /** Comma-separated layer codes to filter */
208
- layer?: string;
209
- /** Max features to return (default 100) */
400
+ /** Corridor half-width in nautical miles — convenience for `buffer`. */
401
+ bufferNm?: number;
402
+ /**
403
+ * Restrict to one scale band, e.g. `"Harbor"` for harbour cells. Matched exactly and
404
+ * **case-sensitively** against the same column `chrt.cells.list()` filters — the
405
+ * bands are words, not band numbers, so `"5"` matches nothing and returns an empty
406
+ * `cells` list rather than an error.
407
+ */
408
+ scaleBand?: ScaleBand$1;
210
409
  limit?: number;
211
- /** Pagination offset */
212
- offset?: number;
213
410
  /** AbortSignal to cancel the request */
214
411
  signal?: AbortSignal;
215
412
  }
216
- interface RouteFeatureQueryResponse extends FeatureCollection {
413
+ interface CoverageRouteCell extends AheadCoverageCell {
414
+ bbox_north: number | null;
415
+ bbox_south: number | null;
416
+ bbox_east: number | null;
417
+ bbox_west: number | null;
418
+ /** Geodesic metres from the route to the cell's footprint. 0 if the route crosses it. */
419
+ distance_m: number;
420
+ }
421
+ interface CoverageRouteResponse {
422
+ /** Largest scale (most detailed) first — S-57 says believe the finer chart. */
423
+ cells: CoverageRouteCell[];
217
424
  query: {
425
+ route_points: number;
218
426
  buffer_m: number;
219
- route_length_m?: number;
427
+ scale_band: string | null;
428
+ cell_count: number;
220
429
  };
430
+ total_count: number;
431
+ limit: number;
432
+ attribution: string;
433
+ disclaimer: string;
434
+ /** An empty `cells` list means CHRT holds no chart for this passage. */
435
+ notice: string;
221
436
  }
222
437
  interface CoverageSource {
223
- /** Chart source ID, e.g. "NOAA" */
438
+ /**
439
+ * Chart-source id, e.g. `"NOAA_ENC"`. This is the value `CellsOptions.source`
440
+ * filters on — pass this, not {@link CoverageSource.agency}.
441
+ */
224
442
  source: string;
225
443
  /** ISO 3166-1 alpha-2 country code */
226
444
  country: string;
227
- /** Display name of the agency */
445
+ /** Display name of the issuing office, e.g. `"NOAA"`. Never a filter value. */
228
446
  agency: string;
229
447
  /** Number of available ENC cells */
230
448
  cell_count: number;
231
- /** Scale bands with cell counts */
449
+ /** Cell counts keyed by {@link ScaleBand}, e.g. `{ Harbor: 46, Coastal: 10 }`. */
232
450
  scale_bands?: Record<string, number>;
233
451
  }
234
452
  interface CoverageResponse {
@@ -236,9 +454,9 @@ interface CoverageResponse {
236
454
  total_cells: number;
237
455
  }
238
456
  interface CoverageOptions {
239
- /** Filter by country code */
457
+ /** Filter by country code, e.g. `"US"` */
240
458
  country?: string;
241
- /** Filter by chart source */
459
+ /** Filter by chart-source id, e.g. `"NOAA_ENC"` — see {@link CellsOptions.source}. */
242
460
  source?: string;
243
461
  /** AbortSignal to cancel the request */
244
462
  signal?: AbortSignal;
@@ -262,9 +480,44 @@ interface ChrtErrorBody {
262
480
  code?: string;
263
481
  message?: string;
264
482
  }
483
+ /**
484
+ * The vessel you're answering for — one value, read by every depth-aware call.
485
+ *
486
+ * CHRT has historically carried several disagreeing notions of "how deep is safe":
487
+ * the sounding panel's safety contour, the isolated-danger ring's threshold, and the
488
+ * query's `safetyDepth`. This is the single place a client states it once.
489
+ *
490
+ * **It is deliberately client-side and per-instance, not stored on your API key.** A
491
+ * consumer app has one key and many skippers with different boats; a key-scoped draft
492
+ * would give them all the same one. Construct a `Chrt` per user (or pass `safetyDepth`
493
+ * per call) and each gets their own.
494
+ */
495
+ interface VesselProfile {
496
+ /**
497
+ * Safety depth in metres — your draft plus whatever margin you want, i.e. the depth
498
+ * below which you consider water unsafe for *this* vessel.
499
+ *
500
+ * Becomes the default for `ahead({ safetyDepth })`, `alongRoute({ safetyDepth })`
501
+ * and `style({ safetyContour })`. Any explicit per-call value still wins.
502
+ */
503
+ safetyDepthM: number;
504
+ }
265
505
  interface ChrtConfig {
266
- /** API key (chrt_live_… or chrt_test_…) */
506
+ /** API key — `chrt_{pk|sk}_{live|test}_{team}_{random}`. `pk` and `sk` carry
507
+ * identical privileges: the prefix records intent and re-revealability, not
508
+ * capability. Treat any key embedded in a client as public. */
267
509
  apiKey: string;
510
+ /**
511
+ * The vessel this client is answering for. Sets one safety depth that defaults
512
+ * every depth-aware call — the query's `safetyDepth` and the generated style's
513
+ * `safetyContour` — so they cannot silently disagree.
514
+ *
515
+ * @example
516
+ * const chrt = new Chrt({ apiKey, vessel: { safetyDepthM: 3 } });
517
+ * await chrt.ahead({ point, bearing }); // filters at 3 m
518
+ * chrt.style(); // shades and rings at 3 m
519
+ */
520
+ vessel?: VesselProfile;
268
521
  /** Override base URL (default https://api.chrt.co/v1) */
269
522
  baseUrl?: string;
270
523
  /** Max retries on 5xx/network errors (default 2) */
@@ -542,6 +795,8 @@ declare class TileAuth {
542
795
  private registerLifecycleHandlers;
543
796
  }
544
797
 
798
+ /** Symbol-artwork families served by the Style API. */
799
+ type ChrtSymbolSet = "s52" | "s101" | "chrt" | "raster";
545
800
  /** Palette themes served by the Style API (S-52 colour tables). */
546
801
  type ChrtStyleTheme = "day" | "dusk" | "night";
547
802
  interface StyleUrlOptions {
@@ -549,11 +804,12 @@ interface StyleUrlOptions {
549
804
  * default — for keys with a bound CHRTstyle custom style, that's the
550
805
  * style's default palette. */
551
806
  theme?: ChrtStyleTheme;
552
- /** Symbol artwork family: "s52" (default) or "s101" (official S-101
553
- * Portrayal Catalogue symbols). Load the matching images client-side
554
- * via `loadS101SymbolImages` / `loadChrtSymbolImages` from
555
- * `@chrtco/sdk/atlas/browser`. */
556
- symbols?: "s52" | "s101" | "chrt";
807
+ /** Symbol-artwork family: `s52` (default PresLib set), `s101` (official
808
+ * S-101 Portrayal Catalogue symbols), `chrt` (the CHRT house set), or
809
+ * `raster` (CHRTraster — the traditional paper-chart portrayal that
810
+ * raster charts are scans of). Load the matching images client-side
811
+ * from `@chrtco/sdk/atlas/browser`. */
812
+ symbols?: ChrtSymbolSet;
557
813
  }
558
814
  interface FetchStyleOptions extends StyleUrlOptions {
559
815
  signal?: AbortSignal;
@@ -629,6 +885,30 @@ declare class CoverageEndpoint {
629
885
  constructor(http: HttpClient);
630
886
  /** List available chart sources with cell counts */
631
887
  list(options?: CoverageOptions): Promise<CoverageResponse>;
888
+ /**
889
+ * Which ENC cells cover a passage, and how current they are.
890
+ *
891
+ * Lives on `coverage`, not `query`, deliberately: it returns *cells*, so it answers
892
+ * "which charts must I hold for this passage?" — a download-planning question, and
893
+ * the natural companion of CHRToffline. For "what might I hit along this route?",
894
+ * use `chrt.alongRoute()`.
895
+ *
896
+ * @example
897
+ * ```ts
898
+ * const { cells } = await chrt.coverage.forRoute({
899
+ * route: [[-70.95, 42.33], [-71.0, 42.35]],
900
+ * bufferNm: 1,
901
+ * });
902
+ * // cells arrive largest-scale first — S-57 says believe the finer chart
903
+ * for (const c of cells) {
904
+ * console.log(c.cell_id, `ed ${c.edition}/${c.update_number}`, c.issue_date);
905
+ * }
906
+ * ```
907
+ *
908
+ * @remarks An empty `cells` array means CHRT holds no chart for this passage — a
909
+ * reason to stop and check official charts, never a sign the passage is clear.
910
+ */
911
+ forRoute(options: CoverageRouteOptions): Promise<CoverageRouteResponse>;
632
912
  }
633
913
 
634
914
  /**
@@ -1594,6 +1874,29 @@ declare function getLookupEntry(layerName: string): LookupEntry;
1594
1874
  * Each function takes feature properties and returns paint overrides (hex colours).
1595
1875
  * All expression builders accept an optional colorTable parameter.
1596
1876
  */
1877
+ /**
1878
+ * Default safety contour in metres — the depth below which a feature is
1879
+ * "dangerous to my hull" for portrayal purposes.
1880
+ *
1881
+ * This is the single source of truth for the *default* used by every
1882
+ * safety-contour-driven conditional symbology rule, so the rules can't
1883
+ * silently disagree with each other:
1884
+ *
1885
+ * - §SOUNDG02 — soundings shoaler than the contour get the
1886
+ * high-contrast shade (see `getLabelConfig` in labels.ts).
1887
+ * - §CSYM02 — isolated dangers shoaler than the contour get the
1888
+ * DNGCIR01 ring (see `buildLayersForClass` in layer-builder.ts).
1889
+ *
1890
+ * Both are overridden by the user's setting (sounding panel slider,
1891
+ * `safetyContour` in the style options); this is only the fallback when
1892
+ * none is supplied. It matches `custom-style.ts`'s config default.
1893
+ *
1894
+ * NOTE: `depareFillExpression` below deliberately does NOT use this — its
1895
+ * 2/5/10 m bands are the S-52 four-shade DEPARE ramp, a separate concept
1896
+ * from the safety contour. Wiring those to the user's contour would change
1897
+ * rendering for every existing user and needs its own decision.
1898
+ */
1899
+ declare const DEFAULT_SAFETY_CONTOUR_M = 10;
1597
1900
  declare function depareColor(props: Record<string, unknown>, ct?: ColorTableName): string;
1598
1901
  declare function depareFillExpression(ct?: ColorTableName): unknown[];
1599
1902
  declare function lightColor(props: Record<string, unknown>, ct?: ColorTableName): string;
@@ -2090,8 +2393,15 @@ interface StyleLayer {
2090
2393
  *
2091
2394
  * Post-consolidation: all S-57 classes live as MVT layers inside one
2092
2395
  * consolidated PMTiles per region; `source` is always the region name.
2396
+ *
2397
+ * `opts.safetyContour` is the mariner's safety depth in metres — it drives
2398
+ * the §CSYM02 isolated-danger ring on hazard classes. Defaults to
2399
+ * `DEFAULT_SAFETY_CONTOUR_M`, the same default the SOUNDG two-shade rule
2400
+ * uses, so the ring and the sounding colour always agree.
2093
2401
  */
2094
- declare function buildLayersForClass(objectClass: string, ct: ColorTableName, sourceId?: string): StyleLayer[];
2402
+ declare function buildLayersForClass(objectClass: string, ct: ColorTableName, sourceId?: string, opts?: {
2403
+ safetyContour?: number;
2404
+ }): StyleLayer[];
2095
2405
  /**
2096
2406
  * Get the full ordered list of object classes for style generation.
2097
2407
  * Uses S52_DRAW_ORDER as base, then adds any missing classes from the
@@ -2257,6 +2567,64 @@ declare function withinBboxFilter(bbox: BBox): unknown[];
2257
2567
  */
2258
2568
  declare function worldMinusBboxPolygon(bbox: BBox): GeoJSON.Polygon;
2259
2569
 
2570
+ /**
2571
+ * Dominance fan-out collapse (P8 Phase 3.3 / D4 "axis 1").
2572
+ *
2573
+ * `makeBandVariants()` historically emitted, per (class, band): one
2574
+ * "not-dominated" layer plus up to five `-domBy{D}` copies that differ ONLY
2575
+ * in their dominance filter and a `maxzoom` of `dominatedVariantMaxzoom(D)`.
2576
+ * That fan existed because MapLibre filters could not read `["zoom"]`. They
2577
+ * can now (style-spec 24.8.5; buckets evaluate at the tile's `overscaledZ`),
2578
+ * so the whole fan folds into ONE layer per (class, band) whose filter
2579
+ * carries the zoom gate.
2580
+ *
2581
+ * **Construction principle — read before changing anything here.** The
2582
+ * collapsed filter is the literal `["any"]` UNION of the clauses the fan
2583
+ * already encoded: every variant the legacy emitter would have produced
2584
+ * contributes exactly one `["all", <its dominance filter>, ["<", ["zoom"],
2585
+ * <its maxzoom>]]` clause, and the dead-variant skip is mirrored too.
2586
+ * Equivalence is therefore *structural*, not re-derived — which matters,
2587
+ * because the two "obvious" hand-written forms are both wrong:
2588
+ *
2589
+ * 1. A `["match", ["get","_dominated_by_min"], 2,5, 3,7, 4,9, 5,11, 6,11, 24]`
2590
+ * threshold (the form the design doc proposed) uses band 6's *eligibility*
2591
+ * zoom 11. The real ceiling is `dominatedVariantMaxzoom("6") = 13` — band
2592
+ * 6's DATA floor. Using 11 reopens the z11–z12 void window that
2593
+ * `band-priority.ts` documents closing.
2594
+ * 2. That same form silently drops the legacy `_dominated: true` boolean,
2595
+ * which `dominatedByMinFilter("6")` still honours. A legacy-tagged feature
2596
+ * has no `_dominated_by_min`, so `match` falls to its default and the
2597
+ * feature renders FOREVER instead of dropping at z13.
2598
+ *
2599
+ * Deriving the gate from the emitter's own clauses avoids both by construction.
2600
+ * `band-collapse.test.ts` re-proves it on every CI run by differentially
2601
+ * evaluating legacy-vs-collapsed through MapLibre's real three gates.
2602
+ *
2603
+ * **Scope.** This is axis 1 ONLY: the per-band layers survive, with identical
2604
+ * ids and identical positions, so the band-major draw order is untouched.
2605
+ * "Axis 2" (collapsing the per-band copies into one layer per class) was
2606
+ * evaluated and **KILLED** — it is unsound for the opaque-fill classes
2607
+ * (`DOMINANCE_EXEMPT_FILL_CLASSES` ≡ `OPAQUE_FILL_LAYERS`), where band-major
2608
+ * order is the ONLY thing separating the bands, and 15/15 class pairs are
2609
+ * provably unsatisfiable with one layer each. Do not reintroduce it.
2610
+ * Evidence: `.chrt/perf/2026-07-14-p8-d4-spike.md`.
2611
+ *
2612
+ * **Load-bearing invariant.** The collapsed layer MUST keep the `-band{B}`
2613
+ * id segment: `custom-style.ts` `bandFloor()` parses `/-band(\d)/` to clamp
2614
+ * customer minzoom/scamin overrides to the band's data floor, on the live
2615
+ * public Style API path. Renaming the id silently unclamps those overrides.
2616
+ * Asserted in `band-collapse.test.ts`.
2617
+ */
2618
+
2619
+ /**
2620
+ * Which emitter the style generator uses for the dominance fan.
2621
+ * `"legacy"` exists so CI can differentially prove the two equivalent (it is
2622
+ * the oracle in `band-collapse.test.ts`) and as a one-line rollback. It is an
2623
+ * internal knob — deliberately NOT on the exported `StyleGeneratorOptions`,
2624
+ * so it cannot reach `@chrtco/sdk` consumers via `ChrtStyleOptions`.
2625
+ */
2626
+ type BandFanMode = "collapsed" | "legacy";
2627
+
2260
2628
  /**
2261
2629
  * Generates a complete Mapbox GL Style Specification v8 JSON
2262
2630
  * from the S-52 atlas definitions.
@@ -2312,11 +2680,21 @@ interface StyleGeneratorOptions extends BuildSourcesOptions {
2312
2680
  * CHRT house ATON/LIGHTS glyphs with colour-true tinting — see
2313
2681
  * chrt-style.ts. Selection logic differences are documented per module.
2314
2682
  */
2315
- symbolSet?: "s52" | "s101" | "chrt";
2683
+ symbolSet?: "s52" | "s101" | "chrt" | "raster";
2316
2684
  /**
2317
- * S-52 §SOUNDG02 — soundings shoaler than this depth (metres) render in
2318
- * the high-contrast color; deeper soundings render in the muted color.
2319
- * Default 10 m.
2685
+ * The mariner's safety contour in metres — the depth below which a
2686
+ * feature is "dangerous to my hull". Drives both safety-contour
2687
+ * conditional symbology rules:
2688
+ *
2689
+ * - §SOUNDG02 — soundings shoaler than this render in the
2690
+ * high-contrast color; deeper soundings render in the muted color.
2691
+ * - §CSYM02 — isolated dangers (rocks / wrecks / obstructions)
2692
+ * shoaler than this get the DNGCIR01 danger ring.
2693
+ *
2694
+ * Default `DEFAULT_SAFETY_CONTOUR_M` (10 m).
2695
+ *
2696
+ * Does not affect the DEPARE depth-area shading, whose 2/5/10 m bands
2697
+ * are a separate S-52 concept.
2320
2698
  */
2321
2699
  safetyContour?: number;
2322
2700
  /**
@@ -2329,7 +2707,9 @@ interface StyleGeneratorOptions extends BuildSourcesOptions {
2329
2707
  */
2330
2708
  soundingFontWeight?: "regular" | "semibold";
2331
2709
  }
2332
- declare function generateStyle(palette?: ColorTableName, opts?: StyleGeneratorOptions): MapboxStyle;
2710
+ declare function generateStyle(palette?: ColorTableName, opts?: StyleGeneratorOptions & {
2711
+ bandFan?: BandFanMode;
2712
+ }): MapboxStyle;
2333
2713
  declare function generateStyleJSON(palette?: ColorTableName, opts?: StyleGeneratorOptions): string;
2334
2714
  /** Get summary stats about the generated style */
2335
2715
  declare function getStyleStats(style: MapboxStyle): {
@@ -2426,12 +2806,236 @@ declare function chrtAtonTintExpression(): unknown[];
2426
2806
  * the encoded colour, unlike the S-52 three-bucket flares); colourless
2427
2807
  * lights read as white with the dark halo carrying contrast. */
2428
2808
  declare function chrtLightTintExpression(): unknown[];
2809
+ /** Per-class house icon expressions. Classes not listed keep their S-52
2810
+ * icons (e.g. TOPMAR's minimal black shapes already fit the house look). */
2811
+ declare const CHRT_ICON_EXPRESSIONS: Record<string, () => string | unknown[]>;
2429
2812
  /**
2430
2813
  * Switch a generated style document to the CHRT house symbol set: rewrite
2431
- * the ATON/LIGHTS icon expressions and inject the colour-true tint on
2432
- * tintable layers. Returns the same style object (mutated).
2814
+ * the ATON/LIGHTS icon expressions, inject the colour-true tint on tintable
2815
+ * layers, and restore the house dash on the cable/pipeline family. Returns
2816
+ * the same style object (mutated).
2817
+ *
2818
+ * @param palette Active colour table, used to resolve the house dash tokens.
2819
+ * Defaults to DAY so existing single-arg callers keep working.
2820
+ */
2821
+ declare function applyChrtSymbolSet(style: MapboxStyle, palette?: ColorTableName): MapboxStyle;
2822
+
2823
+ declare const CHRT_GLYPH_SVGS: Record<string, string>;
2824
+
2825
+ declare const CHRTRASTER_SVGS: Record<string, string>;
2826
+ declare const CHRTRASTER_REF_CROPS: Record<string, {
2827
+ x: number;
2828
+ y: number;
2829
+ w: number;
2830
+ h: number;
2831
+ }>;
2832
+
2833
+ /**
2834
+ * CHRTraster symbol-set support — the traditional PAPER-CHART ATON
2835
+ * portrayal, so a digital chart reads like the raster charts (RNCs) that
2836
+ * are scans of the paper charts.
2837
+ *
2838
+ * Two artwork sources cover the paper lookup table
2839
+ * (`S52_ATON_LOOKUPS_PAPER` in s52-aton-lookups-generated.ts):
2840
+ *
2841
+ * - Paper-table symbols that exist as vendored PresLib VECTORS
2842
+ * (BOYCAN01, BCNTOW01, …) render from the standard S-52 image set,
2843
+ * unprefixed — no duplicate registration.
2844
+ * - The ~131 colour-banded paper variants that are raster-only in the
2845
+ * reference are synthesized as vectors (hull geometry lifted from the
2846
+ * vendored paper glyphs; colours from each symbol's own reference
2847
+ * metadata — see scripts/gen-chrtraster-svgs.ts) and registered under
2848
+ * the `raster:` image namespace.
2849
+ *
2850
+ * Selection logic is the PresLib PAPER lookup table compiled through the
2851
+ * same expression machinery as the s52 family (`compileAtonExpression`).
2852
+ * Classes whose paper table matches the s52 family's table (TOPMAR,
2853
+ * DAYMAR, FOGSIG, RTPBCN already use Paper there) keep their s52
2854
+ * expressions untouched. LIGHTS keeps the LIGHTS05/06 flare — paper
2855
+ * charts use the same magenta flare family.
2856
+ */
2857
+
2858
+ /** MapLibre image-id namespace for the synthesized paper variants. */
2859
+ declare const RASTER_ICON_PREFIX = "raster:";
2860
+ /** Synthesized paper-variant symbol names (unprefixed). */
2861
+ declare const RASTER_GLYPH_NAMES: ReadonlySet<string>;
2862
+ /**
2863
+ * Map a paper-lookup symbol name to the image id CHRTraster renders it
2864
+ * from: synthesized variants under `raster:`, vendored PresLib vectors
2865
+ * bare, unavailable names null (their rows stay dropped, exactly like the
2866
+ * generator's `dropped` accounting).
2867
+ */
2868
+ declare function resolveRasterImage(name: string): string | null;
2869
+ /**
2870
+ * Classes the CHRTraster set actually re-portrays: those whose PAPER
2871
+ * lookup differs from the s52 family's table. Computed from the generated
2872
+ * data so it can never drift from it. TOPMAR and DAYMAR are excluded for
2873
+ * the same reason they're excluded from ATON_EXPRESSION_CLASSES — their
2874
+ * paper rows reference the TOPSHP glyph family we don't synthesize, and
2875
+ * the s52 family's hand-written TOPSHP mapping already uses the paper
2876
+ * topmark vectors.
2877
+ */
2878
+ declare const RASTER_ATON_CLASSES: ReadonlyArray<string>;
2879
+ /** Paper-table icon expression for a CHRTraster class. */
2880
+ declare function rasterAtonIconExpression(objectClass: string): string | unknown[];
2881
+ /** Themed SVG for a synthesized paper variant (tokens → palette hex). */
2882
+ declare function getRasterSvg(name: string, table?: ColorTableName): string | null;
2883
+ /**
2884
+ * Switch a generated style document to the CHRTraster symbol set: every
2885
+ * symbol layer belonging to a paper-differing ATON class gets its
2886
+ * icon-image recompiled from the PAPER lookup table. All other layers —
2887
+ * and the LIGHTS flares — are untouched. Returns the same style object
2888
+ * (mutated), for chaining.
2889
+ */
2890
+ declare function applyRasterSymbolSet(style: MapboxStyle): MapboxStyle;
2891
+
2892
+ /** One attribute-keyed lookup row: every attr must match for the row to fire. */
2893
+ interface AtonLookupRow {
2894
+ /** [S-57 attribute acronym, required value — comma list for L-types] */
2895
+ attrs: ReadonlyArray<readonly [string, string]>;
2896
+ symbol: string;
2897
+ }
2898
+ interface AtonClassLookup {
2899
+ table: "Simplified" | "Paper";
2900
+ /** Rows ordered by S-52 best-match precedence (most attrs first, stable). */
2901
+ rows: ReadonlyArray<AtonLookupRow>;
2902
+ /** The attribute-less reference row's symbol, if any. */
2903
+ defaultSymbol: string | null;
2904
+ /** Reference rows not compiled into the expression, with the reason. */
2905
+ dropped: ReadonlyArray<{
2906
+ attrs: ReadonlyArray<string>;
2907
+ symbol: string | null;
2908
+ reason: string;
2909
+ }>;
2910
+ }
2911
+ declare const S52_ATON_LOOKUPS: Record<string, AtonClassLookup>;
2912
+ /**
2913
+ * CHRTraster: the PresLib PAPER-chart lookup table for every ATON class —
2914
+ * the traditional symbology NOAA raster charts (RNC scans of the paper
2915
+ * charts) use. 565 attribute-keyed rows. Serve via symbols=raster.
2916
+ */
2917
+ declare const S52_ATON_LOOKUPS_PAPER: Record<string, AtonClassLookup>;
2918
+
2919
+ /**
2920
+ * The symbol-artwork families, as one shared vocabulary.
2921
+ *
2922
+ * `applyS101SymbolSet` / `applyChrtSymbolSet` / `applyRasterSymbolSet`
2923
+ * switch a whole generated style document. Consumers that build layers
2924
+ * class-by-class (the /map viewer's renderer) need the same decision at
2925
+ * the level of a single class — that's `iconExpressionForFamily`, so the
2926
+ * viewer and the Style API can never disagree about what a family means.
2927
+ */
2928
+ type SymbolFamily = "s52" | "s101" | "chrt" | "raster";
2929
+ /** Every family, in presentation order. Mirrors the Style API's
2930
+ * `?symbols=` values (SYMBOL_SET_PARAMS in the tile worker). */
2931
+ declare const SYMBOL_FAMILIES: readonly SymbolFamily[];
2932
+ interface SymbolFamilyMeta {
2933
+ /** Short UI label. */
2934
+ label: string;
2935
+ /** One-line description of what the family is. */
2936
+ description: string;
2937
+ }
2938
+ declare const SYMBOL_FAMILY_META: Record<SymbolFamily, SymbolFamilyMeta>;
2939
+ /** Narrow an arbitrary string to a family, or null. Case-insensitive. */
2940
+ declare function parseSymbolFamily(raw: string | null | undefined): SymbolFamily | null;
2941
+ /**
2942
+ * The icon-image expression for one S-57 class under a given family —
2943
+ * the per-class equivalent of the `apply*SymbolSet` style rewrites.
2944
+ * Classes a family doesn't restyle fall back to the S-52 expression,
2945
+ * exactly as they do on a served style.
2946
+ */
2947
+ declare function iconExpressionForFamily(objectClass: string, family: SymbolFamily): string | unknown[];
2948
+
2949
+ /**
2950
+ * Spec-exact ATON + LIGHTS icon selection, compiled from the S-52 PresLib
2951
+ * lookup tables in `s52-aton-lookups-generated.ts` (P7b wave A1).
2952
+ *
2953
+ * The generated rows are ordered by S-52 best-match precedence (most
2954
+ * attributes first); compiling them into a first-match `case` expression
2955
+ * reproduces the reference matcher. Attribute matchers respect S-57 types:
2956
+ *
2957
+ * - E (enumeration) attributes arrive as numbers in MVT (or occasionally
2958
+ * strings); matched by numeric OR stringified equality.
2959
+ * - L (list) attributes arrive as `["3","4","3"]` (GeoJSON path) or the
2960
+ * JSON-stringified `'["3","4","3"]'` (post-tippecanoe) — see the shape
2961
+ * notes on `listAttrCase` in s52-conditional.ts. The PresLib lookup
2962
+ * requires the FULL list to match (order-sensitive), so we compare the
2963
+ * `to-string` of the attribute against every canonical encoding of the
2964
+ * required list.
2965
+ *
2966
+ * LIGHTS has no lookup rows (the table delegates to CS(LIGHTS05)); the
2967
+ * flare selection here transcribes the PresLib LIGHTS05/06 procedure as
2968
+ * implemented by OpenCPN s52cnsy.cpp `_selSYcol()` + the CATLIT pre-checks:
2969
+ * red→LIGHTS11, green→LIGHTS12, white/yellow/amber→LIGHTS13,
2970
+ * white+red→LIGHTS11, white+green→LIGHTS12, floodlight/fog-detector→LIGHTS82,
2971
+ * strip light→LIGHTS81, everything else→LITDEF11. The vendored flare SVGs
2972
+ * carry the spec colour and the 135° flare rotation baked in, so selection
2973
+ * is purely icon-name choice. Sector geometry (SECTR1/SECTR2 arcs) stays
2974
+ * app-side (P6); this module only picks the symbol at the light.
2975
+ */
2976
+
2977
+ /** Classes whose icon selection is fully lookup-table-driven. TOPMAR and
2978
+ * DAYMAR are deliberately absent: their reference tables delegate to the
2979
+ * TOPMAR01 CSP or reference raster-only combo symbols, so the hand-written
2980
+ * TOPSHP mapping in symbols.ts remains their (documented) approximation. */
2981
+ declare const ATON_EXPRESSION_CLASSES: ReadonlyArray<string>;
2982
+ /**
2983
+ * Build the data-driven icon-image expression for an ATON class from its
2984
+ * PresLib lookup rows. Returns the bare default symbol name when the class
2985
+ * has no attribute-keyed rows (single-symbol classes like BOYSAW).
2986
+ */
2987
+ declare function atonIconExpression(objectClass: string): string | unknown[];
2988
+ /**
2989
+ * LIGHTS flare selection per the LIGHTS05/06 conditional symbology
2990
+ * procedure (see module header for provenance). COLOUR codes: 1 white,
2991
+ * 3 red, 4 green, 6 yellow, 9 amber.
2992
+ */
2993
+ declare function lightsIconExpression(): unknown[];
2994
+
2995
+ /**
2996
+ * S-52 area-pattern (AP) sprite namespace.
2997
+ *
2998
+ * The S-52 Presentation Library fills many areas with an `AP(NAME)`
2999
+ * instruction: a tile of artwork repeated across the polygon. This module owns
3000
+ * the image-id namespace those tiles register under. It is deliberately tiny —
3001
+ * the prefix is the whole contract.
3002
+ *
3003
+ * ── Why a prefix ───────────────────────────────────────────────────────────
3004
+ *
3005
+ * The PresLib keys each catalogue separately, so a name is unique only WITHIN
3006
+ * a catalogue. FOUR names are defined as both a `<symbol>` and a `<pattern>`,
3007
+ * with different artwork:
3008
+ *
3009
+ * AIRARE02 FSHFAC03 MARCUL02 QUESMRK1
3010
+ *
3011
+ * `ALL_S52_SVGS` is one flat map and patterns spread AFTER points, so
3012
+ * registering pattern tiles under their bare names silently shadows those four
3013
+ * point symbols. `AIRARE02` the symbol is a plane inside a ring; `AIRARE02` the
3014
+ * pattern is the bare plane — so `icon-image: "AIRARE02"` would quietly draw a
3015
+ * ringless plane and look plausible while being wrong. Prefixing keeps the two
3016
+ * catalogues disjoint by construction. `s52-pattern.test.ts` locks it.
3017
+ *
3018
+ * `AP:` is named for the PresLib instruction itself, matching the existing
3019
+ * `raster:` (`RASTER_ICON_PREFIX`) convention for namespacing image ids.
3020
+ *
3021
+ * Chosen to be inert for the rest of the pipeline: `style-resolver`'s colour
3022
+ * regex (`/^[A-Z]{4,5}[0-9]?$/`) cannot match a prefixed id, so a pattern
3023
+ * sprite id is never mistaken for a colour token and hex-converted.
3024
+ *
3025
+ * ── Why this lives here and not in the loader ──────────────────────────────
3026
+ *
3027
+ * `s52-lookup` and `s52-conditional` emit `fill-pattern` ids but must not pull
3028
+ * in the inlined SVG catalogue — style generation runs in Workers/Node, where
3029
+ * `s52-svgs-generated` would be dead bundle weight. So the id-minting helper
3030
+ * lives in a catalogue-free module and the loader imports IT, not the reverse.
2433
3031
  */
2434
- declare function applyChrtSymbolSet(style: MapboxStyle): MapboxStyle;
3032
+
3033
+ /**
3034
+ * Prefix distinguishing area-pattern (AP) sprites in the flat image namespace.
3035
+ */
3036
+ declare const S52_PATTERN_SPRITE_PREFIX = "AP:";
3037
+ /** Registered image id for a PresLib pattern name, e.g. "VEGATN03" -> "AP:VEGATN03". */
3038
+ declare function patternSpriteId(patternName: string): string;
2435
3039
 
2436
3040
  /**
2437
3041
  * S-52 SVG Loader — pure (DOM-free) string operations over the inlined S-52
@@ -2446,6 +3050,13 @@ declare function applyChrtSymbolSet(style: MapboxStyle): MapboxStyle;
2446
3050
  /**
2447
3051
  * Get raw SVG string for an S-52 symbol by name (e.g., "BOYCAN01").
2448
3052
  * Returns null if the symbol is not found.
3053
+ *
3054
+ * Accepts either a registered image id ("AP:VEGATN03") or a bare PresLib name
3055
+ * ("VEGATN03") — bare pattern names stay resolvable so the published accessor
3056
+ * keeps working. Bare lookups are POINT-FIRST: for the four dual-catalogue
3057
+ * names (AIRARE02, FSHFAC03, MARCUL02, QUESMRK1) this returns the point
3058
+ * symbol, which is what a caller asking for a symbol by name means. Ask for
3059
+ * `AP:AIRARE02` to get the pattern tile.
2449
3060
  */
2450
3061
  declare function getS52SvgString(symbolName: string): string | null;
2451
3062
  /**
@@ -2462,9 +3073,15 @@ declare function getThemedS52Svg(symbolName: string, colorTable?: ColorTableName
2462
3073
  declare function applyColorTheme(svgString: string, colorTable?: ColorTableName): string;
2463
3074
  /** Get a list of all available S-52 point symbol names. */
2464
3075
  declare function getAvailablePointSymbols(): string[];
2465
- /** Get a list of all available S-52 pattern names. */
3076
+ /**
3077
+ * Get a list of all available S-52 pattern names, as bare PresLib names
3078
+ * (e.g. "VEGATN03"). Use `patternSpriteId()` for the registered image id.
3079
+ */
2466
3080
  declare function getAvailablePatterns(): string[];
2467
- /** Get a list of all available S-52 line symbol names. */
3081
+ /**
3082
+ * Get a list of all available S-52 linestyle names, as bare PresLib names
3083
+ * (e.g. "ACHARE51"). Use `lineSpriteId()` for the registered image id.
3084
+ */
2468
3085
  declare function getAvailableLineSymbols(): string[];
2469
3086
  /** Stats for debugging */
2470
3087
  declare function getS52SymbolStats(): {
@@ -2690,7 +3307,9 @@ interface GeneratedCustomStyle {
2690
3307
  * palette against the config's enabled set, thread the sounding rendering
2691
3308
  * settings into the generator, then apply the static layer transforms.
2692
3309
  */
2693
- declare function generateCustomStyle(config: CustomStyleConfig, opts?: GenerateCustomStyleOptions): GeneratedCustomStyle;
3310
+ declare function generateCustomStyle(config: CustomStyleConfig, opts?: GenerateCustomStyleOptions & {
3311
+ bandFan?: "collapsed" | "legacy";
3312
+ }): GeneratedCustomStyle;
2694
3313
 
2695
3314
  /**
2696
3315
  * Generic async auto-pagination generator.
@@ -2779,7 +3398,11 @@ declare function parseWebhook(body: unknown): WebhookEvent;
2779
3398
  * machine-readable context for programmatic handling (byte math for budget
2780
3399
  * violations, `resumable: true` for interrupts, etc.).
2781
3400
  */
2782
- type OfflineErrorCode = "budget_exceeded" | "coverage_denied" | "quota_exceeded" | "download_corrupt" | "network_interrupted" | "partial_file_invalid" | "invalid_api_key" | "region_not_available" | "offline_not_enabled" | "missing_device_id" | "missing_manifest_path" | "sha_missing" | "unexpected_response";
3401
+ type OfflineErrorCode = "budget_exceeded" | "coverage_denied" | "quota_exceeded" | "download_corrupt" | "network_interrupted" | "partial_file_invalid" | "invalid_api_key" | "region_not_available"
3402
+ /** The team's plan doesn't include CHRToffline (Pro and above). */
3403
+ | "offline_requires_pro_plan"
3404
+ /** The API key lacks the `offline:download` scope. Distinct from the plan gate. */
3405
+ | "offline_not_enabled" | "missing_device_id" | "missing_manifest_path" | "sha_missing" | "unexpected_response";
2783
3406
  declare class OfflineError extends Error {
2784
3407
  readonly code: OfflineErrorCode;
2785
3408
  readonly status?: number;
@@ -2817,11 +3440,97 @@ declare class Chrt {
2817
3440
  private tileAuth;
2818
3441
  private readonly region;
2819
3442
  private readonly tileBaseUrl;
3443
+ /** The vessel this client answers for; defaults every depth-aware call. */
3444
+ private readonly vessel?;
2820
3445
  constructor(apiKeyOrConfig: string | ChrtConfig);
2821
3446
  /** Query GeoJSON features by bounding box */
2822
3447
  features(options: FeaturesOptions): Promise<FeatureCollection>;
2823
3448
  /** Spatial proximity query around a point */
2824
- query(options: QueryOptions): Promise<FeatureCollection>;
3449
+ query(options: QueryOptions): Promise<QueryResponse>;
3450
+ /**
3451
+ * What's charted ahead — the significant features in a sector from a point along a
3452
+ * bearing. Built for the "position + COG, no route" case: a craft that has a heading
3453
+ * but not a plan.
3454
+ *
3455
+ * @example
3456
+ * ```ts
3457
+ * const fan = await chrt.ahead({
3458
+ * point: [-70.9245, 42.33604],
3459
+ * bearing: gps.cog, // degrees true
3460
+ * arc: 60, // full width, so ±30°
3461
+ * rangeNm: 3,
3462
+ * safetyDepth: 3, // your draft plus a margin
3463
+ * });
3464
+ *
3465
+ * for (const f of fan.features) {
3466
+ * const { distance_m, bearing_deg, depth_clearance_m } = f.properties;
3467
+ * // sort/threshold however your alert policy wants — CHRT ranks nothing
3468
+ * }
3469
+ * ```
3470
+ *
3471
+ * @remarks
3472
+ * **This answers "what is charted ahead", not "is it safe ahead".** There is no
3473
+ * severity score and no `safe` flag, by design: a risk model is a navigation-grade
3474
+ * claim, and CHRT is situational-awareness only. You get facts that sort —
3475
+ * `distance_m`, `bearing_deg`, `least_depth_m`, `depth_clearance_m` — and you own
3476
+ * the policy.
3477
+ *
3478
+ * **An empty `features` array is not an all-clear.** It can mean nothing is charted
3479
+ * there, or that CHRT has no coverage there. Always check `coverage.covered` before
3480
+ * telling a user anything; `coverage.cells` carries the issuing office and currency
3481
+ * of every cell under the sector.
3482
+ *
3483
+ * **There is no `speed` parameter, deliberately.** Turning SOG into a range means
3484
+ * deciding how many minutes of lookahead a vessel needs — a navigational judgment
3485
+ * that belongs to you, not to us. The usual recipe is
3486
+ * `rangeNm = SOG_knots × lookahead_hours`, narrowing `arc` as speed rises. Re-fan on
3487
+ * a meaningful course change rather than on a timer; a sharp turn should force an
3488
+ * immediate re-fan on the new heading.
3489
+ */
3490
+ ahead(options: AheadOptions): Promise<AheadResponse>;
3491
+ /**
3492
+ * What's charted along a passage — the significant features in a corridor either
3493
+ * side of a route. The passage-planning sibling of {@link ahead}.
3494
+ *
3495
+ * @example
3496
+ * ```ts
3497
+ * const passage = await chrt.alongRoute({
3498
+ * route: [[-70.95, 42.33], [-71.0, 42.35], [-71.02, 42.36]],
3499
+ * bufferNm: 0.5,
3500
+ * safetyDepth: 3,
3501
+ * });
3502
+ *
3503
+ * if (!passage.coverage.covered) {
3504
+ * // CHRT holds no chart for this passage — say so; do not report it as clear.
3505
+ * }
3506
+ *
3507
+ * for (const f of passage.features) {
3508
+ * const { along_m, distance_m, depth_clearance_m } = f.properties;
3509
+ * // ordered by along_m: the order you actually meet them
3510
+ * }
3511
+ * ```
3512
+ *
3513
+ * @remarks
3514
+ * **This does not replace {@link ahead}, and `ahead` does not replace this.** A boat
3515
+ * with a route still drifts, and COG rarely equals the route bearing — so a passage
3516
+ * app wants both: this once per route edit, `ahead` continuously against the real
3517
+ * track.
3518
+ *
3519
+ * Same invariants as `ahead`: **what is charted, never whether it is safe.** No
3520
+ * severity, no verdict — facts that sort. And an empty `features` array is **not** a
3521
+ * clear passage: check `coverage.covered` first.
3522
+ *
3523
+ * For "which cells must I download for this passage?" — a different question — use
3524
+ * {@link CoverageEndpoint.forRoute | chrt.coverage.forRoute}.
3525
+ */
3526
+ alongRoute(options: AlongRouteOptions): Promise<AlongRouteResponse>;
3527
+ /**
3528
+ * Iterate every feature along a route, auto-paginating.
3529
+ *
3530
+ * @remarks Yields features only. If you need the coverage block — and for anything
3531
+ * user-facing you do — call {@link alongRoute} directly.
3532
+ */
3533
+ alongRouteIter(options: Omit<AlongRouteOptions, "offset">): AsyncIterable<GeoJsonFeature<AlongRouteFeatureProperties>>;
2825
3534
  /**
2826
3535
  * Iterate all features matching a bounding-box query, auto-paginating.
2827
3536
  *
@@ -2838,7 +3547,7 @@ declare class Chrt {
2838
3547
  *
2839
3548
  * @example
2840
3549
  * ```ts
2841
- * for await (const cell of chrt.cellsIter({ source: "NOAA" })) {
3550
+ * for await (const cell of chrt.cellsIter({ source: "NOAA_ENC" })) {
2842
3551
  * console.log(cell.cell_id, cell.title);
2843
3552
  * }
2844
3553
  * ```
@@ -2860,13 +3569,25 @@ declare class Chrt {
2860
3569
  * transformRequest: chrt.transformRequest(),
2861
3570
  * });
2862
3571
  *
2863
- * @remarks Pass `symbolSet: "s101"` for the official S-101 Portrayal
2864
- * Catalogue symbol artwork (load images via `loadS101SymbolImages` from
2865
- * `@chrtco/sdk/atlas/browser`); default is the S-52 set. Generated
3572
+ * @remarks `symbolSet` picks the artwork family: `"s101"` for the
3573
+ * official S-101 Portrayal Catalogue symbols, `"chrt"` for the CHRT house
3574
+ * set, `"raster"` for CHRTraster (the traditional paper-chart portrayal);
3575
+ * default is the S-52 set. Load the matching images from
3576
+ * `@chrtco/sdk/atlas/browser` (`loadS101SymbolImages`,
3577
+ * `loadChrtSymbolImages`, `loadChrtRasterSymbolImages`). Generated
2866
3578
  * locally, so it never applies a key-bound CHRTstyle custom style — use
2867
3579
  * {@link styleUrl} / {@link fetchStyle} (the served Style API) for that.
2868
3580
  */
2869
3581
  style(opts?: ChrtStyleOptions): MapboxStyle;
3582
+ /**
3583
+ * Fill in `safetyDepth` from the vessel profile when a call didn't state one.
3584
+ *
3585
+ * Only substitutes when the caller omitted it entirely — an explicit `undefined`
3586
+ * behaves the same as omission (both mean "no depth filter"), and any explicit
3587
+ * number wins. This is the whole mechanism behind the profile: one value, defaulted
3588
+ * in one place, never guessed.
3589
+ */
3590
+ private withVesselDepth;
2870
3591
  /**
2871
3592
  * URL of the served S-52 style — the CHRTstyle Style API at
2872
3593
  * `tiles.chrt.co/v1/styles/{region}/style.json`. Unlike {@link style}
@@ -2916,4 +3637,4 @@ declare class Chrt {
2916
3637
  warmTiles(region?: string): Promise<void>;
2917
3638
  }
2918
3639
 
2919
- export { ALL_NAV_AID_LAYERS, ALL_WATER_BODY_LAYERS, AREA_QUERY_LAYERS, type AreaInspectResult, type AtlasStyle, type AttachOptions, BAND_DRAW_ORDER, BAND_PRIORITY_RANGES, type BBox, BRIDGE_QUERY_LAYERS, BUILD_MAXZOOM, BUILD_MINZOOM, type BandCode, type BandPriorityRange, type BridgeInspectResult, type BuildOfflineStyleOptions, type BuildSourcesOptions, CHART_BAND_CODES, CHART_CASCADE_CODES, CHART_SECTIONS, CHRT_ATON_COLORS, CHRT_GLYPH_NAMES, CHRT_ICON_PREFIX, CHRT_SDF_GLYPHS, type CandidateQueryOptions, type CandidatesEventContext, type CellDeletedEvent, type CellDetail, type CellEventData, type CellNewEvent, type CellUpdatedData, type CellUpdatedEvent, type CellsOptions, type CellsResponse, type ChartSection, Chrt, type ChrtConfig, ChrtError, type ChrtStyleOptions, type ChrtStyleTheme, type ClickAnchor, ColorTableName, type CoverageOptions, type CoverageResponse, type CoverageSource, type CustomStyleConfig, DEFAULT_CLICK_TOLERANCE, DEFAULT_CUSTOM_STYLE_CONFIG, DEFAULT_INSPECT_CLASS_RANK, DEFAULT_LABEL_SETTINGS, DEFAULT_NAV_AID_SETTINGS, DEFAULT_REGION, DEFAULT_SOUNDING_SETTINGS, DEFAULT_TILE_BASE_URL, DEFAULT_TILE_REGION, DEFAULT_WATER_BODY_SETTINGS, type DecodedAreaFeature, type DecodedBridge, type DecodedNavAid, type DecodedProperty, type DisplayCategory, type DisplayMode, type FeatureCollection, type FeaturesOptions, type FetchStyleOptions, FileSystemAdapter, type FlashPattern, type FlashPhase, type GenerateCustomStyleOptions, type GeneratedCustomStyle, type GeoJsonFeature, type GeoJsonGeometry, type GeomType, INSPECT_CLASS_RANK, INSPECT_RANK_DEVIATIONS, type InspectCandidate, type InspectCandidateSet, type InspectEnrichment, type InspectEventContext, type InspectNotice, type InspectOptions, type InspectResult, type InspectTargetClass, type InspectableFeature, InteractClient, type InteractEntitlement, InteractError, type InteractErrorCode, type InteractHandle, type InteractMapLike, type InteractMapMouseEvent, LABELED_LAYERS, LABEL_BEHAVIOR, LABEL_LAYER_GROUPS, LABEL_PRIORITY, type LabelBehavior, type LabelConfigOptions, type LabelLayerConfig, type LabelSettings, type LookupEntry, type MapLibreStyle, type MapboxStyle, type MapboxVectorSource, NAUTICAL_SYMBOLS, NAV_AID_LAYER_GROUPS, NAV_AID_QUERY_LAYERS, NON_SOLAS_DISCLAIMER, NON_SOLAS_DISCLAIMER_SHORT, type NauticalCategory, type NauticalSymbol, type NavAidInspectResult, type NavAidSettings, type OfflineBundleResponse, type OfflineDownloadOptions, type OfflineDownloadProgress, type OfflineDownloadResult, OfflineError, type OfflineErrorCode, type OfflineFetchStyleOptions, type OfflineHeartbeatOptions, type OfflineRegionSummary, type OfflineRemoveOptions, OfflineStyleError, type OfflineStylePlatform, type OfflineStyleResponse, type PmtilesVectorSource, type QueryOptions, type ResolvedStyle, type ResolvedTheme, type RouteFeatureQueryOptions, type RouteFeatureQueryResponse, type RouteQueryOptions, type RouteQueryResponse, type S101Feature, S101_CHART_SECTIONS, S101_FEATURES, S101_ICON_PREFIX, S101_NEW_SYMBOLS, S101_SOURCE_COMMIT, S101_SYMBOL_GROUPS, S101_SYMBOL_NAMES, S52_DRAW_ORDER, S52_LOOKUP, SOUNDING_LAYERS, SYMBOL_GROUPS, SYMBOL_MAP, type ScaleBand, type SectorArcFeature, type SectorArcFeatureCollection, type SectorInfo, type SoundingSettings, type SourceMode, type StyleGeneratorOptions, type StyleLayer, type StyleUrlOptions, type SymbolGroup, TileAuth, type TileGroup, type TileGroupsResponse, TileTokenError, type TileTokenErrorCode, type TileTokenResponse, type TransformRequest, type TransformedRequest, type VectorSourceSpec, WATER_BODY_LAYER_GROUPS, type WaterBodySettings, type WebhookEvent, type WebhookEventType, type WebhookPayload, applyChrtSymbolSet, applyColorTheme, applyCustomStyleSettings, applyS101SymbolSet, bandFilterExpression, buildFlashPattern, buildLabelLayer, buildLayersForClass, buildOfflineStyle, buildSectorArcGeoJSON, buildSectorArcsFC, buildSectorsFromFeatures, buildSources, candidateRank, chrtAtonTintExpression, chrtLightTintExpression, collectCandidates, collectClickCandidates, combineWithBandFilter, daymarkColor, decimateFeatures, decodeAreaFeatureProps, decodeBridgeProps, decodeCandidate, decodeNavAidProps, depareColor, depareFillExpression, deriveMarinerName, extractNavAidDesignation, featureIdentity, generateCustomStyle, generateSpriteManifest, generateSpriteSheet, generateStyle, generateStyleJSON, getAvailableLineSymbols, getAvailablePatterns, getAvailablePointSymbols, getCollisionSettings, getDrawPriority, getGridSizeForZoom, getIconExpression, getLabelBehavior, getLabelConfig, getLabelPriority, getLookupEntry, getMaxCellsForZoom, getOrderedClasses, getS52SvgString, getS52SymbolStats, getStyleStats, getSymbolSvgString, getThemedS101Svg, getThemedS52Svg, getTopmarkMeaning, hazardColor, hazardFillExpression, isLabelLayerVisible, isNavAidLayer, isNavAidLayerVisible, isSoundingLayerVisible, isVisibleInMode, isWaterBodyLayerVisible, labelSortKey, lateralColor, lateralFillExpression, lightColor, lightFillExpression, normalizeTheme, paginate, parseListCodes, parseWebhook, preprocessLights, preprocessNavAids, preprocessSoundings, preprocessWaterBodies, resolveCustomTheme, resolveS101Color, resolveStyle, resolveTargetClass, sanitizeCustomStyleConfig, scaleSizeValue, scaminToMinZoom, withinBboxFilter, worldMinusBboxPolygon };
3640
+ export { ALL_NAV_AID_LAYERS, ALL_WATER_BODY_LAYERS, AREA_QUERY_LAYERS, ATON_EXPRESSION_CLASSES, type AheadCoverageCell, type AheadFeatureProperties, type AheadOptions, type AheadResponse, type AlongRouteFeatureProperties, type AlongRouteOptions, type AlongRouteResponse, type AreaInspectResult, type AtlasStyle, type AttachOptions, BAND_DRAW_ORDER, BAND_PRIORITY_RANGES, type BBox, BRIDGE_QUERY_LAYERS, BUILD_MAXZOOM, BUILD_MINZOOM, type BandCode, type BandPriorityRange, type BridgeInspectResult, type BuildOfflineStyleOptions, type BuildSourcesOptions, CHART_BAND_CODES, CHART_CASCADE_CODES, CHART_SECTIONS, CHRTRASTER_REF_CROPS, CHRTRASTER_SVGS, CHRT_ATON_COLORS, CHRT_GLYPH_NAMES, CHRT_GLYPH_SVGS, CHRT_ICON_EXPRESSIONS, CHRT_ICON_PREFIX, CHRT_SDF_GLYPHS, type CandidateQueryOptions, type CandidatesEventContext, type CellDeletedEvent, type CellDetail, type CellEventData, type CellNewEvent, type CellUpdatedData, type CellUpdatedEvent, type CellsOptions, type CellsResponse, type ChartSection, Chrt, type ChrtConfig, ChrtError, type ChrtStyleOptions, type ChrtStyleTheme, type ChrtSymbolSet, type ClickAnchor, ColorTableName, type CoverageOptions, type CoverageResponse, type CoverageRouteCell, type CoverageRouteOptions, type CoverageRouteResponse, type CoverageSource, type CustomStyleConfig, DEFAULT_CLICK_TOLERANCE, DEFAULT_CUSTOM_STYLE_CONFIG, DEFAULT_INSPECT_CLASS_RANK, DEFAULT_LABEL_SETTINGS, DEFAULT_NAV_AID_SETTINGS, DEFAULT_REGION, DEFAULT_SAFETY_CONTOUR_M, DEFAULT_SOUNDING_SETTINGS, DEFAULT_TILE_BASE_URL, DEFAULT_TILE_REGION, DEFAULT_WATER_BODY_SETTINGS, type DecodedAreaFeature, type DecodedBridge, type DecodedNavAid, type DecodedProperty, type DisplayCategory, type DisplayMode, type FeatureCollection, type FeaturesOptions, type FetchStyleOptions, FileSystemAdapter, type FlashPattern, type FlashPhase, type GenerateCustomStyleOptions, type GeneratedCustomStyle, type GeoJsonFeature, type GeoJsonGeometry, type GeomType, INSPECT_CLASS_RANK, INSPECT_RANK_DEVIATIONS, type InspectCandidate, type InspectCandidateSet, type InspectEnrichment, type InspectEventContext, type InspectNotice, type InspectOptions, type InspectResult, type InspectTargetClass, type InspectableFeature, InteractClient, type InteractEntitlement, InteractError, type InteractErrorCode, type InteractHandle, type InteractMapLike, type InteractMapMouseEvent, LABELED_LAYERS, LABEL_BEHAVIOR, LABEL_LAYER_GROUPS, LABEL_PRIORITY, type LabelBehavior, type LabelConfigOptions, type LabelLayerConfig, type LabelSettings, type LookupEntry, type MapLibreStyle, type MapboxStyle, type MapboxVectorSource, NAUTICAL_SYMBOLS, NAV_AID_LAYER_GROUPS, NAV_AID_QUERY_LAYERS, NON_SOLAS_DISCLAIMER, NON_SOLAS_DISCLAIMER_SHORT, type NauticalCategory, type NauticalSymbol, type NavAidInspectResult, type NavAidSettings, type OfflineBundleResponse, type OfflineDownloadOptions, type OfflineDownloadProgress, type OfflineDownloadResult, OfflineError, type OfflineErrorCode, type OfflineFetchStyleOptions, type OfflineHeartbeatOptions, type OfflineRegionSummary, type OfflineRemoveOptions, OfflineStyleError, type OfflineStylePlatform, type OfflineStyleResponse, type PmtilesVectorSource, type QueryFeatureProperties, type QueryOptions, type QueryResponse, RASTER_ATON_CLASSES, RASTER_GLYPH_NAMES, RASTER_ICON_PREFIX, type ResolvedStyle, type ResolvedTheme, type S101Feature, S101_CHART_SECTIONS, S101_FEATURES, S101_ICON_PREFIX, S101_NEW_SYMBOLS, S101_SOURCE_COMMIT, S101_SYMBOL_GROUPS, S101_SYMBOL_NAMES, S52_ATON_LOOKUPS, S52_ATON_LOOKUPS_PAPER, S52_DRAW_ORDER, S52_LOOKUP, S52_PATTERN_SPRITE_PREFIX, SOUNDING_LAYERS, SYMBOL_FAMILIES, SYMBOL_FAMILY_META, SYMBOL_GROUPS, SYMBOL_MAP, type ScaleBand$1 as ScaleBand, type SectorArcFeature, type SectorArcFeatureCollection, type SectorInfo, type SoundingSettings, type SourceMode, type StyleGeneratorOptions, type StyleLayer, type StyleUrlOptions, type SymbolFamily, type SymbolFamilyMeta, type SymbolGroup, TileAuth, type TileGroup, type TileGroupsResponse, TileTokenError, type TileTokenErrorCode, type TileTokenResponse, type TransformRequest, type TransformedRequest, type VectorSourceSpec, type VesselProfile, WATER_BODY_LAYER_GROUPS, type WaterBodySettings, type WebhookEvent, type WebhookEventType, type WebhookPayload, applyChrtSymbolSet, applyColorTheme, applyCustomStyleSettings, applyRasterSymbolSet, applyS101SymbolSet, atonIconExpression, bandFilterExpression, buildFlashPattern, buildLabelLayer, buildLayersForClass, buildOfflineStyle, buildSectorArcGeoJSON, buildSectorArcsFC, buildSectorsFromFeatures, buildSources, candidateRank, chrtAtonTintExpression, chrtLightTintExpression, collectCandidates, collectClickCandidates, combineWithBandFilter, daymarkColor, decimateFeatures, decodeAreaFeatureProps, decodeBridgeProps, decodeCandidate, decodeNavAidProps, depareColor, depareFillExpression, deriveMarinerName, extractNavAidDesignation, featureIdentity, generateCustomStyle, generateSpriteManifest, generateSpriteSheet, generateStyle, generateStyleJSON, getAvailableLineSymbols, getAvailablePatterns, getAvailablePointSymbols, getCollisionSettings, getDrawPriority, getGridSizeForZoom, getIconExpression, getLabelBehavior, getLabelConfig, getLabelPriority, getLookupEntry, getMaxCellsForZoom, getOrderedClasses, getRasterSvg, getS52SvgString, getS52SymbolStats, getStyleStats, getSymbolSvgString, getThemedS101Svg, getThemedS52Svg, getTopmarkMeaning, hazardColor, hazardFillExpression, iconExpressionForFamily, isLabelLayerVisible, isNavAidLayer, isNavAidLayerVisible, isSoundingLayerVisible, isVisibleInMode, isWaterBodyLayerVisible, labelSortKey, lateralColor, lateralFillExpression, lightColor, lightFillExpression, lightsIconExpression, normalizeTheme, paginate, parseListCodes, parseSymbolFamily, parseWebhook, patternSpriteId, preprocessLights, preprocessNavAids, preprocessSoundings, preprocessWaterBodies, rasterAtonIconExpression, resolveCustomTheme, resolveRasterImage, resolveS101Color, resolveStyle, resolveTargetClass, sanitizeCustomStyleConfig, scaleSizeValue, scaminToMinZoom, withinBboxFilter, worldMinusBboxPolygon };