@chrtco/sdk 0.10.11 → 0.10.13

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/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to `@chrtco/sdk` will be documented in this file.
5
5
  ## [Unreleased]
6
6
 
7
7
  ### Added
8
+ - **`StyleGeneratorOptions.labels`** — `"legacy"` (default) or `"placename"`.
9
+ `"placename"` swaps the 9 orientation classes' 54-layer per-band label fan
10
+ for 2 collision-managed `PLACENAME` symbol layers over the tile pipeline's
11
+ place-name index: one label per named place, per-feature zoom windows
12
+ (`min_z`/`max_z` from place extent, S-57 SCAMIN and class floors), and a
13
+ build-side `rank` driving `symbol-sort-key` so the most important names
14
+ survive density. Requires tiles carrying the `PLACENAME` layer; the default
15
+ stays `"legacy"` until the deconfliction rollout flips it
16
+ (docs/design/chart-label-deconfliction.md §2.3). New exports:
17
+ `buildPlaceNameLayers()`, `PLACENAME_SOURCE_LAYER`,
18
+ `PLACENAME_WATER_CLASSES`, `PLACENAME_LAND_CLASSES`, and the per-class
19
+ tier expression builders (`buildSeaAreaTierExpression()` et al.).
8
20
  - **CHRTland on the Style API** — `StyleUrlOptions.land` / `.relief` thread
9
21
  `?land=` / `?relief=` to `GET /v1/styles/{region}/style.json`. `generateStyle`
10
22
  learned to emit CHRTland and the first-party app learned to ask for it, but
package/dist/index.cjs CHANGED
@@ -70,6 +70,9 @@ __export(index_exports, {
70
70
  NON_SOLAS_DISCLAIMER_SHORT: () => NON_SOLAS_DISCLAIMER_SHORT,
71
71
  OfflineError: () => OfflineError,
72
72
  OfflineStyleError: () => OfflineStyleError,
73
+ PLACENAME_LAND_CLASSES: () => PLACENAME_LAND_CLASSES,
74
+ PLACENAME_SOURCE_LAYER: () => PLACENAME_SOURCE_LAYER,
75
+ PLACENAME_WATER_CLASSES: () => PLACENAME_WATER_CLASSES,
73
76
  RASTER_ATON_CLASSES: () => RASTER_ATON_CLASSES,
74
77
  RASTER_GLYPH_NAMES: () => RASTER_GLYPH_NAMES,
75
78
  RASTER_ICON_PREFIX: () => RASTER_ICON_PREFIX,
@@ -109,10 +112,18 @@ __export(index_exports, {
109
112
  applyS101SymbolSet: () => applyS101SymbolSet,
110
113
  atonIconExpression: () => atonIconExpression,
111
114
  bandFilterExpression: () => bandFilterExpression,
115
+ buildAirportTierExpression: () => buildAirportTierExpression,
116
+ buildBuiltUpAreaTierExpression: () => buildBuiltUpAreaTierExpression,
112
117
  buildFlashPattern: () => buildFlashPattern,
113
118
  buildLabelLayer: () => buildLabelLayer,
119
+ buildLakeTierExpression: () => buildLakeTierExpression,
120
+ buildLandAreaTierExpression: () => buildLandAreaTierExpression,
121
+ buildLandRegionTierExpression: () => buildLandRegionTierExpression,
114
122
  buildLayersForClass: () => buildLayersForClass,
115
123
  buildOfflineStyle: () => buildOfflineStyle,
124
+ buildPlaceNameLayers: () => buildPlaceNameLayers,
125
+ buildRiverTierExpression: () => buildRiverTierExpression,
126
+ buildSeaAreaTierExpression: () => buildSeaAreaTierExpression,
116
127
  buildSectorArcGeoJSON: () => buildSectorArcGeoJSON,
117
128
  buildSectorArcsFC: () => buildSectorArcsFC,
118
129
  buildSectorsFromFeatures: () => buildSectorsFromFeatures,
@@ -5477,16 +5488,23 @@ function patternSpriteId(patternName) {
5477
5488
  // src/atlas/s52-conditional.ts
5478
5489
  var DEFAULT_SAFETY_CONTOUR_M = 10;
5479
5490
  var DEPARE_DEEP_CUT_M = 18.2;
5480
- 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) {
5481
5497
  const drval1 = Number(props.DRVAL1 ?? props.drval1 ?? -1);
5482
5498
  const drval2 = Number(props.DRVAL2 ?? props.drval2 ?? drval1);
5499
+ const safety = effectiveSafetyContour(safetyContourM);
5483
5500
  if (drval1 < 0 && drval2 <= 0) return resolveColor("DEPIT", ct);
5484
- if (drval1 < 2) return resolveColor("DEPVS", ct);
5485
- 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);
5486
5503
  if (drval1 < DEPARE_DEEP_CUT_M) return resolveColor("DEPMD", ct);
5487
5504
  return resolveColor("DEPDW", ct);
5488
5505
  }
5489
- function depareFillExpression(ct = "DAY") {
5506
+ function depareFillExpression(ct = "DAY", safetyContourM = DEFAULT_SAFETY_CONTOUR_M) {
5507
+ const safety = effectiveSafetyContour(safetyContourM);
5490
5508
  return [
5491
5509
  "case",
5492
5510
  [
@@ -5495,9 +5513,9 @@ function depareFillExpression(ct = "DAY") {
5495
5513
  ["<=", ["coalesce", ["get", "DRVAL2"], ["get", "drval2"], 0], 0]
5496
5514
  ],
5497
5515
  resolveColor("DEPIT", ct),
5498
- ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], 2],
5516
+ ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], DEPARE_SHALLOW_CUT_M],
5499
5517
  resolveColor("DEPVS", ct),
5500
- ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], 5],
5518
+ ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], safety],
5501
5519
  resolveColor("DEPMS", ct),
5502
5520
  ["<", ["coalesce", ["get", "DRVAL1"], ["get", "drval1"], -1], DEPARE_DEEP_CUT_M],
5503
5521
  resolveColor("DEPMD", ct),
@@ -22276,15 +22294,32 @@ function isVisibleInMode(displayCategory, mode) {
22276
22294
  if (mode === "STANDARD") return displayCategory === "BASE" || displayCategory === "STANDARD";
22277
22295
  return displayCategory === "BASE";
22278
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
+ ];
22279
22305
  function scaminToMinZoom(scamin) {
22280
22306
  if (scamin === void 0 || scamin === null || scamin === 0) return void 0;
22281
- if (scamin >= 15e5) return 6;
22282
- if (scamin >= 35e4) return 8;
22283
- if (scamin >= 9e4) return 10;
22284
- if (scamin >= 45e3) return 11;
22285
- if (scamin >= 22e3) return 12;
22286
- if (scamin >= 12e3) return 14;
22287
- 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];
22288
22323
  }
22289
22324
  var SAFETY_CRITICAL_GROUPS = /* @__PURE__ */ new Set([24020, 27010, 27070, 27080]);
22290
22325
  var OPAQUE_FILL_LAYERS = /* @__PURE__ */ new Set(["DEPARE", "LNDARE", "UNSARE", "LAKARE", "DRGARE", "SEAARE"]);
@@ -22318,7 +22353,7 @@ var ZOOM_SCALED_LINES = {
22318
22353
  BRIDGE: [8, 1, 14, 2],
22319
22354
  DAMCON: [8, 1, 14, 2]
22320
22355
  };
22321
- function resolvePaint(layerName, paint, geomType, ct = "DAY") {
22356
+ function resolvePaint(layerName, paint, geomType, ct = "DAY", opts = {}) {
22322
22357
  const resolved = {};
22323
22358
  for (const [key, value] of Object.entries(paint)) {
22324
22359
  if (typeof value === "string" && /^[A-Z]{4,5}[0-9]?$/.test(value)) {
@@ -22328,7 +22363,7 @@ function resolvePaint(layerName, paint, geomType, ct = "DAY") {
22328
22363
  }
22329
22364
  }
22330
22365
  if (layerName === "DEPARE" && geomType === "fill") {
22331
- resolved["fill-color"] = depareFillExpression(ct);
22366
+ resolved["fill-color"] = depareFillExpression(ct, opts.safetyContour);
22332
22367
  } else if (layerName === "LIGHTS" && geomType === "circle") {
22333
22368
  resolved["circle-color"] = lightFillExpression(ct);
22334
22369
  } else if ((layerName === "BOYLAT" || layerName === "BCNLAT") && geomType === "circle") {
@@ -22340,7 +22375,7 @@ function resolvePaint(layerName, paint, geomType, ct = "DAY") {
22340
22375
  }
22341
22376
  return resolved;
22342
22377
  }
22343
- function resolveStyle(layerName, geomKind, ct = "DAY") {
22378
+ function resolveStyle(layerName, geomKind, ct = "DAY", opts = {}) {
22344
22379
  const entry = S52_LOOKUP[layerName];
22345
22380
  if (!entry) {
22346
22381
  const type = geomKind ?? "circle";
@@ -22376,7 +22411,7 @@ function resolveStyle(layerName, geomKind, ct = "DAY") {
22376
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 };
22377
22412
  return { type: effectiveType, paint: paint2 };
22378
22413
  }
22379
- const paint = resolvePaint(layerName, { ...entry.style.paint }, effectiveType, ct);
22414
+ const paint = resolvePaint(layerName, { ...entry.style.paint }, effectiveType, ct, opts);
22380
22415
  if (effectiveType === "fill" && OPAQUE_FILL_LAYERS.has(layerName)) {
22381
22416
  paint["fill-opacity"] = 1;
22382
22417
  }
@@ -23325,21 +23360,19 @@ function getLabelConfig(layerName, opts = {}) {
23325
23360
  layout: orientationLabelLayout({
23326
23361
  "text-letter-spacing": 0.15,
23327
23362
  "text-max-width": 9,
23328
- // SEAARE is always-paint, unlike the other orientation classes.
23329
- // MapLibre places symbols topmost-layer-first, so with
23330
- // allow-overlap:false the dense sounding field (drawn above)
23331
- // collision-starves big-water names — observed on staging at z8:
23332
- // only 2 SEAARE labels survived viewport-wide, with Cape Cod Bay
23333
- // and Gulf of Maine dropped entirely. Always-paint is safe for
23334
- // this class because (a) the dominance composition in
23335
- // fanOrientationLabelByCellBand suppresses cross-band duplicate
23336
- // copies, and (b) tier sizing zeroes relief/anchorage names at
23337
- // the zooms where density is a problem. Rivers/lakes/land keep
23338
- // collision-dedup — their within-band multi-polygon stacking was
23339
- // the original complaint, and they lack big-water headline value.
23340
- "text-allow-overlap": true,
23341
- "text-ignore-placement": true,
23342
- "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.
23343
23376
  "text-padding": 2
23344
23377
  })
23345
23378
  };
@@ -24032,6 +24065,102 @@ function computePolygonCentroid(geom) {
24032
24065
  }
24033
24066
  return [sumX / coords.length, sumY / coords.length];
24034
24067
  }
24068
+ var PLACENAME_SOURCE_LAYER = "PLACENAME";
24069
+ var PLACENAME_WATER_CLASSES = [
24070
+ "SEAARE",
24071
+ "LAKARE",
24072
+ "RIVERS",
24073
+ "CANALS",
24074
+ "PRDARE"
24075
+ ];
24076
+ var PLACENAME_LAND_CLASSES = [
24077
+ "LNDRGN",
24078
+ "LNDARE",
24079
+ "BUAARE",
24080
+ "AIRARE"
24081
+ ];
24082
+ var PLACENAME_FITS = ["<=", ["coalesce", ["get", "min_z"], 0], ["zoom"]];
24083
+ var PLACENAME_RETIRES = [">=", ["coalesce", ["get", "max_z"], 14], ["zoom"]];
24084
+ var PLACENAME_TIER = [
24085
+ "to-number",
24086
+ ["coalesce", ["get", "tier"], 9]
24087
+ ];
24088
+ var PLACENAME_TEXT_OPACITY = [
24089
+ "interpolate",
24090
+ ["linear"],
24091
+ ["zoom"],
24092
+ 3,
24093
+ 0.55,
24094
+ 9,
24095
+ 0.5,
24096
+ 12,
24097
+ 0.4,
24098
+ 16,
24099
+ 0.3
24100
+ ];
24101
+ var PLACENAME_WATER_SIZE_STOPS = [
24102
+ [3, [14, 9, 0, 0, 0, 0, 0, 0, 0, 0]],
24103
+ [5, [16, 13, 9, 0, 0, 0, 0, 0, 0, 0]],
24104
+ [7, [14, 14, 12, 9, 0, 0, 0, 0, 0, 0]],
24105
+ [9, [12, 13, 14, 11, 10, 9, 0, 9, 9, 9]],
24106
+ [11, [10, 12, 13, 12, 10, 9, 9, 11, 11, 9]],
24107
+ [13, [9, 11, 12, 11, 10, 10, 10, 11, 11, 10]],
24108
+ [16, [9, 10, 11, 10, 9, 9, 9, 10, 10, 9]]
24109
+ ];
24110
+ var PLACENAME_LAND_SIZE_STOPS = [
24111
+ [3, [0, 0, 11, 0, 0, 0, 0, 0, 0, 0]],
24112
+ [5, [0, 0, 13, 0, 0, 0, 0, 0, 0, 0]],
24113
+ [7, [0, 0, 13, 9, 9, 0, 0, 0, 0, 0]],
24114
+ [9, [0, 0, 12, 12, 11, 0, 0, 0, 0, 0]],
24115
+ [11, [0, 0, 11, 12, 11, 9, 9, 0, 0, 9]],
24116
+ [13, [0, 0, 10, 11, 10, 11, 10, 10, 10, 10]],
24117
+ [16, [0, 0, 10, 10, 9, 10, 9, 9, 9, 9]]
24118
+ ];
24119
+ function buildPlaceNameLayers(sourceId) {
24120
+ const sortKey = ["to-number", ["coalesce", ["get", "rank"], 9999999]];
24121
+ const make = (idSuffix, classes, font, haloColor, letterSpacing, stops) => ({
24122
+ id: `chrt-placename-${idSuffix}`,
24123
+ type: "symbol",
24124
+ source: sourceId,
24125
+ "source-layer": PLACENAME_SOURCE_LAYER,
24126
+ minzoom: 3,
24127
+ filter: [
24128
+ "all",
24129
+ ["match", ["get", "cls"], [...classes], true, false],
24130
+ PLACENAME_FITS,
24131
+ PLACENAME_RETIRES
24132
+ ],
24133
+ layout: {
24134
+ "text-field": ["get", "name"],
24135
+ "text-font": font,
24136
+ "text-size": tieredSizeCurve(PLACENAME_TIER, stops),
24137
+ "text-letter-spacing": letterSpacing,
24138
+ "text-max-width": 9,
24139
+ "text-padding": 4,
24140
+ "text-allow-overlap": false,
24141
+ "text-ignore-placement": false,
24142
+ "text-optional": true,
24143
+ "text-variable-anchor": ["center", "top", "bottom", "left", "right"],
24144
+ "text-radial-offset": 0.3,
24145
+ "text-justify": "auto",
24146
+ "symbol-sort-key": sortKey
24147
+ },
24148
+ paint: {
24149
+ "text-color": DARK_BG,
24150
+ "text-halo-color": haloColor,
24151
+ "text-halo-width": ORIENTATION_HALO_WIDTH,
24152
+ "text-opacity": PLACENAME_TEXT_OPACITY
24153
+ },
24154
+ metadata: {
24155
+ "chrt:placename": true,
24156
+ "chrt:labelPriority": ORIENTATION_LABEL_PRIORITY
24157
+ }
24158
+ });
24159
+ return [
24160
+ make("water", PLACENAME_WATER_CLASSES, ["Open Sans Italic"], COLOR.WATER_DEEP, 0.05, PLACENAME_WATER_SIZE_STOPS),
24161
+ make("land", PLACENAME_LAND_CLASSES, ["Open Sans Regular"], COLOR.LAND_REGION, 0.02, PLACENAME_LAND_SIZE_STOPS)
24162
+ ];
24163
+ }
24035
24164
 
24036
24165
  // src/atlas/feature-decimation.ts
24037
24166
  function getGridSizeForZoom(zoom) {
@@ -26850,6 +26979,13 @@ function buildSources(opts = {}) {
26850
26979
  }
26851
26980
 
26852
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
+ ]);
26853
26989
  var GEOM_FILTERS = {
26854
26990
  fill: ["any", ["==", ["geometry-type"], "Polygon"], ["==", ["geometry-type"], "MultiPolygon"]],
26855
26991
  line: ["any", ["==", ["geometry-type"], "LineString"], ["==", ["geometry-type"], "MultiLineString"]],
@@ -27008,7 +27144,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
27008
27144
  const layers = [];
27009
27145
  const geomTypes = getGeomTypesForClass(objectClass, entry?.style.type);
27010
27146
  for (const geomType of geomTypes) {
27011
- const resolved = resolveStyle(objectClass, geomType, ct);
27147
+ const resolved = resolveStyle(objectClass, geomType, ct, { safetyContour: opts.safetyContour });
27012
27148
  const layerId = `s52-${objectClass}-${resolved.type}`;
27013
27149
  const layer = {
27014
27150
  id: layerId,
@@ -27027,6 +27163,9 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
27027
27163
  } else if (GEOM_FILTERS[resolved.type]) {
27028
27164
  layer.filter = GEOM_FILTERS[resolved.type];
27029
27165
  }
27166
+ if (SCAMIN_DECLUTTER_CLASSES.has(objectClass)) {
27167
+ layer.filter = layer.filter ? ["all", layer.filter, scaminZoomGateFilter()] : ["all", scaminZoomGateFilter()];
27168
+ }
27030
27169
  if (resolved.minzoom) {
27031
27170
  layer.minzoom = resolved.minzoom;
27032
27171
  }
@@ -28322,6 +28461,18 @@ function isExemptOpaqueFill(layer) {
28322
28461
  }
28323
28462
  return false;
28324
28463
  }
28464
+ var FILL_FLOOR_FREE_BANDS = /* @__PURE__ */ new Set([
28465
+ "1",
28466
+ "2",
28467
+ "3"
28468
+ ]);
28469
+ function bandVariantMinzoom(layer, B) {
28470
+ const classMinzoom = layer.minzoom ?? 0;
28471
+ if (isExemptOpaqueFill(layer) && FILL_FLOOR_FREE_BANDS.has(B)) {
28472
+ return classMinzoom;
28473
+ }
28474
+ return Math.max(classMinzoom, BAND_PRIORITY_RANGES[B].minzoom);
28475
+ }
28325
28476
  var BAND_DRAW_ORDER = ["1", "2", "3", "4", "5", "6"];
28326
28477
  var CHART_BAND_CODES = {
28327
28478
  Overview: ["1"],
@@ -28440,9 +28591,7 @@ function collapsedDominanceGate(B, minzoom) {
28440
28591
  return ["any", ...clauses];
28441
28592
  }
28442
28593
  function makeCollapsedBandVariant(layer, B, isExempt) {
28443
- const range = BAND_PRIORITY_RANGES[B];
28444
- const classMinzoom = layer.minzoom ?? 0;
28445
- const minzoom = isExemptOpaqueFill(layer) ? classMinzoom : Math.max(classMinzoom, range.minzoom);
28594
+ const minzoom = bandVariantMinzoom(layer, B);
28446
28595
  if (isExempt) {
28447
28596
  const exempt = {
28448
28597
  ...layer,
@@ -28581,8 +28730,7 @@ function isDominanceExempt(layer) {
28581
28730
  var TOP_STACK_LIGHT_CLASSES = /* @__PURE__ */ new Set(["LIGHTS"]);
28582
28731
  function makeBandVariants(layer, B) {
28583
28732
  const range = BAND_PRIORITY_RANGES[B];
28584
- const classMinzoom = layer.minzoom ?? 0;
28585
- const minzoom = isExemptOpaqueFill(layer) ? classMinzoom : Math.max(classMinzoom, range.minzoom);
28733
+ const minzoom = bandVariantMinzoom(layer, B);
28586
28734
  const variants = [];
28587
28735
  if (isDominanceExempt(layer)) {
28588
28736
  const exempt = {
@@ -28812,9 +28960,12 @@ function generateStyle(palette = "DAY", opts = {}) {
28812
28960
  emitChartFeatureVariants(topLightBundles);
28813
28961
  const chartLabelBundles = byLabelPriorityBottomUp(
28814
28962
  bundles.filter(
28815
- (b) => b.label !== null
28963
+ (b) => b.label !== null && (opts.labels !== "placename" || getLabelBehavior(b.objectClass) !== "orientation-cross-band")
28816
28964
  )
28817
28965
  );
28966
+ if (opts.labels === "placename") {
28967
+ labelLayers.push(...buildPlaceNameLayers(sourceId));
28968
+ }
28818
28969
  for (const { label } of chartLabelBundles) {
28819
28970
  if (bandFan === "collapsed") {
28820
28971
  labelLayers.push(
@@ -28859,10 +29010,14 @@ function generateStyle(palette = "DAY", opts = {}) {
28859
29010
  }
28860
29011
  }
28861
29012
  }
28862
- for (const { objectClass, label } of bundles) {
28863
- if (!label) continue;
28864
- if (getLabelBehavior(objectClass) === "orientation-cross-band") {
28865
- labelLayers.push(...fanOrientationLabelByCellBand(label, bandFan));
29013
+ if (opts.labels === "placename") {
29014
+ labelLayers.push(...buildPlaceNameLayers(sourceId));
29015
+ } else {
29016
+ for (const { objectClass, label } of bundles) {
29017
+ if (!label) continue;
29018
+ if (getLabelBehavior(objectClass) === "orientation-cross-band") {
29019
+ labelLayers.push(...fanOrientationLabelByCellBand(label, bandFan));
29020
+ }
28866
29021
  }
28867
29022
  }
28868
29023
  const featureLabelBundles = byLabelPriorityBottomUp(
@@ -28883,6 +29038,12 @@ function generateStyle(palette = "DAY", opts = {}) {
28883
29038
  }
28884
29039
  const symbolSet = opts.symbolSet ?? "s52";
28885
29040
  const spriteBase = `${opts.tileBaseUrl ?? "https://tiles.chrt.co"}/v1/sprites`;
29041
+ const isLndareFill = (l) => /^s52-LNDARE-fill-band\d+$/.test(l.id);
29042
+ const emitted = opts.land ? (() => {
29043
+ const fills = layers.filter(isLndareFill);
29044
+ const rest = layers.filter((l) => !isLndareFill(l));
29045
+ return [rest[0], ...fills, ...rest.slice(1)];
29046
+ })() : layers;
28886
29047
  const style = {
28887
29048
  version: 8,
28888
29049
  name: `CHRT ${SYMBOL_SET_LABELS[symbolSet]} ${PALETTE_NAMES[palette]}${opts.chartMode ? ` \u2014 chart:${opts.chartMode.band}` : ""}`,
@@ -28891,7 +29052,7 @@ function generateStyle(palette = "DAY", opts = {}) {
28891
29052
  "chrt:symbol-set": symbolSet,
28892
29053
  "chrt:color-table": palette,
28893
29054
  "chrt:generated-at": (/* @__PURE__ */ new Date()).toISOString(),
28894
- "chrt:layer-count": layers.length,
29055
+ "chrt:layer-count": emitted.length,
28895
29056
  "chrt:land": Boolean(opts.land),
28896
29057
  "chrt:relief": Boolean(opts.relief),
28897
29058
  "chrt:render-mode": opts.chartMode ? `chart:${opts.chartMode.band}` : "continuous",
@@ -28914,7 +29075,7 @@ function generateStyle(palette = "DAY", opts = {}) {
28914
29075
  sprite: `${spriteBase}/${symbolSet}-${palette.toLowerCase()}`
28915
29076
  },
28916
29077
  glyphs: "https://fonts.openmaptiles.org/{fontstack}/{range}.pbf",
28917
- layers
29078
+ layers: emitted
28918
29079
  };
28919
29080
  if (symbolSet === "s101") return applyS101SymbolSet(style);
28920
29081
  if (symbolSet === "chrt") return applyChrtSymbolSet(style, palette);
@@ -29743,6 +29904,9 @@ var Chrt = class {
29743
29904
  NON_SOLAS_DISCLAIMER_SHORT,
29744
29905
  OfflineError,
29745
29906
  OfflineStyleError,
29907
+ PLACENAME_LAND_CLASSES,
29908
+ PLACENAME_SOURCE_LAYER,
29909
+ PLACENAME_WATER_CLASSES,
29746
29910
  RASTER_ATON_CLASSES,
29747
29911
  RASTER_GLYPH_NAMES,
29748
29912
  RASTER_ICON_PREFIX,
@@ -29782,10 +29946,18 @@ var Chrt = class {
29782
29946
  applyS101SymbolSet,
29783
29947
  atonIconExpression,
29784
29948
  bandFilterExpression,
29949
+ buildAirportTierExpression,
29950
+ buildBuiltUpAreaTierExpression,
29785
29951
  buildFlashPattern,
29786
29952
  buildLabelLayer,
29953
+ buildLakeTierExpression,
29954
+ buildLandAreaTierExpression,
29955
+ buildLandRegionTierExpression,
29787
29956
  buildLayersForClass,
29788
29957
  buildOfflineStyle,
29958
+ buildPlaceNameLayers,
29959
+ buildRiverTierExpression,
29960
+ buildSeaAreaTierExpression,
29789
29961
  buildSectorArcGeoJSON,
29790
29962
  buildSectorArcsFC,
29791
29963
  buildSectorsFromFeatures,