@chrtco/sdk 0.10.7 → 0.10.9

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,22 @@ All notable changes to `@chrtco/sdk` will be documented in this file.
5
5
  ## [Unreleased]
6
6
 
7
7
  ### Added
8
+ - **CHRTland on the Style API** — `StyleUrlOptions.land` / `.relief` thread
9
+ `?land=` / `?relief=` to `GET /v1/styles/{region}/style.json`. `generateStyle`
10
+ learned to emit CHRTland and the first-party app learned to ask for it, but
11
+ the SERVED style had no parameter, so every consumer that takes its style
12
+ from the worker — CHRTsail included — got a document with no `chrtland-*`
13
+ layers and no `CHRT_LAND` source at all. `land=1` adds the land base beneath
14
+ every S-57 class (it can never occlude a portrayal); `relief=1` adds the
15
+ terrarium hillshade, is separately opt-in, and requires `land` (`relief`
16
+ alone is a typed 400). Both are authorized like any region: the token must
17
+ carry `CHRT_LAND` / `CHRT_RELIEF` or the request is a 403 naming the missing
18
+ one, so a served style never declares a source whose tiles the holder cannot
19
+ fetch. **`CHRT_RELIEF` is granted by no chart source yet, so `relief=1` is
20
+ refused today** — it begins working with no SDK or API change once that
21
+ grant ships. CHRTland is **not chart data and not from a hydrographic
22
+ office**; never present its shoreline as charted. New exports: `LAND_REGION`,
23
+ `RELIEF_REGION`.
8
24
  - **Render-mode lever on the Style API** (`sail#2`): `StyleUrlOptions.mode` /
9
25
  `OfflineFetchStyleOptions.mode` — `"continuous"` (default) or
10
26
  `"chart:{band}"` (`chart:Overview | chart:General | chart:Coastal |
package/dist/index.cjs CHANGED
@@ -62,6 +62,7 @@ __export(index_exports, {
62
62
  LABEL_BEHAVIOR: () => LABEL_BEHAVIOR,
63
63
  LABEL_LAYER_GROUPS: () => LABEL_LAYER_GROUPS,
64
64
  LABEL_PRIORITY: () => LABEL_PRIORITY,
65
+ LAND_REGION: () => LAND_REGION,
65
66
  NAUTICAL_SYMBOLS: () => NAUTICAL_SYMBOLS,
66
67
  NAV_AID_LAYER_GROUPS: () => NAV_AID_LAYER_GROUPS,
67
68
  NAV_AID_QUERY_LAYERS: () => NAV_AID_QUERY_LAYERS,
@@ -72,6 +73,7 @@ __export(index_exports, {
72
73
  RASTER_ATON_CLASSES: () => RASTER_ATON_CLASSES,
73
74
  RASTER_GLYPH_NAMES: () => RASTER_GLYPH_NAMES,
74
75
  RASTER_ICON_PREFIX: () => RASTER_ICON_PREFIX,
76
+ RELIEF_REGION: () => RELIEF_REGION,
75
77
  S101_AREA_FILL_NAMES: () => S101_AREA_FILL_NAMES,
76
78
  S101_CHART_SECTIONS: () => S101_CHART_SECTIONS,
77
79
  S101_FEATURES: () => S101_FEATURES,
@@ -549,6 +551,8 @@ var TilesEndpoint = class {
549
551
  if (opts.theme) params.set("theme", opts.theme);
550
552
  if (opts.symbols) params.set("symbols", opts.symbols);
551
553
  if (opts.mode) params.set("mode", opts.mode);
554
+ if (opts.land) params.set("land", "1");
555
+ if (opts.relief) params.set("relief", "1");
552
556
  const query2 = params.size > 0 ? `?${params.toString()}` : "";
553
557
  return `${this.tileBaseUrl}/v1/styles/${this.region}/style.json${query2}`;
554
558
  }
@@ -1845,6 +1849,14 @@ var CATWRK_CODES = {
1845
1849
  4: { code: 4, name: "Wreck showing mast/masts" },
1846
1850
  5: { code: 5, name: "Wreck showing any portion of hull or superstructure" }
1847
1851
  };
1852
+ var CATZOC_CODES = {
1853
+ 1: { code: 1, name: "Zone of confidence A1" },
1854
+ 2: { code: 2, name: "Zone of confidence A2" },
1855
+ 3: { code: 3, name: "Zone of confidence B" },
1856
+ 4: { code: 4, name: "Zone of confidence C" },
1857
+ 5: { code: 5, name: "Zone of confidence D" },
1858
+ 6: { code: 6, name: "Zone of confidence U (data not assessed)" }
1859
+ };
1848
1860
  var COLOUR_CODES = {
1849
1861
  1: { code: 1, name: "White", abbr: "W" },
1850
1862
  2: { code: 2, name: "Black", abbr: "B" },
@@ -1904,6 +1916,18 @@ var MARSYS_CODES = {
1904
1916
  9: { code: 9, name: "No system" },
1905
1917
  10: { code: 10, name: "Other system" }
1906
1918
  };
1919
+ var NATQUA_CODES = {
1920
+ 1: { code: 1, name: "Fine" },
1921
+ 2: { code: 2, name: "Medium" },
1922
+ 3: { code: 3, name: "Coarse" },
1923
+ 4: { code: 4, name: "Broken" },
1924
+ 5: { code: 5, name: "Sticky" },
1925
+ 6: { code: 6, name: "Soft" },
1926
+ 7: { code: 7, name: "Stiff" },
1927
+ 8: { code: 8, name: "Volcanic" },
1928
+ 9: { code: 9, name: "Calcareous" },
1929
+ 10: { code: 10, name: "Hard" }
1930
+ };
1907
1931
  var NATSUR_CODES = {
1908
1932
  1: { code: 1, name: "Mud" },
1909
1933
  2: { code: 2, name: "Clay" },
@@ -2045,11 +2069,13 @@ var S57_CODELISTS = /* @__PURE__ */ new Map([
2045
2069
  ["CATREA", CATREA_CODES],
2046
2070
  ["CATSCF", CATSCF_CODES],
2047
2071
  ["CATWRK", CATWRK_CODES],
2072
+ ["CATZOC", CATZOC_CODES],
2048
2073
  ["COLOUR", COLOUR_CODES],
2049
2074
  ["CONDTN", CONDTN_CODES],
2050
2075
  ["CONVIS", CONVIS_CODES],
2051
2076
  ["LITCHR", LITCHR_CODES],
2052
2077
  ["MARSYS", MARSYS_CODES],
2078
+ ["NATQUA", NATQUA_CODES],
2053
2079
  ["NATSUR", NATSUR_CODES],
2054
2080
  ["QUASOU", QUASOU_CODES],
2055
2081
  ["RESTRN", RESTRN_CODES],
@@ -7416,6 +7442,222 @@ var S52_LOOKUP = {
7416
7442
  displayCategory: "META",
7417
7443
  style: { type: "fill", paint: { "fill-color": "CHGRF", "fill-opacity": 0 } },
7418
7444
  auditIgnore: true
7445
+ },
7446
+ // ── Inland ENC domain (USACE IENC) ──────────────────────
7447
+ //
7448
+ // The Inland ENC domain defines its own object classes in the 17000+ OBJL
7449
+ // range, alongside — not instead of — S-57 Appendix A. Both appear in the
7450
+ // same cell: measured on the 2026-08 USACE exchange set, `RESARE` (OBJL 112)
7451
+ // and `resare` (17005) coexist, as do `HRBFAC` (64) and `hrbfac` (17015).
7452
+ // They are genuinely different classes, and GDAL reports the inland ones in
7453
+ // lowercase, which is why the pre-2026-08-18 layer-admission rule discarded
7454
+ // them entirely.
7455
+ //
7456
+ // Until these entries existed the tiles carried them and nothing drew them:
7457
+ // locks, bridges, distance marks and notice marks were served and invisible.
7458
+ //
7459
+ // PROVENANCE OF THE SYMBOLOGY, because it differs per class and the
7460
+ // difference matters:
7461
+ //
7462
+ // • Nine inherit their S-57 cousin's treatment — a bridge is a bridge and
7463
+ // a mariner should read the same symbol whether the cell came from NOAA
7464
+ // or USACE. The inland class carries different ATTRIBUTES (clearances
7465
+ // referenced to inland water levels, for instance), but the cartographic
7466
+ // intent is identical, so diverging would be a change for its own sake.
7467
+ // Their `int1Ref` is inherited too and is honest: it points at the
7468
+ // Chart No. 1 entry a reader would look up.
7469
+ //
7470
+ // • Five have NO S-57 cousin in this atlas — `hrbare`, `sistaw`, `lokbsn`,
7471
+ // `notmrk`, `termnl`. These are styled by geometry and family so they
7472
+ // render legibly and consistently, and they deliberately carry NO
7473
+ // `int1Ref`: Chart No. 1 has no entry for them and inventing a reference
7474
+ // would be a false citation in a safety-relevant table.
7475
+ //
7476
+ // NOT YET IMPLEMENTED, and the gap is real: the Inland ECDIS Standard has
7477
+ // its own Presentation Library (CESNI Ed 2.3) with a dedicated symbol set —
7478
+ // `notmrk01/02/03` for notice marks among them, carrying the actual
7479
+ // regulatory signboard faces. These entries make the classes visible and
7480
+ // correct in kind; they do not implement that library. Notice marks in
7481
+ // particular render here as a generic mark rather than the specific sign,
7482
+ // which is a legibility floor rather than the finished portrayal.
7483
+ achare: {
7484
+ // Inherits ACHARE (S-57 4). Same faint tint + companion boundary/anchor.
7485
+ objectClass: "achare",
7486
+ name: "Anchorage Area (Inland)",
7487
+ category: "Regulated Areas",
7488
+ displayCategory: "STANDARD",
7489
+ int1Ref: "N12",
7490
+ style: { type: "fill", paint: { "fill-color": "TRFCD", "fill-opacity": 0.1 } },
7491
+ viewingGroup: 26010,
7492
+ scamin: 9e4
7493
+ },
7494
+ resare: {
7495
+ // Inherits RESARE (S-57 112) — boundary-only, no flood fill. The stacked
7496
+ // magenta wash that motivated RESARE's treatment applies here too: an
7497
+ // inland reach can carry overlapping regulated areas just as a harbour can.
7498
+ objectClass: "resare",
7499
+ name: "Restricted Area (Inland)",
7500
+ category: "Regulated Areas",
7501
+ displayCategory: "STANDARD",
7502
+ int1Ref: "N2",
7503
+ style: { type: "line", paint: { "line-color": "TRFCD", "line-width": 1.2, "line-opacity": 0.7 } },
7504
+ viewingGroup: 26010,
7505
+ scamin: 9e4
7506
+ },
7507
+ bridge: {
7508
+ // Inherits BRIDGE (S-57 11). The IENC set carries ONLY the inland class —
7509
+ // no uppercase BRIDGE appears in any of the 109 cells — so this entry is
7510
+ // the sole reason a river bridge draws at all. 3,693 features.
7511
+ objectClass: "bridge",
7512
+ name: "Bridge (Inland)",
7513
+ category: "Infrastructure",
7514
+ displayCategory: "STANDARD",
7515
+ int1Ref: "D20",
7516
+ style: { type: "line", paint: { "line-color": "CHGRD", "line-width": 2 } },
7517
+ hasConditional: true,
7518
+ conditionalAttributes: ["CATBRG", "VERCLR"],
7519
+ viewingGroup: 12210,
7520
+ scamin: 22e3
7521
+ },
7522
+ hrbfac: {
7523
+ // Inherits HRBFAC (S-57 64).
7524
+ objectClass: "hrbfac",
7525
+ name: "Harbour Facility (Inland)",
7526
+ category: "Infrastructure",
7527
+ displayCategory: "OTHER",
7528
+ int1Ref: "F10",
7529
+ style: { type: "circle", paint: { "circle-color": "TRFCD", "circle-radius": 4, "circle-stroke-color": "CHBLK", "circle-stroke-width": 0.5 } },
7530
+ viewingGroup: 12410,
7531
+ scamin: 22e3
7532
+ },
7533
+ berths: {
7534
+ // Inherits BERTHS (S-57 10).
7535
+ objectClass: "berths",
7536
+ name: "Berth (Inland)",
7537
+ category: "Infrastructure",
7538
+ displayCategory: "OTHER",
7539
+ int1Ref: "F19",
7540
+ style: { type: "line", paint: { "line-color": "CHBLK", "line-width": 1.5 } },
7541
+ viewingGroup: 12410,
7542
+ scamin: 22e3
7543
+ },
7544
+ cblohd: {
7545
+ // Inherits CBLOHD (S-57 21). Overhead cables are a clearance hazard on a
7546
+ // river in a way they rarely are offshore — 1,631 features across the set.
7547
+ objectClass: "cblohd",
7548
+ name: "Overhead Cable (Inland)",
7549
+ category: "Infrastructure",
7550
+ displayCategory: "STANDARD",
7551
+ int1Ref: "L31",
7552
+ style: { type: "line", paint: { "line-color": "CHMGD", "line-width": 1 } },
7553
+ hasConditional: true,
7554
+ conditionalAttributes: ["CATCBL", "VERCLR", "STATUS"],
7555
+ viewingGroup: 24070,
7556
+ scamin: 9e4
7557
+ },
7558
+ dismar: {
7559
+ // Inherits DISMAR (S-57 44). River mile markers — the single most numerous
7560
+ // inland class at 7,453 features, and the primary way position is
7561
+ // communicated on the inland system.
7562
+ objectClass: "dismar",
7563
+ name: "Distance Mark (Inland)",
7564
+ category: "Navigation Aids",
7565
+ displayCategory: "OTHER",
7566
+ int1Ref: "B25",
7567
+ style: { type: "circle", paint: { "circle-color": "CHBLK", "circle-radius": 3, "circle-stroke-width": 0 } },
7568
+ viewingGroup: 27010,
7569
+ scamin: 22e3
7570
+ },
7571
+ sistat: {
7572
+ // Inherits SISTAT (S-57 90), including its symbol.
7573
+ objectClass: "sistat",
7574
+ name: "Signal Station, Traffic (Inland)",
7575
+ category: "Infrastructure",
7576
+ displayCategory: "OTHER",
7577
+ int1Ref: "T20",
7578
+ style: { type: "symbol", paint: {}, layout: { "icon-size": 0.6, "icon-allow-overlap": true }, iconImage: "SISTAT02" },
7579
+ viewingGroup: 27080,
7580
+ scamin: 22e3
7581
+ },
7582
+ m_nsys: {
7583
+ // Inherits M_NSYS (S-57 306) — a metadata class, never drawn, present so
7584
+ // click-query can report which buoyage system applies.
7585
+ objectClass: "m_nsys",
7586
+ name: "Nav System of Marks (Inland)",
7587
+ category: "Meta",
7588
+ displayCategory: "META",
7589
+ style: { type: "fill", paint: { "fill-color": "CHGRF", "fill-opacity": 0 } },
7590
+ auditIgnore: true
7591
+ },
7592
+ // ── No S-57 cousin: styled by family, no INT1 reference ──
7593
+ sistaw: {
7594
+ // Warning signal station. S-57 has SISTAW (91) but this atlas never needed
7595
+ // it, so there is nothing to inherit. Styled as its traffic sibling with a
7596
+ // hazard-magenta ring, because a warning station is the one a mariner most
7597
+ // needs to notice. 522 features.
7598
+ objectClass: "sistaw",
7599
+ name: "Signal Station, Warning (Inland)",
7600
+ category: "Infrastructure",
7601
+ displayCategory: "OTHER",
7602
+ style: { type: "circle", paint: { "circle-color": "CHWHT", "circle-radius": 3.5, "circle-stroke-color": "CHMGD", "circle-stroke-width": 1.2 } },
7603
+ viewingGroup: 27080,
7604
+ scamin: 22e3
7605
+ },
7606
+ lokbsn: {
7607
+ // Lock basin — the chamber itself. Drawn as a water-toned area with a firm
7608
+ // outline: it IS navigable water, so a hazard tint would be wrong, but its
7609
+ // edges are hard structures a mariner must not drift into. 174 features
7610
+ // across the set and the defining feature of the inland system.
7611
+ objectClass: "lokbsn",
7612
+ name: "Lock Basin (Inland)",
7613
+ category: "Infrastructure",
7614
+ displayCategory: "STANDARD",
7615
+ style: { type: "fill", paint: { "fill-color": "DEPVS", "fill-opacity": 0.35, "fill-outline-color": "CHGRD" } },
7616
+ viewingGroup: 12210,
7617
+ scamin: 45e3
7618
+ },
7619
+ hrbare: {
7620
+ // Harbour area. Inland ports are areas rather than the point facilities
7621
+ // NOAA models, so this is an area at the same low tint as other
7622
+ // infrastructure regions. Only 1 feature set-wide — rare, but it renders.
7623
+ objectClass: "hrbare",
7624
+ name: "Harbour Area (Inland)",
7625
+ category: "Infrastructure",
7626
+ displayCategory: "OTHER",
7627
+ style: { type: "fill", paint: { "fill-color": "TRFCD", "fill-opacity": 0.08 } },
7628
+ viewingGroup: 12410,
7629
+ scamin: 22e3
7630
+ },
7631
+ termnl: {
7632
+ // Terminal — a commercial loading facility. Same family as harbour
7633
+ // facility, distinguished by a square-ish footprint rather than a point.
7634
+ // 59 features.
7635
+ objectClass: "termnl",
7636
+ name: "Terminal (Inland)",
7637
+ category: "Infrastructure",
7638
+ displayCategory: "OTHER",
7639
+ style: { type: "fill", paint: { "fill-color": "TRFCD", "fill-opacity": 0.12, "fill-outline-color": "CHBLK" } },
7640
+ viewingGroup: 12410,
7641
+ scamin: 22e3
7642
+ },
7643
+ notmrk: {
7644
+ // Notice mark — the regulatory signboards along an inland waterway. These
7645
+ // carry real instructions (speed limits, no-wake, keep-out, overhead
7646
+ // clearance), which makes this the class most under-served by a generic
7647
+ // symbol.
7648
+ //
7649
+ // The Inland ECDIS Presentation Library defines `notmrk01/02/03` with the
7650
+ // actual sign faces, and implementing that set is the right end state.
7651
+ // Until then a magenta-ringed mark says "regulatory information here" and
7652
+ // click-query carries the attributes, which is a legibility floor rather
7653
+ // than the finished portrayal. 266 features.
7654
+ objectClass: "notmrk",
7655
+ name: "Notice Mark (Inland)",
7656
+ category: "Regulated Areas",
7657
+ displayCategory: "STANDARD",
7658
+ style: { type: "circle", paint: { "circle-color": "CHMGD", "circle-radius": 3, "circle-stroke-color": "CHWHT", "circle-stroke-width": 1 } },
7659
+ viewingGroup: 26010,
7660
+ scamin: 45e3
7419
7661
  }
7420
7662
  };
7421
7663
 
@@ -7428,6 +7670,7 @@ var S52_DRAW_ORDER = [
7428
7670
  "M_NSYS",
7429
7671
  "M_QUAL",
7430
7672
  "M_SREL",
7673
+ "m_nsys",
7431
7674
  // 2. Depth areas – broadest first (fill).
7432
7675
  //
7433
7676
  // SEAARE goes BEFORE DEPARE so its uniform "deep water" base fill paints
@@ -7485,6 +7728,12 @@ var S52_DRAW_ORDER = [
7485
7728
  "PRDARE",
7486
7729
  "OSPARE",
7487
7730
  "LOGPON",
7731
+ // Inland ENC regulated areas — beside their S-57 cousins so an inland
7732
+ // restricted area paints in the same band as a coastal one.
7733
+ "achare",
7734
+ "resare",
7735
+ "hrbare",
7736
+ "termnl",
7488
7737
  "CBLARE",
7489
7738
  "PIPARE",
7490
7739
  // 6a. Jurisdictional zones + IMO routing measures (boundary-only via
@@ -7506,6 +7755,9 @@ var S52_DRAW_ORDER = [
7506
7755
  "BRIDGE",
7507
7756
  "PYLONS",
7508
7757
  "FORSTC",
7758
+ // Lock basin is navigable water inside a structure — painted with the
7759
+ // infrastructure areas so its hard edges sit above the land fill.
7760
+ "lokbsn",
7509
7761
  // 8. Coastline / shore (line)
7510
7762
  // DYKCON (dyke) and CAUSWY (causeway) join the SLCONS family — solid
7511
7763
  // black lines representing man-made shoreline modifications. Drawn in
@@ -7545,6 +7797,9 @@ var S52_DRAW_ORDER = [
7545
7797
  "GATCON",
7546
7798
  "DAMCON",
7547
7799
  "BERTHS",
7800
+ "cblohd",
7801
+ "berths",
7802
+ "bridge",
7548
7803
  // 11. Point features
7549
7804
  // CGUSTA (coast guard station, emergency-context) sits with the building
7550
7805
  // / landmark family — a structural-presence cue rather than a routing
@@ -7565,6 +7820,8 @@ var S52_DRAW_ORDER = [
7565
7820
  "DISMAR",
7566
7821
  "ACHBRT",
7567
7822
  "CGUSTA",
7823
+ "hrbfac",
7824
+ "dismar",
7568
7825
  "NEWOBJ",
7569
7826
  // 12. Nav aids (on top)
7570
7827
  "FOGSIG",
@@ -7574,6 +7831,11 @@ var S52_DRAW_ORDER = [
7574
7831
  "PILBOP",
7575
7832
  "DAYMAR",
7576
7833
  "TOPMAR",
7834
+ // Notice marks last of the point family: they carry regulatory
7835
+ // instructions and should not be occluded by nearby infrastructure.
7836
+ "sistat",
7837
+ "sistaw",
7838
+ "notmrk",
7577
7839
  "BCNSPP",
7578
7840
  "BCNCAR",
7579
7841
  "BCNISD",
@@ -26538,6 +26800,11 @@ var BUILD_MINZOOM = 0;
26538
26800
  var BUILD_MAXZOOM = 14;
26539
26801
 
26540
26802
  // src/atlas/source-registry.ts
26803
+ var LAND_REGION = "CHRT_LAND";
26804
+ var RELIEF_REGION = "CHRT_RELIEF";
26805
+ var RELIEF_MINZOOM = 8;
26806
+ var RELIEF_MAXZOOM = 13;
26807
+ var RELIEF_TILE_SIZE = 512;
26541
26808
  var DEFAULT_TILE_BASE_URL2 = "https://tiles.chrt.co";
26542
26809
  var DEFAULT_REGION = "NOAA_ENC";
26543
26810
  function buildSources(opts = {}) {
@@ -26552,7 +26819,7 @@ function buildSources(opts = {}) {
26552
26819
  };
26553
26820
  }
26554
26821
  const base = (opts.tileBaseUrl ?? DEFAULT_TILE_BASE_URL2).replace(/\/+$/, "");
26555
- return {
26822
+ const sources = {
26556
26823
  [region]: {
26557
26824
  type: "vector",
26558
26825
  tiles: [`${base}/v1/tiles/${region}/{z}/{x}/{y}.mvt`],
@@ -26560,6 +26827,25 @@ function buildSources(opts = {}) {
26560
26827
  maxzoom: BUILD_MAXZOOM
26561
26828
  }
26562
26829
  };
26830
+ if (opts.land) {
26831
+ sources[LAND_REGION] = {
26832
+ type: "vector",
26833
+ tiles: [`${base}/v1/tiles/${LAND_REGION}/{z}/{x}/{y}.mvt`],
26834
+ minzoom: BUILD_MINZOOM,
26835
+ maxzoom: BUILD_MAXZOOM
26836
+ };
26837
+ }
26838
+ if (opts.relief) {
26839
+ sources[RELIEF_REGION] = {
26840
+ type: "raster-dem",
26841
+ tiles: [`${base}/v1/tiles/${RELIEF_REGION}/{z}/{x}/{y}.png`],
26842
+ encoding: "terrarium",
26843
+ tileSize: RELIEF_TILE_SIZE,
26844
+ minzoom: RELIEF_MINZOOM,
26845
+ maxzoom: RELIEF_MAXZOOM
26846
+ };
26847
+ }
26848
+ return sources;
26563
26849
  }
26564
26850
 
26565
26851
  // src/atlas/layer-builder.ts
@@ -27797,6 +28083,209 @@ function getOrderedClasses() {
27797
28083
  return ordered;
27798
28084
  }
27799
28085
 
28086
+ // src/atlas/land-layers.ts
28087
+ var LAND_THEMES = {
28088
+ DAY: {
28089
+ base: "#e8d9b0",
28090
+ cover: { wood: "#cfd8b4", wetland: "#c9d3c4", urban: "#dedad2", sand: "#efe4c2" },
28091
+ contour: "#a89878",
28092
+ contourIndex: "#8a7a5c",
28093
+ road: "#d9c9a4",
28094
+ roadMajor: "#d2bd8e",
28095
+ label: "#5b4f3a",
28096
+ halo: "#f2ead6",
28097
+ shadow: "#6b5a3e",
28098
+ highlight: "#fffaf0"
28099
+ },
28100
+ DUSK: {
28101
+ base: "#4a4433",
28102
+ cover: { wood: "#414a38", wetland: "#3f4740", urban: "#4e4a42", sand: "#544c39" },
28103
+ contour: "#6b6250",
28104
+ contourIndex: "#837961",
28105
+ road: "#565040",
28106
+ roadMajor: "#63594440",
28107
+ label: "#c3b898",
28108
+ halo: "#2b2820",
28109
+ shadow: "#2e2a20",
28110
+ highlight: "#8a8068"
28111
+ },
28112
+ NIGHT: {
28113
+ base: "#2a2620",
28114
+ cover: { wood: "#242a20", wetland: "#232823", urban: "#2c2925", sand: "#302b22" },
28115
+ contour: "#413b30",
28116
+ contourIndex: "#524a3b",
28117
+ road: "#332f27",
28118
+ roadMajor: "#3d382d",
28119
+ label: "#7d745e",
28120
+ halo: "#15130f",
28121
+ shadow: "#1a1712",
28122
+ highlight: "#4a4436"
28123
+ }
28124
+ };
28125
+ var FITS = ["<=", ["coalesce", ["get", "min_z"], 0], ["zoom"]];
28126
+ var ANCHOR = ["coalesce", ["get", "anchor"], "top"];
28127
+ var anchorOffset = (em) => [
28128
+ "match",
28129
+ ["get", "anchor"],
28130
+ "left",
28131
+ ["literal", [0.6, 0]],
28132
+ "right",
28133
+ ["literal", [-0.6, 0]],
28134
+ "bottom",
28135
+ ["literal", [0, -em]],
28136
+ ["literal", [0, em]]
28137
+ ];
28138
+ var FONT = ["Open Sans Regular"];
28139
+ var FONT_ITALIC = ["Open Sans Italic"];
28140
+ function landLayers(opts = {}) {
28141
+ const palette = opts.palette ?? "DAY";
28142
+ const T = LAND_THEMES[palette] ?? LAND_THEMES.DAY;
28143
+ const src = opts.sourceId ?? LAND_REGION;
28144
+ const demSrc = opts.reliefSourceId ?? RELIEF_REGION;
28145
+ const L = (o) => o;
28146
+ const layers = [
28147
+ // 1. The land itself — LNDARE ∩ coverage, so it can never outrun the chart.
28148
+ L({
28149
+ id: "chrtland-base",
28150
+ type: "fill",
28151
+ source: src,
28152
+ "source-layer": "land_base",
28153
+ paint: { "fill-color": T.base }
28154
+ })
28155
+ ];
28156
+ layers.push(L({
28157
+ id: "chrtland-cover",
28158
+ type: "fill",
28159
+ source: src,
28160
+ "source-layer": "land_cover",
28161
+ filter: ["<=", ["coalesce", ["get", "min_z"], 0], ["zoom"]],
28162
+ paint: {
28163
+ "fill-color": [
28164
+ "match",
28165
+ ["get", "class"],
28166
+ "wood",
28167
+ T.cover.wood,
28168
+ "wetland",
28169
+ T.cover.wetland,
28170
+ "urban",
28171
+ T.cover.urban,
28172
+ "sand",
28173
+ T.cover.sand,
28174
+ T.base
28175
+ ],
28176
+ "fill-opacity": 0.85
28177
+ }
28178
+ }));
28179
+ if (opts.relief) {
28180
+ layers.push(L({
28181
+ id: "chrtland-hillshade",
28182
+ type: "hillshade",
28183
+ source: demSrc,
28184
+ paint: {
28185
+ "hillshade-exaggeration": 0.45,
28186
+ "hillshade-shadow-color": T.shadow,
28187
+ "hillshade-highlight-color": T.highlight,
28188
+ "hillshade-accent-color": T.shadow
28189
+ }
28190
+ }));
28191
+ }
28192
+ layers.push(L({
28193
+ id: "chrtland-contour",
28194
+ type: "line",
28195
+ source: src,
28196
+ "source-layer": "land_contour",
28197
+ paint: {
28198
+ "line-color": ["case", ["==", ["get", "is_index"], 1], T.contourIndex, T.contour],
28199
+ "line-width": ["case", ["==", ["get", "is_index"], 1], 0.8, 0.4],
28200
+ "line-opacity": 0.7
28201
+ }
28202
+ }));
28203
+ layers.push(L({
28204
+ id: "chrtland-road",
28205
+ type: "line",
28206
+ source: src,
28207
+ "source-layer": "land_road",
28208
+ minzoom: 10,
28209
+ paint: {
28210
+ "line-color": ["match", ["get", "kind"], "highway", T.roadMajor, T.road],
28211
+ "line-width": [
28212
+ "interpolate",
28213
+ ["linear"],
28214
+ ["zoom"],
28215
+ 10,
28216
+ ["match", ["get", "kind"], "highway", 0.8, 0.4],
28217
+ 14,
28218
+ ["match", ["get", "kind"], "highway", 2.4, 1.2]
28219
+ ],
28220
+ "line-opacity": 0.8
28221
+ }
28222
+ }));
28223
+ layers.push(L({
28224
+ id: "chrtland-town",
28225
+ type: "symbol",
28226
+ source: src,
28227
+ "source-layer": "land_town",
28228
+ minzoom: 9,
28229
+ filter: FITS,
28230
+ layout: {
28231
+ "text-field": [
28232
+ "case",
28233
+ ["==", ["get", "upcase"], true],
28234
+ ["upcase", ["get", "name"]],
28235
+ ["get", "name"]
28236
+ ],
28237
+ "text-font": FONT,
28238
+ "text-size": ["interpolate", ["linear"], ["zoom"], 9, 10, 14, 13.5],
28239
+ "text-letter-spacing": ["case", ["==", ["get", "upcase"], true], 0.14, 0],
28240
+ "text-offset": anchorOffset(0.75),
28241
+ "text-anchor": ANCHOR,
28242
+ "text-padding": 6,
28243
+ "text-allow-overlap": false,
28244
+ "symbol-sort-key": 2
28245
+ },
28246
+ paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.2 }
28247
+ }));
28248
+ layers.push(L({
28249
+ id: "chrtland-summit",
28250
+ type: "symbol",
28251
+ source: src,
28252
+ "source-layer": "land_summit",
28253
+ minzoom: 11.5,
28254
+ filter: ["all", FITS, [">=", ["get", "peak_m"], 40]],
28255
+ layout: {
28256
+ "text-field": ["concat", ["get", "name"], "\n", ["to-string", ["get", "peak_m"]], " m"],
28257
+ "text-font": FONT,
28258
+ "text-size": ["interpolate", ["linear"], ["zoom"], 11.5, 8.5, 14, 11],
28259
+ "text-offset": anchorOffset(0.8),
28260
+ "text-anchor": ANCHOR,
28261
+ "text-padding": 5,
28262
+ "text-allow-overlap": false,
28263
+ "symbol-sort-key": 3
28264
+ },
28265
+ paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.2 }
28266
+ }));
28267
+ layers.push(L({
28268
+ id: "chrtland-poi",
28269
+ type: "symbol",
28270
+ source: src,
28271
+ "source-layer": "land_poi",
28272
+ minzoom: 13,
28273
+ filter: FITS,
28274
+ layout: {
28275
+ "text-field": ["get", "name"],
28276
+ "text-font": FONT_ITALIC,
28277
+ "text-size": ["interpolate", ["linear"], ["zoom"], 13, 8, 14, 9.5],
28278
+ "text-offset": anchorOffset(0.7),
28279
+ "text-anchor": ANCHOR,
28280
+ "text-padding": 5,
28281
+ "text-allow-overlap": false,
28282
+ "symbol-sort-key": 5
28283
+ },
28284
+ paint: { "text-color": T.label, "text-halo-color": T.halo, "text-halo-width": 1.2 }
28285
+ }));
28286
+ return layers;
28287
+ }
28288
+
27800
28289
  // src/atlas/band-priority.ts
27801
28290
  var BAND_PRIORITY_RANGES = {
27802
28291
  "1": { minzoom: 0 },
@@ -28260,6 +28749,12 @@ function generateStyle(palette = "DAY", opts = {}) {
28260
28749
  paint: { "background-color": resolveColor("NODTA", palette) }
28261
28750
  };
28262
28751
  const layers = [backgroundLayer];
28752
+ if (opts.land) {
28753
+ layers.push(...landLayers({
28754
+ palette,
28755
+ ...opts.relief ? { relief: true } : {}
28756
+ }));
28757
+ }
28263
28758
  const labelLayers = [];
28264
28759
  const chartCascade = opts.chartMode ? CHART_CASCADE_CODES[opts.chartMode.band] : null;
28265
28760
  const chartRequestedCodes = opts.chartMode ? CHART_BAND_CODES[opts.chartMode.band] : null;
@@ -28393,6 +28888,8 @@ function generateStyle(palette = "DAY", opts = {}) {
28393
28888
  "chrt:color-table": palette,
28394
28889
  "chrt:generated-at": (/* @__PURE__ */ new Date()).toISOString(),
28395
28890
  "chrt:layer-count": layers.length,
28891
+ "chrt:land": Boolean(opts.land),
28892
+ "chrt:relief": Boolean(opts.relief),
28396
28893
  "chrt:render-mode": opts.chartMode ? `chart:${opts.chartMode.band}` : "continuous",
28397
28894
  "chrt:description": "IHO S-52 portrayal rules for Electronic Navigational Charts (ENCs). Generated from the CHRT atlas engine."
28398
28895
  },
@@ -29234,6 +29731,7 @@ var Chrt = class {
29234
29731
  LABEL_BEHAVIOR,
29235
29732
  LABEL_LAYER_GROUPS,
29236
29733
  LABEL_PRIORITY,
29734
+ LAND_REGION,
29237
29735
  NAUTICAL_SYMBOLS,
29238
29736
  NAV_AID_LAYER_GROUPS,
29239
29737
  NAV_AID_QUERY_LAYERS,
@@ -29244,6 +29742,7 @@ var Chrt = class {
29244
29742
  RASTER_ATON_CLASSES,
29245
29743
  RASTER_GLYPH_NAMES,
29246
29744
  RASTER_ICON_PREFIX,
29745
+ RELIEF_REGION,
29247
29746
  S101_AREA_FILL_NAMES,
29248
29747
  S101_CHART_SECTIONS,
29249
29748
  S101_FEATURES,