@chrtco/sdk 0.10.12 → 0.10.14

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.cjs CHANGED
@@ -5488,16 +5488,23 @@ function patternSpriteId(patternName) {
5488
5488
  // src/atlas/s52-conditional.ts
5489
5489
  var DEFAULT_SAFETY_CONTOUR_M = 10;
5490
5490
  var DEPARE_DEEP_CUT_M = 18.2;
5491
- function depareColor(props, ct = "DAY") {
5491
+ var DEPARE_SHALLOW_CUT_M = 2;
5492
+ function effectiveSafetyContour(safetyContourM) {
5493
+ if (!Number.isFinite(safetyContourM)) return DEFAULT_SAFETY_CONTOUR_M;
5494
+ return Math.min(Math.max(safetyContourM, DEPARE_SHALLOW_CUT_M), DEPARE_DEEP_CUT_M);
5495
+ }
5496
+ function depareColor(props, ct = "DAY", safetyContourM = DEFAULT_SAFETY_CONTOUR_M) {
5492
5497
  const drval1 = Number(props.DRVAL1 ?? props.drval1 ?? -1);
5493
5498
  const drval2 = Number(props.DRVAL2 ?? props.drval2 ?? drval1);
5499
+ const safety = effectiveSafetyContour(safetyContourM);
5494
5500
  if (drval1 < 0 && drval2 <= 0) return resolveColor("DEPIT", ct);
5495
- if (drval1 < 2) return resolveColor("DEPVS", ct);
5496
- if (drval1 < 5) return resolveColor("DEPMS", ct);
5501
+ if (drval1 < DEPARE_SHALLOW_CUT_M) return resolveColor("DEPVS", ct);
5502
+ if (drval1 < safety) return resolveColor("DEPMS", ct);
5497
5503
  if (drval1 < DEPARE_DEEP_CUT_M) return resolveColor("DEPMD", ct);
5498
5504
  return resolveColor("DEPDW", ct);
5499
5505
  }
5500
- function depareFillExpression(ct = "DAY") {
5506
+ function depareFillExpression(ct = "DAY", safetyContourM = DEFAULT_SAFETY_CONTOUR_M) {
5507
+ const safety = effectiveSafetyContour(safetyContourM);
5501
5508
  return [
5502
5509
  "case",
5503
5510
  [
@@ -5506,9 +5513,9 @@ function depareFillExpression(ct = "DAY") {
5506
5513
  ["<=", ["coalesce", ["get", "DRVAL2"], ["get", "drval2"], 0], 0]
5507
5514
  ],
5508
5515
  resolveColor("DEPIT", ct),
5509
- ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], 2],
5516
+ ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], DEPARE_SHALLOW_CUT_M],
5510
5517
  resolveColor("DEPVS", ct),
5511
- ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], 5],
5518
+ ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], safety],
5512
5519
  resolveColor("DEPMS", ct),
5513
5520
  ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], DEPARE_DEEP_CUT_M],
5514
5521
  resolveColor("DEPMD", ct),
@@ -22287,15 +22294,32 @@ function isVisibleInMode(displayCategory, mode) {
22287
22294
  if (mode === "STANDARD") return displayCategory === "BASE" || displayCategory === "STANDARD";
22288
22295
  return displayCategory === "BASE";
22289
22296
  }
22297
+ var SCAMIN_ZOOM_BUCKETS = [
22298
+ [12e3, 14],
22299
+ [22e3, 12],
22300
+ [45e3, 11],
22301
+ [9e4, 10],
22302
+ [35e4, 8],
22303
+ [15e5, 6]
22304
+ ];
22290
22305
  function scaminToMinZoom(scamin) {
22291
22306
  if (scamin === void 0 || scamin === null || scamin === 0) return void 0;
22292
- if (scamin >= 15e5) return 6;
22293
- if (scamin >= 35e4) return 8;
22294
- if (scamin >= 9e4) return 10;
22295
- if (scamin >= 45e3) return 11;
22296
- if (scamin >= 22e3) return 12;
22297
- if (scamin >= 12e3) return 14;
22298
- return void 0;
22307
+ let zoom;
22308
+ for (const [denom, z] of SCAMIN_ZOOM_BUCKETS) {
22309
+ if (scamin >= denom) zoom = z;
22310
+ }
22311
+ return zoom;
22312
+ }
22313
+ function scaminZoomGateFilter() {
22314
+ const step = [
22315
+ "step",
22316
+ ["to-number", ["coalesce", ["get", "SCAMIN"], ["get", "scamin"], 0]],
22317
+ 0
22318
+ ];
22319
+ for (const [denom, z] of SCAMIN_ZOOM_BUCKETS) {
22320
+ step.push(denom, z);
22321
+ }
22322
+ return [">=", ["zoom"], step];
22299
22323
  }
22300
22324
  var SAFETY_CRITICAL_GROUPS = /* @__PURE__ */ new Set([24020, 27010, 27070, 27080]);
22301
22325
  var OPAQUE_FILL_LAYERS = /* @__PURE__ */ new Set(["DEPARE", "LNDARE", "UNSARE", "LAKARE", "DRGARE", "SEAARE"]);
@@ -22329,7 +22353,7 @@ var ZOOM_SCALED_LINES = {
22329
22353
  BRIDGE: [8, 1, 14, 2],
22330
22354
  DAMCON: [8, 1, 14, 2]
22331
22355
  };
22332
- function resolvePaint(layerName, paint, geomType, ct = "DAY") {
22356
+ function resolvePaint(layerName, paint, geomType, ct = "DAY", opts = {}) {
22333
22357
  const resolved = {};
22334
22358
  for (const [key, value] of Object.entries(paint)) {
22335
22359
  if (typeof value === "string" && /^[A-Z]{4,5}[0-9]?$/.test(value)) {
@@ -22339,7 +22363,7 @@ function resolvePaint(layerName, paint, geomType, ct = "DAY") {
22339
22363
  }
22340
22364
  }
22341
22365
  if (layerName === "DEPARE" && geomType === "fill") {
22342
- resolved["fill-color"] = depareFillExpression(ct);
22366
+ resolved["fill-color"] = depareFillExpression(ct, opts.safetyContour);
22343
22367
  } else if (layerName === "LIGHTS" && geomType === "circle") {
22344
22368
  resolved["circle-color"] = lightFillExpression(ct);
22345
22369
  } else if ((layerName === "BOYLAT" || layerName === "BCNLAT") && geomType === "circle") {
@@ -22351,7 +22375,7 @@ function resolvePaint(layerName, paint, geomType, ct = "DAY") {
22351
22375
  }
22352
22376
  return resolved;
22353
22377
  }
22354
- function resolveStyle(layerName, geomKind, ct = "DAY") {
22378
+ function resolveStyle(layerName, geomKind, ct = "DAY", opts = {}) {
22355
22379
  const entry = S52_LOOKUP[layerName];
22356
22380
  if (!entry) {
22357
22381
  const type = geomKind ?? "circle";
@@ -22387,7 +22411,7 @@ function resolveStyle(layerName, geomKind, ct = "DAY") {
22387
22411
  const paint2 = effectiveType === "fill" ? { "fill-color": hexColor, "fill-opacity": 0.3 } : effectiveType === "line" ? { "line-color": hexColor, "line-width": 1 } : { "circle-color": hexColor, "circle-radius": 3, "circle-stroke-width": 0 };
22388
22412
  return { type: effectiveType, paint: paint2 };
22389
22413
  }
22390
- const paint = resolvePaint(layerName, { ...entry.style.paint }, effectiveType, ct);
22414
+ const paint = resolvePaint(layerName, { ...entry.style.paint }, effectiveType, ct, opts);
22391
22415
  if (effectiveType === "fill" && OPAQUE_FILL_LAYERS.has(layerName)) {
22392
22416
  paint["fill-opacity"] = 1;
22393
22417
  }
@@ -23336,21 +23360,19 @@ function getLabelConfig(layerName, opts = {}) {
23336
23360
  layout: orientationLabelLayout({
23337
23361
  "text-letter-spacing": 0.15,
23338
23362
  "text-max-width": 9,
23339
- // SEAARE is always-paint, unlike the other orientation classes.
23340
- // MapLibre places symbols topmost-layer-first, so with
23341
- // allow-overlap:false the dense sounding field (drawn above)
23342
- // collision-starves big-water names — observed on staging at z8:
23343
- // only 2 SEAARE labels survived viewport-wide, with Cape Cod Bay
23344
- // and Gulf of Maine dropped entirely. Always-paint is safe for
23345
- // this class because (a) the dominance composition in
23346
- // fanOrientationLabelByCellBand suppresses cross-band duplicate
23347
- // copies, and (b) tier sizing zeroes relief/anchorage names at
23348
- // the zooms where density is a problem. Rivers/lakes/land keep
23349
- // collision-dedup — their within-band multi-polygon stacking was
23350
- // the original complaint, and they lack big-water headline value.
23351
- "text-allow-overlap": true,
23352
- "text-ignore-placement": true,
23353
- "text-optional": false,
23363
+ // SEAARE was always-paint (allow-overlap + ignore-placement)
23364
+ // because collision starved big-water names at z8. That trade
23365
+ // inverted in the field on 2026-08-21: the always-paint copies —
23366
+ // per band, per adjacent cell, and one per TILE-CLIPPED piece of
23367
+ // a big polygon — stack into unreadable black smears (dozens of
23368
+ // "Chesapeake Bay" at z9, owner screenshot). With collision ON
23369
+ // the duplicates collide with EACH OTHER, so MapLibre keeps one
23370
+ // and drops the rest: a possibly-starved name beats an illegible
23371
+ // smear categorically. This is the interim state until the
23372
+ // PLACENAME layer (one anchor per named place, PR #1308) reaches
23373
+ // the tiles and replaces this fan outright — do not restore
23374
+ // always-paint on a starvation report; flip `labels: "placename"`
23375
+ // instead once the build carries the layer.
23354
23376
  "text-padding": 2
23355
23377
  })
23356
23378
  };
@@ -26957,6 +26979,13 @@ function buildSources(opts = {}) {
26957
26979
  }
26958
26980
 
26959
26981
  // src/atlas/layer-builder.ts
26982
+ var SCAMIN_DECLUTTER_CLASSES = /* @__PURE__ */ new Set([
26983
+ "OFSPLF",
26984
+ "PIPSOL",
26985
+ "PIPARE",
26986
+ "CBLSUB",
26987
+ "CBLARE"
26988
+ ]);
26960
26989
  var GEOM_FILTERS = {
26961
26990
  fill: ["any", ["==", ["geometry-type"], "Polygon"], ["==", ["geometry-type"], "MultiPolygon"]],
26962
26991
  line: ["any", ["==", ["geometry-type"], "LineString"], ["==", ["geometry-type"], "MultiLineString"]],
@@ -27115,7 +27144,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
27115
27144
  const layers = [];
27116
27145
  const geomTypes = getGeomTypesForClass(objectClass, entry?.style.type);
27117
27146
  for (const geomType of geomTypes) {
27118
- const resolved = resolveStyle(objectClass, geomType, ct);
27147
+ const resolved = resolveStyle(objectClass, geomType, ct, { safetyContour: opts.safetyContour });
27119
27148
  const layerId = `s52-${objectClass}-${resolved.type}`;
27120
27149
  const layer = {
27121
27150
  id: layerId,
@@ -27134,6 +27163,9 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
27134
27163
  } else if (GEOM_FILTERS[resolved.type]) {
27135
27164
  layer.filter = GEOM_FILTERS[resolved.type];
27136
27165
  }
27166
+ if (SCAMIN_DECLUTTER_CLASSES.has(objectClass)) {
27167
+ layer.filter = layer.filter ? ["all", layer.filter, scaminZoomGateFilter()] : ["all", scaminZoomGateFilter()];
27168
+ }
27137
27169
  if (resolved.minzoom) {
27138
27170
  layer.minzoom = resolved.minzoom;
27139
27171
  }
@@ -28192,6 +28224,11 @@ function getOrderedClasses() {
28192
28224
  }
28193
28225
 
28194
28226
  // src/atlas/land-layers.ts
28227
+ var FADE_PX = 80;
28228
+ var FADE_BANDS = 16;
28229
+ function fadeAlpha(r) {
28230
+ return Math.max(0, Math.min(1, (FADE_PX / 2 - Math.abs(r)) / FADE_PX));
28231
+ }
28195
28232
  var LAND_THEMES = {
28196
28233
  DAY: {
28197
28234
  base: "#e8d9b0",
@@ -28203,7 +28240,8 @@ var LAND_THEMES = {
28203
28240
  label: "#5b4f3a",
28204
28241
  halo: "#f2ead6",
28205
28242
  shadow: "#6b5a3e",
28206
- highlight: "#fffaf0"
28243
+ highlight: "#fffaf0",
28244
+ fade: "#e8f2f8"
28207
28245
  },
28208
28246
  DUSK: {
28209
28247
  base: "#4a4433",
@@ -28215,7 +28253,8 @@ var LAND_THEMES = {
28215
28253
  label: "#c3b898",
28216
28254
  halo: "#2b2820",
28217
28255
  shadow: "#2e2a20",
28218
- highlight: "#8a8068"
28256
+ highlight: "#8a8068",
28257
+ fade: "#16202a"
28219
28258
  },
28220
28259
  NIGHT: {
28221
28260
  base: "#2a2620",
@@ -28227,7 +28266,8 @@ var LAND_THEMES = {
28227
28266
  label: "#7d745e",
28228
28267
  halo: "#15130f",
28229
28268
  shadow: "#1a1712",
28230
- highlight: "#4a4436"
28269
+ highlight: "#4a4436",
28270
+ fade: "#07090b"
28231
28271
  }
28232
28272
  };
28233
28273
  var FITS = ["<=", ["coalesce", ["get", "min_z"], 0], ["zoom"]];
@@ -28308,6 +28348,18 @@ function landLayers(opts = {}) {
28308
28348
  "line-opacity": 0.7
28309
28349
  }
28310
28350
  }));
28351
+ const d = FADE_PX / 2 / FADE_BANDS;
28352
+ for (let k = 0; k < FADE_BANDS; k++) {
28353
+ const width = k === 0 ? { "line-width": 2 * d } : { "line-width": d, "line-gap-width": 2 * k * d };
28354
+ layers.push(L({
28355
+ id: `chrtland-fade-${k}`,
28356
+ type: "line",
28357
+ source: src,
28358
+ "source-layer": "land_edge",
28359
+ layout: { "line-join": "bevel" },
28360
+ paint: { "line-color": T.fade, "line-blur": 0, "line-opacity": fadeAlpha(k * d + d / 2), ...width }
28361
+ }));
28362
+ }
28311
28363
  layers.push(L({
28312
28364
  id: "chrtland-road",
28313
28365
  type: "line",
@@ -28429,6 +28481,18 @@ function isExemptOpaqueFill(layer) {
28429
28481
  }
28430
28482
  return false;
28431
28483
  }
28484
+ var FILL_FLOOR_FREE_BANDS = /* @__PURE__ */ new Set([
28485
+ "1",
28486
+ "2",
28487
+ "3"
28488
+ ]);
28489
+ function bandVariantMinzoom(layer, B) {
28490
+ const classMinzoom = layer.minzoom ?? 0;
28491
+ if (isExemptOpaqueFill(layer) && FILL_FLOOR_FREE_BANDS.has(B)) {
28492
+ return classMinzoom;
28493
+ }
28494
+ return Math.max(classMinzoom, BAND_PRIORITY_RANGES[B].minzoom);
28495
+ }
28432
28496
  var BAND_DRAW_ORDER = ["1", "2", "3", "4", "5", "6"];
28433
28497
  var CHART_BAND_CODES = {
28434
28498
  Overview: ["1"],
@@ -28547,9 +28611,7 @@ function collapsedDominanceGate(B, minzoom) {
28547
28611
  return ["any", ...clauses];
28548
28612
  }
28549
28613
  function makeCollapsedBandVariant(layer, B, isExempt) {
28550
- const range = BAND_PRIORITY_RANGES[B];
28551
- const classMinzoom = layer.minzoom ?? 0;
28552
- const minzoom = isExemptOpaqueFill(layer) ? classMinzoom : Math.max(classMinzoom, range.minzoom);
28614
+ const minzoom = bandVariantMinzoom(layer, B);
28553
28615
  if (isExempt) {
28554
28616
  const exempt = {
28555
28617
  ...layer,
@@ -28688,8 +28750,7 @@ function isDominanceExempt(layer) {
28688
28750
  var TOP_STACK_LIGHT_CLASSES = /* @__PURE__ */ new Set(["LIGHTS"]);
28689
28751
  function makeBandVariants(layer, B) {
28690
28752
  const range = BAND_PRIORITY_RANGES[B];
28691
- const classMinzoom = layer.minzoom ?? 0;
28692
- const minzoom = isExemptOpaqueFill(layer) ? classMinzoom : Math.max(classMinzoom, range.minzoom);
28753
+ const minzoom = bandVariantMinzoom(layer, B);
28693
28754
  const variants = [];
28694
28755
  if (isDominanceExempt(layer)) {
28695
28756
  const exempt = {
@@ -28997,6 +29058,12 @@ function generateStyle(palette = "DAY", opts = {}) {
28997
29058
  }
28998
29059
  const symbolSet = opts.symbolSet ?? "s52";
28999
29060
  const spriteBase = `${opts.tileBaseUrl ?? "https://tiles.chrt.co"}/v1/sprites`;
29061
+ const isLndareFill = (l) => /^s52-LNDARE-fill-band\d+$/.test(l.id);
29062
+ const emitted = opts.land ? (() => {
29063
+ const fills = layers.filter(isLndareFill);
29064
+ const rest = layers.filter((l) => !isLndareFill(l));
29065
+ return [rest[0], ...fills, ...rest.slice(1)];
29066
+ })() : layers;
29000
29067
  const style = {
29001
29068
  version: 8,
29002
29069
  name: `CHRT ${SYMBOL_SET_LABELS[symbolSet]} ${PALETTE_NAMES[palette]}${opts.chartMode ? ` \u2014 chart:${opts.chartMode.band}` : ""}`,
@@ -29005,7 +29072,7 @@ function generateStyle(palette = "DAY", opts = {}) {
29005
29072
  "chrt:symbol-set": symbolSet,
29006
29073
  "chrt:color-table": palette,
29007
29074
  "chrt:generated-at": (/* @__PURE__ */ new Date()).toISOString(),
29008
- "chrt:layer-count": layers.length,
29075
+ "chrt:layer-count": emitted.length,
29009
29076
  "chrt:land": Boolean(opts.land),
29010
29077
  "chrt:relief": Boolean(opts.relief),
29011
29078
  "chrt:render-mode": opts.chartMode ? `chart:${opts.chartMode.band}` : "continuous",
@@ -29028,7 +29095,7 @@ function generateStyle(palette = "DAY", opts = {}) {
29028
29095
  sprite: `${spriteBase}/${symbolSet}-${palette.toLowerCase()}`
29029
29096
  },
29030
29097
  glyphs: "https://fonts.openmaptiles.org/{fontstack}/{range}.pbf",
29031
- layers
29098
+ layers: emitted
29032
29099
  };
29033
29100
  if (symbolSet === "s101") return applyS101SymbolSet(style);
29034
29101
  if (symbolSet === "chrt") return applyChrtSymbolSet(style, palette);