@chrtco/sdk 0.10.10 → 0.10.12

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,
@@ -24032,6 +24043,102 @@ function computePolygonCentroid(geom) {
24032
24043
  }
24033
24044
  return [sumX / coords.length, sumY / coords.length];
24034
24045
  }
24046
+ var PLACENAME_SOURCE_LAYER = "PLACENAME";
24047
+ var PLACENAME_WATER_CLASSES = [
24048
+ "SEAARE",
24049
+ "LAKARE",
24050
+ "RIVERS",
24051
+ "CANALS",
24052
+ "PRDARE"
24053
+ ];
24054
+ var PLACENAME_LAND_CLASSES = [
24055
+ "LNDRGN",
24056
+ "LNDARE",
24057
+ "BUAARE",
24058
+ "AIRARE"
24059
+ ];
24060
+ var PLACENAME_FITS = ["<=", ["coalesce", ["get", "min_z"], 0], ["zoom"]];
24061
+ var PLACENAME_RETIRES = [">=", ["coalesce", ["get", "max_z"], 14], ["zoom"]];
24062
+ var PLACENAME_TIER = [
24063
+ "to-number",
24064
+ ["coalesce", ["get", "tier"], 9]
24065
+ ];
24066
+ var PLACENAME_TEXT_OPACITY = [
24067
+ "interpolate",
24068
+ ["linear"],
24069
+ ["zoom"],
24070
+ 3,
24071
+ 0.55,
24072
+ 9,
24073
+ 0.5,
24074
+ 12,
24075
+ 0.4,
24076
+ 16,
24077
+ 0.3
24078
+ ];
24079
+ var PLACENAME_WATER_SIZE_STOPS = [
24080
+ [3, [14, 9, 0, 0, 0, 0, 0, 0, 0, 0]],
24081
+ [5, [16, 13, 9, 0, 0, 0, 0, 0, 0, 0]],
24082
+ [7, [14, 14, 12, 9, 0, 0, 0, 0, 0, 0]],
24083
+ [9, [12, 13, 14, 11, 10, 9, 0, 9, 9, 9]],
24084
+ [11, [10, 12, 13, 12, 10, 9, 9, 11, 11, 9]],
24085
+ [13, [9, 11, 12, 11, 10, 10, 10, 11, 11, 10]],
24086
+ [16, [9, 10, 11, 10, 9, 9, 9, 10, 10, 9]]
24087
+ ];
24088
+ var PLACENAME_LAND_SIZE_STOPS = [
24089
+ [3, [0, 0, 11, 0, 0, 0, 0, 0, 0, 0]],
24090
+ [5, [0, 0, 13, 0, 0, 0, 0, 0, 0, 0]],
24091
+ [7, [0, 0, 13, 9, 9, 0, 0, 0, 0, 0]],
24092
+ [9, [0, 0, 12, 12, 11, 0, 0, 0, 0, 0]],
24093
+ [11, [0, 0, 11, 12, 11, 9, 9, 0, 0, 9]],
24094
+ [13, [0, 0, 10, 11, 10, 11, 10, 10, 10, 10]],
24095
+ [16, [0, 0, 10, 10, 9, 10, 9, 9, 9, 9]]
24096
+ ];
24097
+ function buildPlaceNameLayers(sourceId) {
24098
+ const sortKey = ["to-number", ["coalesce", ["get", "rank"], 9999999]];
24099
+ const make = (idSuffix, classes, font, haloColor, letterSpacing, stops) => ({
24100
+ id: `chrt-placename-${idSuffix}`,
24101
+ type: "symbol",
24102
+ source: sourceId,
24103
+ "source-layer": PLACENAME_SOURCE_LAYER,
24104
+ minzoom: 3,
24105
+ filter: [
24106
+ "all",
24107
+ ["match", ["get", "cls"], [...classes], true, false],
24108
+ PLACENAME_FITS,
24109
+ PLACENAME_RETIRES
24110
+ ],
24111
+ layout: {
24112
+ "text-field": ["get", "name"],
24113
+ "text-font": font,
24114
+ "text-size": tieredSizeCurve(PLACENAME_TIER, stops),
24115
+ "text-letter-spacing": letterSpacing,
24116
+ "text-max-width": 9,
24117
+ "text-padding": 4,
24118
+ "text-allow-overlap": false,
24119
+ "text-ignore-placement": false,
24120
+ "text-optional": true,
24121
+ "text-variable-anchor": ["center", "top", "bottom", "left", "right"],
24122
+ "text-radial-offset": 0.3,
24123
+ "text-justify": "auto",
24124
+ "symbol-sort-key": sortKey
24125
+ },
24126
+ paint: {
24127
+ "text-color": DARK_BG,
24128
+ "text-halo-color": haloColor,
24129
+ "text-halo-width": ORIENTATION_HALO_WIDTH,
24130
+ "text-opacity": PLACENAME_TEXT_OPACITY
24131
+ },
24132
+ metadata: {
24133
+ "chrt:placename": true,
24134
+ "chrt:labelPriority": ORIENTATION_LABEL_PRIORITY
24135
+ }
24136
+ });
24137
+ return [
24138
+ make("water", PLACENAME_WATER_CLASSES, ["Open Sans Italic"], COLOR.WATER_DEEP, 0.05, PLACENAME_WATER_SIZE_STOPS),
24139
+ make("land", PLACENAME_LAND_CLASSES, ["Open Sans Regular"], COLOR.LAND_REGION, 0.02, PLACENAME_LAND_SIZE_STOPS)
24140
+ ];
24141
+ }
24035
24142
 
24036
24143
  // src/atlas/feature-decimation.ts
24037
24144
  function getGridSizeForZoom(zoom) {
@@ -28307,6 +28414,21 @@ var BAND_DATA_MINZOOM = {
28307
28414
  function dominatedVariantMaxzoom(D) {
28308
28415
  return Math.max(BAND_PRIORITY_RANGES[D].minzoom, BAND_DATA_MINZOOM[D]);
28309
28416
  }
28417
+ var DOMINANCE_EXEMPT_FILL_CLASSES = /* @__PURE__ */ new Set([
28418
+ "LNDARE",
28419
+ "DEPARE",
28420
+ "UNSARE",
28421
+ "LAKARE",
28422
+ "DRGARE",
28423
+ "SEAARE"
28424
+ ]);
28425
+ function isExemptOpaqueFill(layer) {
28426
+ if (layer.type !== "fill") return false;
28427
+ for (const cls of DOMINANCE_EXEMPT_FILL_CLASSES) {
28428
+ if (layer.id.startsWith(`s52-${cls}-`)) return true;
28429
+ }
28430
+ return false;
28431
+ }
28310
28432
  var BAND_DRAW_ORDER = ["1", "2", "3", "4", "5", "6"];
28311
28433
  var CHART_BAND_CODES = {
28312
28434
  Overview: ["1"],
@@ -28427,7 +28549,7 @@ function collapsedDominanceGate(B, minzoom) {
28427
28549
  function makeCollapsedBandVariant(layer, B, isExempt) {
28428
28550
  const range = BAND_PRIORITY_RANGES[B];
28429
28551
  const classMinzoom = layer.minzoom ?? 0;
28430
- const minzoom = Math.max(classMinzoom, range.minzoom);
28552
+ const minzoom = isExemptOpaqueFill(layer) ? classMinzoom : Math.max(classMinzoom, range.minzoom);
28431
28553
  if (isExempt) {
28432
28554
  const exempt = {
28433
28555
  ...layer,
@@ -28556,30 +28678,18 @@ var DOMINANCE_EXEMPT_CLASSES = /* @__PURE__ */ new Set([
28556
28678
  "RTPBCN",
28557
28679
  "RDOSTA"
28558
28680
  ]);
28559
- var DOMINANCE_EXEMPT_FILL_CLASSES = /* @__PURE__ */ new Set([
28560
- "LNDARE",
28561
- "DEPARE",
28562
- "UNSARE",
28563
- "LAKARE",
28564
- "DRGARE",
28565
- "SEAARE"
28566
- ]);
28567
28681
  function isDominanceExempt(layer) {
28568
28682
  for (const cls of DOMINANCE_EXEMPT_CLASSES) {
28569
28683
  if (layer.id.startsWith(`s52-${cls}-`)) return true;
28570
28684
  }
28571
- if (layer.type === "fill") {
28572
- for (const cls of DOMINANCE_EXEMPT_FILL_CLASSES) {
28573
- if (layer.id.startsWith(`s52-${cls}-`)) return true;
28574
- }
28575
- }
28685
+ if (isExemptOpaqueFill(layer)) return true;
28576
28686
  return false;
28577
28687
  }
28578
28688
  var TOP_STACK_LIGHT_CLASSES = /* @__PURE__ */ new Set(["LIGHTS"]);
28579
28689
  function makeBandVariants(layer, B) {
28580
28690
  const range = BAND_PRIORITY_RANGES[B];
28581
28691
  const classMinzoom = layer.minzoom ?? 0;
28582
- const minzoom = Math.max(classMinzoom, range.minzoom);
28692
+ const minzoom = isExemptOpaqueFill(layer) ? classMinzoom : Math.max(classMinzoom, range.minzoom);
28583
28693
  const variants = [];
28584
28694
  if (isDominanceExempt(layer)) {
28585
28695
  const exempt = {
@@ -28809,9 +28919,12 @@ function generateStyle(palette = "DAY", opts = {}) {
28809
28919
  emitChartFeatureVariants(topLightBundles);
28810
28920
  const chartLabelBundles = byLabelPriorityBottomUp(
28811
28921
  bundles.filter(
28812
- (b) => b.label !== null
28922
+ (b) => b.label !== null && (opts.labels !== "placename" || getLabelBehavior(b.objectClass) !== "orientation-cross-band")
28813
28923
  )
28814
28924
  );
28925
+ if (opts.labels === "placename") {
28926
+ labelLayers.push(...buildPlaceNameLayers(sourceId));
28927
+ }
28815
28928
  for (const { label } of chartLabelBundles) {
28816
28929
  if (bandFan === "collapsed") {
28817
28930
  labelLayers.push(
@@ -28856,10 +28969,14 @@ function generateStyle(palette = "DAY", opts = {}) {
28856
28969
  }
28857
28970
  }
28858
28971
  }
28859
- for (const { objectClass, label } of bundles) {
28860
- if (!label) continue;
28861
- if (getLabelBehavior(objectClass) === "orientation-cross-band") {
28862
- labelLayers.push(...fanOrientationLabelByCellBand(label, bandFan));
28972
+ if (opts.labels === "placename") {
28973
+ labelLayers.push(...buildPlaceNameLayers(sourceId));
28974
+ } else {
28975
+ for (const { objectClass, label } of bundles) {
28976
+ if (!label) continue;
28977
+ if (getLabelBehavior(objectClass) === "orientation-cross-band") {
28978
+ labelLayers.push(...fanOrientationLabelByCellBand(label, bandFan));
28979
+ }
28863
28980
  }
28864
28981
  }
28865
28982
  const featureLabelBundles = byLabelPriorityBottomUp(
@@ -29740,6 +29857,9 @@ var Chrt = class {
29740
29857
  NON_SOLAS_DISCLAIMER_SHORT,
29741
29858
  OfflineError,
29742
29859
  OfflineStyleError,
29860
+ PLACENAME_LAND_CLASSES,
29861
+ PLACENAME_SOURCE_LAYER,
29862
+ PLACENAME_WATER_CLASSES,
29743
29863
  RASTER_ATON_CLASSES,
29744
29864
  RASTER_GLYPH_NAMES,
29745
29865
  RASTER_ICON_PREFIX,
@@ -29779,10 +29899,18 @@ var Chrt = class {
29779
29899
  applyS101SymbolSet,
29780
29900
  atonIconExpression,
29781
29901
  bandFilterExpression,
29902
+ buildAirportTierExpression,
29903
+ buildBuiltUpAreaTierExpression,
29782
29904
  buildFlashPattern,
29783
29905
  buildLabelLayer,
29906
+ buildLakeTierExpression,
29907
+ buildLandAreaTierExpression,
29908
+ buildLandRegionTierExpression,
29784
29909
  buildLayersForClass,
29785
29910
  buildOfflineStyle,
29911
+ buildPlaceNameLayers,
29912
+ buildRiverTierExpression,
29913
+ buildSeaAreaTierExpression,
29786
29914
  buildSectorArcGeoJSON,
29787
29915
  buildSectorArcsFC,
29788
29916
  buildSectorsFromFeatures,