@chrtco/sdk 0.10.22 → 0.10.24

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
@@ -22653,13 +22653,23 @@ var HAS_OBJNAM = [
22653
22653
  ["has", "OBJNAM"],
22654
22654
  ["!=", ["coalesce", ["get", "OBJNAM"], ""], ""]
22655
22655
  ];
22656
+ var OBJNAM_TEXT = [
22657
+ "case",
22658
+ [
22659
+ "all",
22660
+ ["has", "NOBJNM"],
22661
+ [">=", ["index-of", "?", ["coalesce", ["get", "OBJNAM"], ""]], 0]
22662
+ ],
22663
+ ["get", "NOBJNM"],
22664
+ ["get", "OBJNAM"]
22665
+ ];
22656
22666
  function catalogueNamePairs(acronym) {
22657
22667
  const names = s57NameTable(acronym);
22658
22668
  return Object.keys(names).map(Number).sort((a, b) => a - b).map((code) => [String(code), names[code]]);
22659
22669
  }
22660
22670
  function buildLandmarkLabelExpression() {
22661
22671
  const categoryExpr = listAttrCase("CATLMK", catalogueNamePairs("CATLMK"), "");
22662
- const base = ["case", HAS_OBJNAM, ["get", "OBJNAM"], categoryExpr];
22672
+ const base = ["case", HAS_OBJNAM, OBJNAM_TEXT, categoryExpr];
22663
22673
  const conspic = s57AbbrTable("CONVIS")[1] ?? "";
22664
22674
  const conspicExpr = [
22665
22675
  "case",
@@ -22678,7 +22688,7 @@ function buildHarbourFacilityLabelExpression() {
22678
22688
  return [
22679
22689
  "case",
22680
22690
  HAS_OBJNAM,
22681
- ["get", "OBJNAM"],
22691
+ OBJNAM_TEXT,
22682
22692
  listAttrCase("CATHAF", catalogueNamePairs("CATHAF"), "")
22683
22693
  ];
22684
22694
  }
@@ -23235,31 +23245,37 @@ function extractNavAidDesignation(objnam) {
23235
23245
  return token;
23236
23246
  }
23237
23247
  function navAidDesignationTextExpression(objnam, colourPrefix) {
23238
- const len = ["length", objnam];
23248
+ const name = ["var", "navAidName"];
23249
+ const len = ["length", name];
23239
23250
  const tokenBranches = [];
23240
23251
  for (let k = 1; k <= 4; k++) {
23241
23252
  tokenBranches.push(
23242
23253
  [
23243
23254
  "all",
23244
23255
  [">", len, k],
23245
- ["==", ["slice", objnam, ["-", len, k + 1], ["-", len, k]], " "]
23256
+ ["==", ["slice", name, ["-", len, k + 1], ["-", len, k]], " "]
23246
23257
  ],
23247
- ["slice", objnam, ["-", len, k]]
23258
+ ["slice", name, ["-", len, k]]
23248
23259
  );
23249
23260
  }
23250
23261
  return [
23251
23262
  "let",
23252
- "desig",
23253
- ["case", ...tokenBranches, ""],
23263
+ "navAidName",
23264
+ objnam,
23254
23265
  [
23255
- "case",
23266
+ "let",
23267
+ "desig",
23268
+ ["case", ...tokenBranches, ""],
23256
23269
  [
23257
- "any",
23258
- ["==", ["var", "desig"], ""],
23259
- ["match", ["var", "desig"], [...NAV_DESIGNATION_STOPWORDS], true, false]
23260
- ],
23261
- ["concat", colourPrefix, objnam],
23262
- ["concat", colourPrefix, '"', ["var", "desig"], '"']
23270
+ "case",
23271
+ [
23272
+ "any",
23273
+ ["==", ["var", "desig"], ""],
23274
+ ["match", ["var", "desig"], [...NAV_DESIGNATION_STOPWORDS], true, false]
23275
+ ],
23276
+ ["concat", colourPrefix, name],
23277
+ ["concat", colourPrefix, '"', ["var", "desig"], '"']
23278
+ ]
23263
23279
  ]
23264
23280
  ];
23265
23281
  }
@@ -23366,7 +23382,7 @@ function getLabelConfig(layerName, opts = {}) {
23366
23382
  const tierExpr = buildLandRegionTierExpression();
23367
23383
  return {
23368
23384
  type: "symbol",
23369
- textField: ["get", "OBJNAM"],
23385
+ textField: OBJNAM_TEXT,
23370
23386
  // Tier-1 entry moved from z5 to z7: undersea-relief names without a
23371
23387
  // generic keyword ("Three Dory", "Outer Fall") can't be demoted by
23372
23388
  // pattern, so the overview/general bands stay clean by holding ALL
@@ -23435,7 +23451,7 @@ function getLabelConfig(layerName, opts = {}) {
23435
23451
  case "BOYLAT":
23436
23452
  return {
23437
23453
  type: "symbol",
23438
- textField: ["coalesce", ["get", "_navAidLabel"], ["get", "OBJNAM"]],
23454
+ textField: ["coalesce", ["get", "_navAidLabel"], OBJNAM_TEXT],
23439
23455
  textSize: NAV_AID_LABEL_TEXT_SIZE,
23440
23456
  textFont: NAV_AID_LABEL_FONT,
23441
23457
  textColor: DARK_BG,
@@ -23448,7 +23464,7 @@ function getLabelConfig(layerName, opts = {}) {
23448
23464
  case "BCNLAT":
23449
23465
  return {
23450
23466
  type: "symbol",
23451
- textField: ["coalesce", ["get", "_navAidLabel"], ["get", "OBJNAM"]],
23467
+ textField: ["coalesce", ["get", "_navAidLabel"], OBJNAM_TEXT],
23452
23468
  textSize: NAV_AID_LABEL_TEXT_SIZE,
23453
23469
  textFont: NAV_AID_LABEL_FONT,
23454
23470
  textColor: DARK_BG,
@@ -23462,7 +23478,7 @@ function getLabelConfig(layerName, opts = {}) {
23462
23478
  case "BCNCAR":
23463
23479
  return {
23464
23480
  type: "symbol",
23465
- textField: ["coalesce", ["get", "_navAidLabel"], ["get", "OBJNAM"]],
23481
+ textField: ["coalesce", ["get", "_navAidLabel"], OBJNAM_TEXT],
23466
23482
  textSize: NAV_AID_LABEL_TEXT_SIZE,
23467
23483
  textFont: NAV_AID_LABEL_FONT,
23468
23484
  textColor: DARK_BG,
@@ -23476,7 +23492,7 @@ function getLabelConfig(layerName, opts = {}) {
23476
23492
  case "BCNSPP":
23477
23493
  return {
23478
23494
  type: "symbol",
23479
- textField: ["coalesce", ["get", "_navAidLabel"], ["get", "OBJNAM"]],
23495
+ textField: ["coalesce", ["get", "_navAidLabel"], OBJNAM_TEXT],
23480
23496
  textSize: NAV_AID_LABEL_TEXT_SIZE,
23481
23497
  textFont: NAV_AID_LABEL_FONT,
23482
23498
  textColor: DARK_BG,
@@ -23490,7 +23506,7 @@ function getLabelConfig(layerName, opts = {}) {
23490
23506
  case "BOYSAW":
23491
23507
  return {
23492
23508
  type: "symbol",
23493
- textField: ["coalesce", ["get", "_navAidLabel"], ["get", "OBJNAM"]],
23509
+ textField: ["coalesce", ["get", "_navAidLabel"], OBJNAM_TEXT],
23494
23510
  textSize: NAV_AID_LABEL_TEXT_SIZE,
23495
23511
  textFont: NAV_AID_LABEL_FONT,
23496
23512
  textColor: DARK_BG,
@@ -23548,7 +23564,7 @@ function getLabelConfig(layerName, opts = {}) {
23548
23564
  const tierExpr = buildSeaAreaTierExpression();
23549
23565
  return {
23550
23566
  type: "symbol",
23551
- textField: ["get", "OBJNAM"],
23567
+ textField: OBJNAM_TEXT,
23552
23568
  // Default tier (CATSEA=0, uncategorised: canyons, seamounts, fishing
23553
23569
  // banks etc.) is suppressed at overview/general/coastal scales because
23554
23570
  // it floods the overview with hundreds of small italic labels that
@@ -23670,7 +23686,7 @@ function getLabelConfig(layerName, opts = {}) {
23670
23686
  const tierExpr = buildLakeTierExpression();
23671
23687
  return {
23672
23688
  type: "symbol",
23673
- textField: ["get", "OBJNAM"],
23689
+ textField: OBJNAM_TEXT,
23674
23690
  textSize: tieredSizeCurve(tierExpr, [
23675
23691
  [7, [9, 0]],
23676
23692
  [9, [11, 0]],
@@ -23691,7 +23707,7 @@ function getLabelConfig(layerName, opts = {}) {
23691
23707
  const tierExpr = buildRiverTierExpression();
23692
23708
  return {
23693
23709
  type: "symbol",
23694
- textField: ["get", "OBJNAM"],
23710
+ textField: OBJNAM_TEXT,
23695
23711
  textSize: tieredSizeCurve(tierExpr, [
23696
23712
  [7, [9, 0]],
23697
23713
  [9, [11, 0]],
@@ -23711,7 +23727,7 @@ function getLabelConfig(layerName, opts = {}) {
23711
23727
  case "CANALS":
23712
23728
  return {
23713
23729
  type: "symbol",
23714
- textField: ["get", "OBJNAM"],
23730
+ textField: OBJNAM_TEXT,
23715
23731
  textSize: [
23716
23732
  "interpolate",
23717
23733
  ["linear"],
@@ -23735,7 +23751,7 @@ function getLabelConfig(layerName, opts = {}) {
23735
23751
  const tierExpr = buildLandAreaTierExpression();
23736
23752
  return {
23737
23753
  type: "symbol",
23738
- textField: ["get", "OBJNAM"],
23754
+ textField: OBJNAM_TEXT,
23739
23755
  // Tier 2 is the SMALL island — the one the owner cannot identify.
23740
23756
  // Its old curve peaked at 10 px and fell to 10 px, i.e. it never got
23741
23757
  // its own scale: a name that only exists because you zoomed in was
@@ -23767,7 +23783,7 @@ function getLabelConfig(layerName, opts = {}) {
23767
23783
  const tierExpr = buildBuiltUpAreaTierExpression();
23768
23784
  return {
23769
23785
  type: "symbol",
23770
- textField: ["get", "OBJNAM"],
23786
+ textField: OBJNAM_TEXT,
23771
23787
  textSize: tieredSizeCurve(tierExpr, [
23772
23788
  [3, [11, 0, 0]],
23773
23789
  [5, [13, 0, 0]],
@@ -23791,7 +23807,7 @@ function getLabelConfig(layerName, opts = {}) {
23791
23807
  case "BRIDGE":
23792
23808
  return {
23793
23809
  type: "symbol",
23794
- textField: ["get", "OBJNAM"],
23810
+ textField: OBJNAM_TEXT,
23795
23811
  textSize: 9,
23796
23812
  textColor: "#bfa88a",
23797
23813
  textHaloColor: DARK_BG,
@@ -23810,7 +23826,7 @@ function getLabelConfig(layerName, opts = {}) {
23810
23826
  case "PILPNT":
23811
23827
  return {
23812
23828
  type: "symbol",
23813
- textField: ["get", "OBJNAM"],
23829
+ textField: OBJNAM_TEXT,
23814
23830
  textSize: 8,
23815
23831
  textColor: "#c8c0b0",
23816
23832
  textHaloColor: DARK_BG,
@@ -23821,7 +23837,7 @@ function getLabelConfig(layerName, opts = {}) {
23821
23837
  case "MORFAC":
23822
23838
  return {
23823
23839
  type: "symbol",
23824
- textField: ["get", "OBJNAM"],
23840
+ textField: OBJNAM_TEXT,
23825
23841
  textSize: 8,
23826
23842
  textColor: "#c8c0b0",
23827
23843
  textHaloColor: DARK_BG,
@@ -23832,7 +23848,7 @@ function getLabelConfig(layerName, opts = {}) {
23832
23848
  case "SLCONS":
23833
23849
  return {
23834
23850
  type: "symbol",
23835
- textField: ["get", "OBJNAM"],
23851
+ textField: OBJNAM_TEXT,
23836
23852
  textSize: 8,
23837
23853
  textColor: "#bfa88a",
23838
23854
  textHaloColor: DARK_BG,
@@ -23847,7 +23863,7 @@ function getLabelConfig(layerName, opts = {}) {
23847
23863
  case "BUISGL":
23848
23864
  return {
23849
23865
  type: "symbol",
23850
- textField: ["get", "OBJNAM"],
23866
+ textField: OBJNAM_TEXT,
23851
23867
  textSize: 8,
23852
23868
  textColor: DARK_BG,
23853
23869
  textHaloColor: "#c8c0b0",
@@ -23863,7 +23879,7 @@ function getLabelConfig(layerName, opts = {}) {
23863
23879
  const tierExpr = buildAirportTierExpression();
23864
23880
  return {
23865
23881
  type: "symbol",
23866
- textField: ["get", "OBJNAM"],
23882
+ textField: OBJNAM_TEXT,
23867
23883
  textSize: tieredSizeCurve(tierExpr, [
23868
23884
  [7, [9, 0]],
23869
23885
  [9, [11, 0]],
@@ -23947,7 +23963,7 @@ function getLabelConfig(layerName, opts = {}) {
23947
23963
  case "PRDARE":
23948
23964
  return {
23949
23965
  type: "symbol",
23950
- textField: ["get", "OBJNAM"],
23966
+ textField: OBJNAM_TEXT,
23951
23967
  textSize: [
23952
23968
  "interpolate",
23953
23969
  ["linear"],
@@ -27327,7 +27343,7 @@ function buildNavAidLabelExpression() {
27327
27343
  // doesn't precompute `_navAidLabel`, so before this expression every
27328
27344
  // production buoy label carried its full 30-40 glyph OBJNAM, the
27329
27345
  // biggest single driver of Harbor/Approach label overprint (E3).
27330
- navAidDesignationTextExpression(["get", "OBJNAM"], colourPrefix),
27346
+ navAidDesignationTextExpression(OBJNAM_TEXT, colourPrefix),
27331
27347
  ""
27332
27348
  ];
27333
27349
  }
@@ -27743,7 +27759,7 @@ function buildLayersForClass(objectClass, ct, sourceId = DEFAULT_REGION, opts =
27743
27759
  "icon-allow-overlap": true,
27744
27760
  "icon-ignore-placement": false,
27745
27761
  "symbol-placement": "point",
27746
- "text-field": ["case", ["has", "OBJNAM"], ["get", "OBJNAM"], ""],
27762
+ "text-field": ["case", ["has", "OBJNAM"], OBJNAM_TEXT, ""],
27747
27763
  "text-size": ["interpolate", ["linear"], ["zoom"], 11, 9, 16, 11],
27748
27764
  "text-anchor": "top",
27749
27765
  "text-offset": [0, 1.4],
@@ -28460,6 +28476,18 @@ function buildLabelLayer2(objectClass, ct, sourceId = DEFAULT_REGION, depthUnits
28460
28476
  ["round", ["*", ["to-number", ["get", "VALDCO"]], 3.28084]],
28461
28477
  "ft"
28462
28478
  ];
28479
+ } else if (objectClass === "UWTROC" || objectClass === "OBSTRN" || objectClass === "WRECKS") {
28480
+ const valsouFt = [
28481
+ "to-number",
28482
+ ["coalesce", ["get", "VALSOU"], ["get", "valsou"], 0],
28483
+ 0
28484
+ ];
28485
+ textField = [
28486
+ "case",
28487
+ [">", valsouFt, 0],
28488
+ ["concat", ["to-string", ["round", ["*", valsouFt, 3.28084]]], "ft"],
28489
+ ""
28490
+ ];
28463
28491
  }
28464
28492
  }
28465
28493
  const labelPriority = getLabelPriority(objectClass);
@@ -29066,36 +29094,60 @@ var BASE_THEMES = {
29066
29094
  inlandHalo: "#B9CFDD"
29067
29095
  },
29068
29096
  DUSK: {
29069
- // Same rule, this palette's tan: 3.03:1 vs sea, 2.97:1 vs tan.
29070
- earth: "#6F6752",
29097
+ // ── Re-derived 2026-09-01: the basemap sits UNDER the chart ──────────
29098
+ // These were the DAY tones scaled by a constant, never measured against
29099
+ // the DUSK chart — so `jurisdiction` came out at L=0.506 against a chart
29100
+ // whose own land (LANDA #8a7d5e) is L=0.209, i.e. the context layer was
29101
+ // 2.4x brighter than the data layer. Now capped at the chart's own land.
29102
+ // The rest of the block rides the same 0.666 channel factor so every
29103
+ // relationship the palette already chose is preserved, only moved down.
29104
+ // `water` is deliberately NOT scaled: it is the reference the land/sea
29105
+ // floor is measured against.
29106
+ earth: "#544E3E",
29071
29107
  water: "#161D21",
29072
- river: "#1E2930",
29073
- boundary: "#3E4A50",
29074
- jurisdiction: "#C9BC96",
29075
- inlandWater: "#93A9B7",
29076
- stateLine: "#A6976F",
29077
- stateLabel: "#776E55",
29078
- stateHalo: "#C9BC96",
29079
- inlandShore: "#74899A",
29080
- inlandLabel: "#41586A",
29081
- inlandHalo: "#93A9B7"
29108
+ river: "#141B20",
29109
+ boundary: "#293135",
29110
+ jurisdiction: "#867D64",
29111
+ inlandWater: "#62717A",
29112
+ // Label stays DARK here — the halo is still mid-toned, so 3:1 is reachable
29113
+ // downward and a bright label would be a new light source at dusk.
29114
+ stateLine: "#B5A987",
29115
+ stateLabel: "#38342A",
29116
+ stateHalo: "#867D64",
29117
+ inlandShore: "#4D5B67",
29118
+ inlandLabel: "#212629",
29119
+ inlandHalo: "#62717A"
29082
29120
  },
29083
29121
  NIGHT: {
29084
- // Same rule. NIGHT is inherently compressed — every tone is dark — so it
29085
- // reaches 2.06:1 vs sea / 2.44:1 vs tan rather than 3:1. Still 1.8x the
29086
- // separation it had, and pushing further would collide with the tan.
29087
- earth: "#4F4A3B",
29122
+ // ── Re-derived 2026-09-01 — the owner-reported night defect ──────────
29123
+ // `jurisdiction` was L=0.240 against a chart whose own land (LANDA
29124
+ // #3a3020) is L=0.031: the world basemap was 7.7x BRIGHTER than the chart
29125
+ // it sits under, so at z<11 the uncharted interior read as a pale
29126
+ // continent-sized slab on a night chart. Capped at the chart's own land.
29127
+ //
29128
+ // ⚠ `earth` AND `jurisdiction` CONVERGE HERE, and that is forced, not
29129
+ // sloppy. Holding earth 2:1 above the ocean AND 2:1 below the jurisdiction
29130
+ // tan requires jurisdiction >= L 0.174 — 5.6x the chart's own land. At
29131
+ // night the range between the ocean (L 0.006) and the cap (L 0.031) is too
29132
+ // compressed to carry two land tones. COVERAGE IS STILL SAID, by the
29133
+ // `chrt:hatch-nocoverage` crosshatch, which is a stronger signal than tone
29134
+ // and is the one thing that cannot be lost to palette compression.
29135
+ //
29136
+ // Labels invert to light-on-dark: 3:1 downward is unreachable off a halo
29137
+ // this close to black, and CHRTland's own night town labels already sit at
29138
+ // L=0.177 for exactly this reason.
29139
+ earth: "#333026",
29088
29140
  water: "#10161A",
29089
- river: "#182228",
29090
- boundary: "#333E44",
29091
- jurisdiction: "#8F866C",
29092
- inlandWater: "#5F7280",
29093
- stateLine: "#6B6350",
29094
- stateLabel: "#565040",
29095
- stateHalo: "#8F866C",
29096
- inlandShore: "#4A5C68",
29097
- inlandLabel: "#8CA2B0",
29098
- inlandHalo: "#5F7280"
29141
+ river: "#090D0F",
29142
+ boundary: "#131719",
29143
+ jurisdiction: "#353128",
29144
+ inlandWater: "#232A30",
29145
+ stateLine: "#484236",
29146
+ stateLabel: "#837963",
29147
+ stateHalo: "#353128",
29148
+ inlandShore: "#1C2227",
29149
+ inlandLabel: "#617484",
29150
+ inlandHalo: "#232A30"
29099
29151
  }
29100
29152
  };
29101
29153
  function baseLayers(opts = {}) {
@@ -30022,6 +30074,7 @@ function generateStyle(palette = "DAY", opts = {}) {
30022
30074
  }
30023
30075
  const symbolSet = opts.symbolSet ?? "s52";
30024
30076
  const spriteBase = `${opts.tileBaseUrl ?? "https://tiles.chrt.co"}/v1/sprites`;
30077
+ const glyphsBase = `${opts.tileBaseUrl ?? "https://tiles.chrt.co"}/v1/glyphs`;
30025
30078
  const isLndareFill = (l) => /^s52-LNDARE-fill-band\d+$/.test(l.id);
30026
30079
  const isWaterBaseFill = (l) => /^s52-(DEPARE|DRGARE|UNSARE)-fill-band\d+$/.test(l.id);
30027
30080
  const bandOf = (l) => {
@@ -30163,7 +30216,23 @@ function generateStyle(palette = "DAY", opts = {}) {
30163
30216
  ...symbolSet !== "s101" && {
30164
30217
  sprite: `${spriteBase}/${symbolSet}-${palette.toLowerCase()}`
30165
30218
  },
30166
- glyphs: "https://fonts.openmaptiles.org/{fontstack}/{range}.pbf",
30219
+ // ⚠ DEPLOY ORDER. This URL is baked into the style by callers on BOTH
30220
+ // sides: the tile-worker's /v1/styles route AND the app's own
30221
+ // `generateStyle` call in ChartCanvas.tsx, which ships from Railway on a
30222
+ // SEPARATE deploy from the Workers. The glyph objects must be in R2 and
30223
+ // the /v1/glyphs route live BEFORE any client asks for this URL — if the
30224
+ // frontend ships first, every label on the chart goes blank, not just the
30225
+ // ones with an ʻokina. Land the tile-worker deploy first, or revert this
30226
+ // one line to the openmaptiles URL to fall back.
30227
+ //
30228
+ // Self-hosted, beside the sprite sheets on the same host. Was
30229
+ // fonts.openmaptiles.org, whose 2013-vintage Open Sans has NO GLYPH for
30230
+ // U+02BB ʻOKINA (a CONSONANT in Hawaiian) or five other codepoints that
30231
+ // occur in NOAA names — MapLibre draws a missing glyph as nothing, so
30232
+ // "Island of Hawaiʻi" rendered with a hole in it. Our ranges keep every
30233
+ // upstream glyph byte-identical and composite only those six in from
30234
+ // Noto Sans; see scripts/generate-glyphs.mjs.
30235
+ glyphs: `${glyphsBase}/{fontstack}/{range}.pbf`,
30167
30236
  layers: emitted
30168
30237
  };
30169
30238
  if (symbolSet === "s101") return applyS101SymbolSet(style);