@tileflow/core 0.1.0-alpha.6 → 0.1.0-alpha.8

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.js CHANGED
@@ -1,130 +1,13 @@
1
1
  // src/basemaps/osm.ts
2
+ var defaultOsmBrightSprite = "https://openmaptiles.github.io/osm-bright-gl-style/sprite";
2
3
  function osm(options = {}) {
3
4
  return {
4
5
  type: "osm",
6
+ sprite: defaultOsmBrightSprite,
5
7
  ...options
6
8
  };
7
9
  }
8
10
 
9
- // src/modules/roads/index.ts
10
- function roads(options = {}) {
11
- return {
12
- type: "roads",
13
- preset: options.preset ?? "modern",
14
- ...options.density ? { density: options.density } : {}
15
- };
16
- }
17
- function resolveRoads(legacyRoads, moduleConfig, fallback) {
18
- if (!moduleConfig) {
19
- return legacyRoads ?? fallback;
20
- }
21
- if (moduleConfig.preset === "minimal" || moduleConfig.density === "clean") {
22
- return "soft";
23
- }
24
- if (moduleConfig.density === "dense") {
25
- return "detailed";
26
- }
27
- return "standard";
28
- }
29
- function roadLayers(source, sourceLayer, colors, roadsMode) {
30
- if (roadsMode === "hidden") {
31
- return [];
32
- }
33
- const opacity = roadsMode === "soft" ? 0.72 : roadsMode === "standard" ? 0.84 : 0.96;
34
- const casingOpacity = roadsMode === "detailed" ? 0.3 : 0.16;
35
- const majorRoadFilter = [
36
- "match",
37
- ["get", "class"],
38
- ["motorway", "trunk", "primary", "secondary"],
39
- true,
40
- false
41
- ];
42
- const minorRoadFilter = ["!", majorRoadFilter];
43
- const nonTunnelRoadFilter = ["!=", ["get", "brunnel"], "tunnel"];
44
- return [
45
- {
46
- id: "roads-tunnels-casing",
47
- type: "line",
48
- source,
49
- "source-layer": sourceLayer,
50
- minzoom: 10,
51
- filter: ["==", ["get", "brunnel"], "tunnel"],
52
- paint: {
53
- "line-color": colors.roads.tunnel,
54
- "line-dasharray": [0.6, 0.8],
55
- "line-opacity": 0.28,
56
- "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.5, 14, 1.7, 16, 3.8]
57
- }
58
- },
59
- {
60
- id: "roads-tunnels",
61
- type: "line",
62
- source,
63
- "source-layer": sourceLayer,
64
- minzoom: 10,
65
- filter: ["==", ["get", "brunnel"], "tunnel"],
66
- paint: {
67
- "line-color": colors.roads.minor,
68
- "line-dasharray": [0.6, 0.8],
69
- "line-opacity": 0.2,
70
- "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.25, 14, 1, 16, 2.4]
71
- }
72
- },
73
- {
74
- id: "roads-casing",
75
- type: "line",
76
- source,
77
- "source-layer": sourceLayer,
78
- minzoom: 6,
79
- filter: nonTunnelRoadFilter,
80
- paint: {
81
- "line-color": colors.roads.casing,
82
- "line-opacity": casingOpacity,
83
- "line-width": ["interpolate", ["linear"], ["zoom"], 6, 0.4, 12, 1.2, 16, 4]
84
- }
85
- },
86
- {
87
- id: "roads-minor",
88
- type: "line",
89
- source,
90
- "source-layer": sourceLayer,
91
- minzoom: 6,
92
- filter: ["all", nonTunnelRoadFilter, minorRoadFilter],
93
- paint: {
94
- "line-color": colors.roads.minor,
95
- "line-opacity": opacity * 0.88,
96
- "line-width": ["interpolate", ["linear"], ["zoom"], 6, 0.2, 12, 0.8, 16, 3]
97
- }
98
- },
99
- {
100
- id: "roads-major",
101
- type: "line",
102
- source,
103
- "source-layer": sourceLayer,
104
- minzoom: 5,
105
- filter: ["all", nonTunnelRoadFilter, majorRoadFilter],
106
- paint: {
107
- "line-color": colors.roads.primary,
108
- "line-opacity": opacity,
109
- "line-width": ["interpolate", ["linear"], ["zoom"], 5, 0.35, 12, 1.2, 16, 4]
110
- }
111
- },
112
- {
113
- id: "roads-bridges",
114
- type: "line",
115
- source,
116
- "source-layer": sourceLayer,
117
- minzoom: 10,
118
- filter: ["==", ["get", "brunnel"], "bridge"],
119
- paint: {
120
- "line-color": colors.roads.bridge,
121
- "line-opacity": opacity,
122
- "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.45, 14, 1.6, 16, 4.4]
123
- }
124
- }
125
- ];
126
- }
127
-
128
11
  // src/themes/index.ts
129
12
  var defaultColors = {
130
13
  background: "#F6F7F3",
@@ -266,7 +149,8 @@ function resolveColors(overrides = {}, defaults = defaultColors, moduleOverrides
266
149
  const defaultModules = mergeThemeModules(extractLegacyModuleColors(defaults), moduleDefaults);
267
150
  const overrideModules = mergeThemeModules(extractLegacyModuleColors(overrides), moduleOverrides);
268
151
  const base = resolveBaseColors(overrideInput, defaultInput);
269
- const roadCasing = overrideModules.roads?.casing ?? defaultModules.roads?.casing ?? overrideInput.roadCasing ?? defaultInput.roadCasing ?? mix(base.road, base.text, 0.2);
152
+ const baseRoadCasing = overrideInput.roadCasing ?? defaultInput.roadCasing ?? mix(base.road, base.text, 0.2);
153
+ const roadCasing = overrideModules.roads?.casing ?? defaultModules.roads?.casing ?? baseRoadCasing;
270
154
  const baseResolved = {
271
155
  ...base,
272
156
  canvas: base.background,
@@ -286,18 +170,33 @@ function resolveColors(overrides = {}, defaults = defaultColors, moduleOverrides
286
170
  rail: base.textMuted,
287
171
  secondary: base.roadMajor,
288
172
  trunk: base.roadMajor,
289
- tunnel: mix(roadCasing, base.background, 0.35)
173
+ tunnel: mix(baseRoadCasing, base.background, 0.35)
290
174
  };
291
175
  const labelDefaults = {
292
176
  country: base.text,
293
177
  halo: base.textHalo,
294
178
  muted: base.textMuted,
179
+ neighborhood: base.textMuted,
295
180
  poi: base.textMuted,
296
181
  primary: base.text,
297
182
  road: base.textMuted,
298
183
  settlement: base.text,
299
184
  water: mix(base.water, base.text, 0.42)
300
185
  };
186
+ const poiDefaults = {
187
+ coffee: mix(base.textMuted, base.roadMajor, 0.24),
188
+ culture: mix(base.textMuted, base.text, 0.18),
189
+ education: mix(base.textMuted, base.water, 0.18),
190
+ food: mix(base.textMuted, base.roadMajor, 0.34),
191
+ halo: labelDefaults.halo,
192
+ health: mix(base.textMuted, "#D45E5E", 0.22),
193
+ icon: labelDefaults.poi,
194
+ label: labelDefaults.poi,
195
+ lodging: mix(base.textMuted, base.water, 0.24),
196
+ services: labelDefaults.poi,
197
+ shopping: mix(base.textMuted, base.park, 0.2),
198
+ transit: labelDefaults.road
199
+ };
301
200
  const landuseDefaults = {
302
201
  cemetery: mix(base.park, base.land, 0.35),
303
202
  civic: mix(base.land, base.textMuted, 0.08),
@@ -348,6 +247,7 @@ function resolveColors(overrides = {}, defaults = defaultColors, moduleOverrides
348
247
  landuse: mapColorRecord(
349
248
  resolveGroup(landuseDefaults, defaultModules.landuse, overrideModules.landuse)
350
249
  ),
250
+ poi: mapColorRecord(resolveGroup(poiDefaults, defaultModules.poi, overrideModules.poi)),
351
251
  roads: mapColorRecord(resolveGroup(roadDefaults, defaultModules.roads, overrideModules.roads))
352
252
  };
353
253
  }
@@ -501,6 +401,7 @@ function stripLegacyModuleColors(colors) {
501
401
  labels: _labels,
502
402
  landcover: _landcover,
503
403
  landuse: _landuse,
404
+ poi: _poi,
504
405
  roads: _roads,
505
406
  ...baseColors
506
407
  } = colors;
@@ -517,6 +418,7 @@ function extractLegacyModuleColors(colors) {
517
418
  ...colors.labels ? { labels: colors.labels } : {},
518
419
  ...colors.landcover ? { landcover: colors.landcover } : {},
519
420
  ...colors.landuse ? { landuse: colors.landuse } : {},
421
+ ...colors.poi ? { poi: colors.poi } : {},
520
422
  ...colors.roads ? { roads: colors.roads } : {}
521
423
  };
522
424
  }
@@ -528,6 +430,7 @@ function mergeThemeModules(defaults = {}, overrides = {}) {
528
430
  ...mergeThemeModuleGroup("labels", defaults, overrides),
529
431
  ...mergeThemeModuleGroup("landcover", defaults, overrides),
530
432
  ...mergeThemeModuleGroup("landuse", defaults, overrides),
433
+ ...mergeThemeModuleGroup("poi", defaults, overrides),
531
434
  ...mergeThemeModuleGroup("roads", defaults, overrides)
532
435
  };
533
436
  }
@@ -611,6 +514,437 @@ function rgbToHex(r, g, b) {
611
514
  return `#${[r, g, b].map((value) => value.toString(16).padStart(2, "0")).join("")}`;
612
515
  }
613
516
 
517
+ // src/modules/labels/index.ts
518
+ function labels(options = {}) {
519
+ return {
520
+ type: "labels",
521
+ ...options.language ? { language: options.language } : {},
522
+ ...options.places ? { places: options.places } : {},
523
+ ...options.preset ? { preset: options.preset } : {},
524
+ ...options.roads ? { roads: options.roads } : {},
525
+ ...options.water ? { water: options.water } : {}
526
+ };
527
+ }
528
+ function resolveLabels(legacyLabels, moduleConfig, fallback) {
529
+ const mode = moduleConfig?.preset ?? legacyLabels ?? fallback;
530
+ const density = labelDensityFromMode(mode);
531
+ return {
532
+ language: moduleConfig?.language ?? "auto",
533
+ mode,
534
+ places: moduleConfig?.places ?? density,
535
+ roads: moduleConfig?.roads ?? density,
536
+ water: moduleConfig?.water ?? density
537
+ };
538
+ }
539
+ function labelLayers(source, sourceLayer, colors, labelsConfig, typography) {
540
+ if (labelsConfig.mode === "none" || labelsConfig.places === "none") {
541
+ return [];
542
+ }
543
+ const maxRank = labelsConfig.places === "minimal" ? 3 : labelsConfig.places === "balanced" ? 7 : 12;
544
+ return [
545
+ {
546
+ id: "place-labels",
547
+ type: "symbol",
548
+ source,
549
+ "source-layer": sourceLayer,
550
+ minzoom: 3,
551
+ filter: ["<=", ["coalesce", ["get", "rank"], 10], maxRank],
552
+ layout: {
553
+ "text-field": labelTextField(labelsConfig.language),
554
+ "text-font": textFont(typography),
555
+ "text-size": ["interpolate", ["linear"], ["zoom"], 3, 10, 8, 13, 14, 17],
556
+ "text-letter-spacing": 0,
557
+ "text-padding": 4
558
+ },
559
+ paint: {
560
+ "text-color": colors.labels.primary,
561
+ "text-halo-color": colors.labels.halo,
562
+ "text-halo-width": 1.2
563
+ }
564
+ }
565
+ ];
566
+ }
567
+ function labelDensityFromMode(mode) {
568
+ if (mode === "none") return "none";
569
+ if (mode === "essential") return "minimal";
570
+ if (mode === "full") return "detailed";
571
+ return "balanced";
572
+ }
573
+ function labelTextField(language) {
574
+ if (language === "local") {
575
+ return ["coalesce", ["get", "name"], ["get", "name:latin"], ["get", "name:en"]];
576
+ }
577
+ if (language === "auto") {
578
+ return ["coalesce", ["get", "name:latin"], ["get", "name"], ["get", "name:en"]];
579
+ }
580
+ return ["coalesce", ["get", `name:${language}`], ["get", "name:latin"], ["get", "name"]];
581
+ }
582
+
583
+ // src/modules/poi/index.ts
584
+ var defaultPoiMinZoom = {
585
+ none: 24,
586
+ minimal: 15,
587
+ balanced: 13,
588
+ full: 11
589
+ };
590
+ function poi(options = {}) {
591
+ return {
592
+ type: "poi",
593
+ ...options.categories ? { categories: options.categories } : {},
594
+ ...options.icons !== void 0 ? { icons: options.icons } : {},
595
+ ...options.labels ? { labels: options.labels } : {},
596
+ ...options.minZoom !== void 0 ? { minZoom: options.minZoom } : {},
597
+ ...options.preset ? { preset: options.preset } : {}
598
+ };
599
+ }
600
+ function resolvePoi(legacyPoi, moduleConfig, fallback) {
601
+ const mode = moduleConfig?.preset ?? legacyPoi ?? fallback;
602
+ return {
603
+ categories: moduleConfig?.categories,
604
+ icons: moduleConfig?.icons ?? (mode === "none" ? false : "essential"),
605
+ labels: moduleConfig?.labels ?? poiLabelsFromMode(mode),
606
+ minZoom: moduleConfig?.minZoom ?? defaultPoiMinZoom[mode],
607
+ mode
608
+ };
609
+ }
610
+ function poiLayers(source, sourceLayer, colors, poiConfig, typography, customIconSpriteId2, customIconMapping) {
611
+ if (poiConfig.mode === "none") {
612
+ return [];
613
+ }
614
+ const layout = {
615
+ "text-field": labelTextField("auto"),
616
+ "text-font": textFont(typography),
617
+ "text-size": ["interpolate", ["linear"], ["zoom"], poiConfig.minZoom, 10, 17, 12],
618
+ "text-letter-spacing": 0,
619
+ "text-padding": 2
620
+ };
621
+ if (poiConfig.labels === "none") {
622
+ delete layout["text-field"];
623
+ }
624
+ if (poiConfig.icons) {
625
+ layout["icon-image"] = poiIconImageExpression(customIconSpriteId2, customIconMapping);
626
+ layout["icon-optional"] = true;
627
+ layout["icon-padding"] = 2;
628
+ layout["icon-size"] = ["interpolate", ["linear"], ["zoom"], poiConfig.minZoom, 0.85, 17, 1];
629
+ layout["text-anchor"] = "top";
630
+ layout["text-offset"] = [0, 0.75];
631
+ }
632
+ return [
633
+ {
634
+ id: "poi-labels",
635
+ type: "symbol",
636
+ source,
637
+ "source-layer": sourceLayer,
638
+ minzoom: poiConfig.minZoom,
639
+ ...poiConfig.categories ? { filter: poiCategoryFilter(poiConfig.categories) } : {},
640
+ layout,
641
+ paint: {
642
+ "text-color": colors.poi.label,
643
+ "text-halo-color": colors.poi.halo ?? alpha(colors.background, 0.78),
644
+ "text-halo-width": 1
645
+ }
646
+ }
647
+ ];
648
+ }
649
+ function poiLabelsFromMode(mode) {
650
+ if (mode === "none") return "none";
651
+ if (mode === "minimal") return "minimal";
652
+ if (mode === "full") return "full";
653
+ return "balanced";
654
+ }
655
+ function poiCategoryFilter(categories) {
656
+ return ["in", "class", ...categories.flatMap((category) => poiCategoryClasses(category))];
657
+ }
658
+ function poiIconImageExpression(customIconSpriteId2, customIconMapping = {}) {
659
+ const matchParts = [];
660
+ for (const [className, iconName] of Object.entries(poiClassIconNames)) {
661
+ matchParts.push(
662
+ className,
663
+ poiIconImageFallback(iconName, customIconSpriteId2, customIconMapping)
664
+ );
665
+ }
666
+ return [
667
+ "match",
668
+ ["get", "class"],
669
+ ...matchParts,
670
+ poiIconImageFallback("default", customIconSpriteId2, customIconMapping)
671
+ ];
672
+ }
673
+ function poiCategoryClasses(category) {
674
+ return poiCategoryClassNames[category] ?? [category];
675
+ }
676
+ function poiIconImageFallback(iconName, customIconSpriteId2, customIconMapping) {
677
+ const osmBrightIcon = `${osmBrightIconNameForSemanticIcon(iconName)}_11`;
678
+ const customIconName = customIconMapping[iconName] ?? iconName;
679
+ if (!customIconSpriteId2) {
680
+ return ["coalesce", ["image", customIconName], ["image", osmBrightIcon]];
681
+ }
682
+ return [
683
+ "coalesce",
684
+ ["image", `${customIconSpriteId2}:${customIconName}`],
685
+ ["image", osmBrightIcon]
686
+ ];
687
+ }
688
+ function osmBrightIconNameForSemanticIcon(iconName) {
689
+ return semanticToOsmBrightIconName[iconName] ?? "marker";
690
+ }
691
+ var poiCategoryClassNames = {
692
+ coffee: ["cafe"],
693
+ culture: ["museum", "gallery", "theatre", "cinema", "library", "attraction"],
694
+ education: ["school", "college", "university"],
695
+ food: ["restaurant", "fast_food", "bar", "pub", "biergarten"],
696
+ health: ["hospital", "pharmacy", "doctors", "dentist"],
697
+ lodging: ["hotel", "motel", "hostel", "guest_house"],
698
+ services: ["bank", "post", "toilet", "police", "fire_station", "town_hall"],
699
+ shopping: ["shop", "mall", "supermarket", "marketplace"],
700
+ transit: ["railway", "station", "bus", "bus_stop", "subway", "tram"]
701
+ };
702
+ var poiClassIconNames = {
703
+ attraction: "culture",
704
+ bank: "services",
705
+ bar: "food",
706
+ biergarten: "food",
707
+ bus: "transit",
708
+ bus_stop: "transit",
709
+ cafe: "coffee",
710
+ cinema: "culture",
711
+ college: "education",
712
+ dentist: "health",
713
+ doctors: "health",
714
+ fast_food: "food",
715
+ fire_station: "services",
716
+ gallery: "culture",
717
+ guest_house: "lodging",
718
+ hospital: "health",
719
+ hostel: "lodging",
720
+ hotel: "lodging",
721
+ library: "culture",
722
+ mall: "shopping",
723
+ marketplace: "shopping",
724
+ motel: "lodging",
725
+ museum: "culture",
726
+ pharmacy: "health",
727
+ police: "services",
728
+ post: "services",
729
+ pub: "food",
730
+ railway: "transit",
731
+ restaurant: "food",
732
+ school: "education",
733
+ shop: "shopping",
734
+ station: "transit",
735
+ subway: "transit",
736
+ supermarket: "shopping",
737
+ theatre: "culture",
738
+ toilet: "services",
739
+ town_hall: "services",
740
+ tram: "transit",
741
+ university: "education"
742
+ };
743
+ var semanticToOsmBrightIconName = {
744
+ coffee: "cafe",
745
+ culture: "museum",
746
+ default: "marker",
747
+ education: "school",
748
+ food: "restaurant",
749
+ health: "hospital",
750
+ lodging: "lodging",
751
+ services: "marker",
752
+ shopping: "shop",
753
+ transit: "railway"
754
+ };
755
+
756
+ // src/modules/roads/index.ts
757
+ function roads(options = {}) {
758
+ return {
759
+ type: "roads",
760
+ preset: options.preset ?? "modern",
761
+ ...options.density ? { density: options.density } : {}
762
+ };
763
+ }
764
+ function resolveRoads(legacyRoads, moduleConfig, fallback) {
765
+ if (!moduleConfig) {
766
+ return legacyRoads ?? fallback;
767
+ }
768
+ if (moduleConfig.preset === "minimal" || moduleConfig.density === "clean") {
769
+ return "soft";
770
+ }
771
+ if (moduleConfig.density === "dense") {
772
+ return "detailed";
773
+ }
774
+ return "standard";
775
+ }
776
+ function roadLayers(source, sourceLayer, colors, roadsMode) {
777
+ if (roadsMode === "hidden") {
778
+ return [];
779
+ }
780
+ const opacity = roadsMode === "soft" ? 0.72 : roadsMode === "standard" ? 0.84 : 0.96;
781
+ const casingOpacity = roadsMode === "detailed" ? 0.3 : 0.16;
782
+ const majorRoadFilter = classFilter(["motorway", "trunk", "primary", "secondary"]);
783
+ const ferryFilter = classFilter(["ferry"]);
784
+ const pathFilter = classFilter(["path", "track"]);
785
+ const railFilter = classFilter(["rail", "transit"]);
786
+ const minorRoadFilter = [
787
+ "all",
788
+ ["!", majorRoadFilter],
789
+ ["!", ferryFilter],
790
+ ["!", pathFilter],
791
+ ["!", railFilter]
792
+ ];
793
+ const nonTunnelRoadFilter = ["!=", ["get", "brunnel"], "tunnel"];
794
+ const roadClassColor = roadClassColorExpression(colors);
795
+ return [
796
+ {
797
+ id: "roads-tunnels-casing",
798
+ type: "line",
799
+ source,
800
+ "source-layer": sourceLayer,
801
+ minzoom: 10,
802
+ filter: ["==", ["get", "brunnel"], "tunnel"],
803
+ paint: {
804
+ "line-color": colors.roads.tunnel,
805
+ "line-dasharray": [0.6, 0.8],
806
+ "line-opacity": 0.28,
807
+ "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.5, 14, 1.7, 16, 3.8]
808
+ }
809
+ },
810
+ {
811
+ id: "roads-tunnels",
812
+ type: "line",
813
+ source,
814
+ "source-layer": sourceLayer,
815
+ minzoom: 10,
816
+ filter: ["==", ["get", "brunnel"], "tunnel"],
817
+ paint: {
818
+ "line-color": roadClassColor,
819
+ "line-dasharray": [0.6, 0.8],
820
+ "line-opacity": 0.2,
821
+ "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.25, 14, 1, 16, 2.4]
822
+ }
823
+ },
824
+ {
825
+ id: "roads-casing",
826
+ type: "line",
827
+ source,
828
+ "source-layer": sourceLayer,
829
+ minzoom: 6,
830
+ filter: nonTunnelRoadFilter,
831
+ paint: {
832
+ "line-color": colors.roads.casing,
833
+ "line-opacity": casingOpacity,
834
+ "line-width": ["interpolate", ["linear"], ["zoom"], 6, 0.4, 12, 1.2, 16, 4]
835
+ }
836
+ },
837
+ {
838
+ id: "roads-ferry",
839
+ type: "line",
840
+ source,
841
+ "source-layer": sourceLayer,
842
+ minzoom: 5,
843
+ filter: ["all", nonTunnelRoadFilter, ferryFilter],
844
+ paint: {
845
+ "line-color": colors.roads.ferry,
846
+ "line-dasharray": [1.2, 1.1],
847
+ "line-opacity": opacity * 0.8,
848
+ "line-width": ["interpolate", ["linear"], ["zoom"], 5, 0.25, 12, 0.8, 16, 2.6]
849
+ }
850
+ },
851
+ {
852
+ id: "roads-paths",
853
+ type: "line",
854
+ source,
855
+ "source-layer": sourceLayer,
856
+ minzoom: 10,
857
+ filter: ["all", nonTunnelRoadFilter, pathFilter],
858
+ paint: {
859
+ "line-color": colors.roads.path,
860
+ "line-dasharray": [0.7, 0.7],
861
+ "line-opacity": opacity * 0.68,
862
+ "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.25, 14, 0.8, 16, 2.2]
863
+ }
864
+ },
865
+ {
866
+ id: "roads-rail",
867
+ type: "line",
868
+ source,
869
+ "source-layer": sourceLayer,
870
+ minzoom: 7,
871
+ filter: ["all", nonTunnelRoadFilter, railFilter],
872
+ paint: {
873
+ "line-color": colors.roads.rail,
874
+ "line-opacity": opacity * 0.58,
875
+ "line-width": ["interpolate", ["linear"], ["zoom"], 7, 0.35, 12, 0.9, 16, 2.4]
876
+ }
877
+ },
878
+ {
879
+ id: "roads-minor",
880
+ type: "line",
881
+ source,
882
+ "source-layer": sourceLayer,
883
+ minzoom: 6,
884
+ filter: ["all", nonTunnelRoadFilter, minorRoadFilter],
885
+ paint: {
886
+ "line-color": colors.roads.minor,
887
+ "line-opacity": opacity * 0.88,
888
+ "line-width": ["interpolate", ["linear"], ["zoom"], 6, 0.2, 12, 0.8, 16, 3]
889
+ }
890
+ },
891
+ {
892
+ id: "roads-major",
893
+ type: "line",
894
+ source,
895
+ "source-layer": sourceLayer,
896
+ minzoom: 5,
897
+ filter: ["all", nonTunnelRoadFilter, majorRoadFilter],
898
+ paint: {
899
+ "line-color": roadClassColor,
900
+ "line-opacity": opacity,
901
+ "line-width": ["interpolate", ["linear"], ["zoom"], 5, 0.35, 12, 1.2, 16, 4]
902
+ }
903
+ },
904
+ {
905
+ id: "roads-bridges",
906
+ type: "line",
907
+ source,
908
+ "source-layer": sourceLayer,
909
+ minzoom: 10,
910
+ filter: ["==", ["get", "brunnel"], "bridge"],
911
+ paint: {
912
+ "line-color": colors.roads.bridge,
913
+ "line-opacity": opacity,
914
+ "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.45, 14, 1.6, 16, 4.4]
915
+ }
916
+ }
917
+ ];
918
+ }
919
+ function classFilter(classes) {
920
+ return ["match", ["get", "class"], classes, true, false];
921
+ }
922
+ function roadClassColorExpression(colors) {
923
+ return [
924
+ "match",
925
+ ["get", "class"],
926
+ "motorway",
927
+ colors.roads.motorway,
928
+ "trunk",
929
+ colors.roads.trunk,
930
+ "primary",
931
+ colors.roads.primary,
932
+ "secondary",
933
+ colors.roads.secondary,
934
+ "rail",
935
+ colors.roads.rail,
936
+ "transit",
937
+ colors.roads.rail,
938
+ "ferry",
939
+ colors.roads.ferry,
940
+ "path",
941
+ colors.roads.path,
942
+ "track",
943
+ colors.roads.path,
944
+ colors.roads.minor
945
+ ];
946
+ }
947
+
614
948
  // src/basemaps/osm/layers.ts
615
949
  function openMapTilesLayers(source, sourceLayers, colors, options) {
616
950
  return [
@@ -622,13 +956,27 @@ function openMapTilesLayers(source, sourceLayers, colors, options) {
622
956
  }
623
957
  },
624
958
  fillLayer("landuse", source, sourceLayers.landuse, colors.landuse.residential, 0.42),
625
- fillLayer("parks", source, sourceLayers.park, colors.landcover.park, parkOpacity(options.density)),
959
+ fillLayer(
960
+ "parks",
961
+ source,
962
+ sourceLayers.park,
963
+ colors.landcover.park,
964
+ parkOpacity(options.density)
965
+ ),
626
966
  fillLayer("water", source, sourceLayers.water, colors.hydro.water, 1),
627
967
  ...buildingLayers(source, sourceLayers.building, colors, options.density, options.buildings),
628
968
  ...roadLayers(source, sourceLayers.road, colors, options.roads),
629
969
  ...boundaryLayers(source, sourceLayers.boundary, colors),
630
970
  ...labelLayers(source, sourceLayers.place, colors, options.labels, options.typography),
631
- ...poiLayers(source, sourceLayers.poi, colors, options.poi, options.typography)
971
+ ...poiLayers(
972
+ source,
973
+ sourceLayers.poi,
974
+ colors,
975
+ options.poi,
976
+ options.typography,
977
+ options.customIconSpriteId,
978
+ options.customIconMapping
979
+ )
632
980
  ];
633
981
  }
634
982
  function fillLayer(id, source, sourceLayer, color, opacity) {
@@ -719,82 +1067,27 @@ function buildingLayers(source, sourceLayer, colors, density, buildings) {
719
1067
  type: "line",
720
1068
  source,
721
1069
  "source-layer": sourceLayer,
722
- minzoom: 15,
723
- paint: {
724
- "line-color": colors.buildings.outline,
725
- "line-opacity": 0.24,
726
- "line-width": 0.6
727
- }
728
- }
729
- ];
730
- }
731
- function boundaryLayers(source, sourceLayer, colors) {
732
- return [
733
- {
734
- id: "boundaries",
735
- type: "line",
736
- source,
737
- "source-layer": sourceLayer,
738
- minzoom: 2,
739
- paint: {
740
- "line-color": colors.boundaries.admin,
741
- "line-opacity": 0.35,
742
- "line-width": ["interpolate", ["linear"], ["zoom"], 2, 0.35, 8, 0.8]
743
- }
744
- }
745
- ];
746
- }
747
- function labelLayers(source, sourceLayer, colors, labels, typography) {
748
- if (labels === "none") {
749
- return [];
750
- }
751
- const maxRank = labels === "essential" ? 3 : labels === "balanced" ? 7 : 12;
752
- return [
753
- {
754
- id: "place-labels",
755
- type: "symbol",
756
- source,
757
- "source-layer": sourceLayer,
758
- minzoom: 3,
759
- filter: ["<=", ["coalesce", ["get", "rank"], 10], maxRank],
760
- layout: {
761
- "text-field": ["coalesce", ["get", "name:en"], ["get", "name"]],
762
- "text-font": textFont(typography),
763
- "text-size": ["interpolate", ["linear"], ["zoom"], 3, 10, 8, 13, 14, 17],
764
- "text-letter-spacing": 0,
765
- "text-padding": 4
766
- },
1070
+ minzoom: 15,
767
1071
  paint: {
768
- "text-color": colors.labels.primary,
769
- "text-halo-color": colors.labels.halo,
770
- "text-halo-width": 1.2
1072
+ "line-color": colors.buildings.outline,
1073
+ "line-opacity": 0.24,
1074
+ "line-width": 0.6
771
1075
  }
772
1076
  }
773
1077
  ];
774
1078
  }
775
- function poiLayers(source, sourceLayer, colors, poi, typography) {
776
- if (poi === "none") {
777
- return [];
778
- }
779
- const minzoom = poi === "minimal" ? 15 : poi === "balanced" ? 13 : 11;
1079
+ function boundaryLayers(source, sourceLayer, colors) {
780
1080
  return [
781
1081
  {
782
- id: "poi-labels",
783
- type: "symbol",
1082
+ id: "boundaries",
1083
+ type: "line",
784
1084
  source,
785
1085
  "source-layer": sourceLayer,
786
- minzoom,
787
- layout: {
788
- "text-field": ["coalesce", ["get", "name:en"], ["get", "name"]],
789
- "text-font": textFont(typography),
790
- "text-size": ["interpolate", ["linear"], ["zoom"], minzoom, 10, 17, 12],
791
- "text-letter-spacing": 0,
792
- "text-padding": 2
793
- },
1086
+ minzoom: 2,
794
1087
  paint: {
795
- "text-color": colors.labels.poi,
796
- "text-halo-color": alpha(colors.background, 0.78),
797
- "text-halo-width": 1
1088
+ "line-color": colors.boundaries.admin,
1089
+ "line-opacity": 0.35,
1090
+ "line-width": ["interpolate", ["linear"], ["zoom"], 2, 0.35, 8, 0.8]
798
1091
  }
799
1092
  }
800
1093
  ];
@@ -880,7 +1173,10 @@ var hexColorSchema = z.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-
880
1173
  message: "Expected a hex color like #F6F7F3 or #F6F7F3FF"
881
1174
  });
882
1175
  function colorGroupSchema(keys) {
883
- return z.object(Object.fromEntries(keys.map((key) => [key, hexColorSchema.optional()]))).strict();
1176
+ const shape = Object.fromEntries(
1177
+ keys.map((key) => [key, hexColorSchema.optional()])
1178
+ );
1179
+ return z.object(shape).strict();
884
1180
  }
885
1181
  var colorConfigSchema = z.object({
886
1182
  accent: hexColorSchema.optional(),
@@ -906,6 +1202,7 @@ var colorConfigSchema = z.object({
906
1202
  "country",
907
1203
  "halo",
908
1204
  "muted",
1205
+ "neighborhood",
909
1206
  "poi",
910
1207
  "primary",
911
1208
  "road",
@@ -920,6 +1217,20 @@ var colorConfigSchema = z.object({
920
1217
  "industrial",
921
1218
  "residential"
922
1219
  ]).optional(),
1220
+ poi: colorGroupSchema([
1221
+ "coffee",
1222
+ "culture",
1223
+ "education",
1224
+ "food",
1225
+ "halo",
1226
+ "health",
1227
+ "icon",
1228
+ "label",
1229
+ "lodging",
1230
+ "services",
1231
+ "shopping",
1232
+ "transit"
1233
+ ]).optional(),
923
1234
  roads: colorGroupSchema([
924
1235
  "bridge",
925
1236
  "casing",
@@ -942,6 +1253,7 @@ var themeModulesSchema = z.object({
942
1253
  "country",
943
1254
  "halo",
944
1255
  "muted",
1256
+ "neighborhood",
945
1257
  "poi",
946
1258
  "primary",
947
1259
  "road",
@@ -956,6 +1268,20 @@ var themeModulesSchema = z.object({
956
1268
  "industrial",
957
1269
  "residential"
958
1270
  ]).optional(),
1271
+ poi: colorGroupSchema([
1272
+ "coffee",
1273
+ "culture",
1274
+ "education",
1275
+ "food",
1276
+ "halo",
1277
+ "health",
1278
+ "icon",
1279
+ "label",
1280
+ "lodging",
1281
+ "services",
1282
+ "shopping",
1283
+ "transit"
1284
+ ]).optional(),
959
1285
  roads: colorGroupSchema([
960
1286
  "bridge",
961
1287
  "casing",
@@ -1026,12 +1352,43 @@ var roadsModuleSchema = z.object({
1026
1352
  density: z.enum(["clean", "balanced", "dense"]).optional(),
1027
1353
  preset: z.enum(["modern", "minimal"]).optional()
1028
1354
  }).strict();
1355
+ var labelDensitySchema = z.enum(["none", "minimal", "balanced", "detailed"]);
1356
+ var labelsModuleSchema = z.object({
1357
+ type: z.literal("labels"),
1358
+ language: z.string().trim().min(1).optional(),
1359
+ places: labelDensitySchema.optional(),
1360
+ preset: z.enum(["none", "essential", "balanced", "full"]).optional(),
1361
+ roads: labelDensitySchema.optional(),
1362
+ water: labelDensitySchema.optional()
1363
+ }).strict();
1364
+ var poiModuleSchema = z.object({
1365
+ type: z.literal("poi"),
1366
+ categories: z.array(identifierSchema).optional(),
1367
+ icons: z.union([z.boolean(), z.enum(["essential", "full"])]).optional(),
1368
+ labels: z.enum(["none", "minimal", "balanced", "full"]).optional(),
1369
+ minZoom: z.number().finite().min(0).max(24).optional(),
1370
+ preset: z.enum(["none", "minimal", "balanced", "full"]).optional()
1371
+ }).strict();
1029
1372
  var styleOverrideModuleSchema = z.object({
1030
1373
  type: z.literal("styleOverride"),
1031
1374
  layers: z.record(z.string(), styleLayerOverrideSchema).optional(),
1032
1375
  removeLayers: z.array(z.string().min(1)).optional()
1033
1376
  }).strict();
1034
- var moduleSchema = z.discriminatedUnion("type", [roadsModuleSchema, styleOverrideModuleSchema]);
1377
+ var moduleSchema = z.discriminatedUnion("type", [
1378
+ labelsModuleSchema,
1379
+ poiModuleSchema,
1380
+ roadsModuleSchema,
1381
+ styleOverrideModuleSchema
1382
+ ]);
1383
+ var iconSetSchema = z.union([
1384
+ z.string().trim().min(1),
1385
+ z.object({
1386
+ extends: z.string().trim().min(1).optional(),
1387
+ mapping: z.record(identifierSchema, z.string().trim().min(1)).optional(),
1388
+ source: z.string().trim().min(1).optional(),
1389
+ sprite: z.string().trim().min(1).optional()
1390
+ }).strict()
1391
+ ]);
1035
1392
  var viewSchema = z.object({
1036
1393
  bearing: z.number().finite().min(-180).max(180).optional(),
1037
1394
  center: z.tuple([z.number().finite(), z.number().finite()]).refine(([lng, lat]) => lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90, {
@@ -1071,6 +1428,7 @@ var tileflowMapSchema = z.object({
1071
1428
  buildings: z.enum(["hidden", "flat", "3d"]).optional(),
1072
1429
  terrain: z.union([z.enum(["none", "hillshade", "3d"]), terrainConfigSchema]).optional(),
1073
1430
  glyphs: z.string().min(1).optional(),
1431
+ icons: iconSetSchema.optional(),
1074
1432
  sprite: z.string().min(1).optional(),
1075
1433
  tiles: tilesSchema.optional(),
1076
1434
  view: viewSchema.optional(),
@@ -1084,6 +1442,7 @@ var tilesetConfigSchema = z.object({
1084
1442
  sourceLayers: sourceLayersSchema.optional()
1085
1443
  }).strict();
1086
1444
  var tileflowProjectSchema = z.object({
1445
+ icons: z.record(identifierSchema, iconSetSchema).optional(),
1087
1446
  themes: z.record(identifierSchema, themeConfigSchema).optional(),
1088
1447
  tilesets: z.record(identifierSchema, tilesetConfigSchema).optional(),
1089
1448
  maps: z.record(identifierSchema, configSchema).refine((maps) => {
@@ -1152,10 +1511,11 @@ function isProjectConfigLike(input) {
1152
1511
  function validateProjectThemeReferences(project) {
1153
1512
  const themeNames = new Set(Object.keys(project.themes ?? {}));
1154
1513
  const themeMessages = validateThemeExtends(project.themes, themeNames);
1514
+ const iconMessages = validateProjectIconReferences(project);
1155
1515
  const mapMessages = Object.entries(project.maps).flatMap(
1156
1516
  ([mapName, mapConfig]) => validateMapThemeReference(mapConfig, themeNames, `maps.${mapName}.theme`)
1157
1517
  );
1158
- return [...themeMessages, ...mapMessages];
1518
+ return [...themeMessages, ...iconMessages, ...mapMessages];
1159
1519
  }
1160
1520
  function validateMapThemeReference(config, themeNames, path) {
1161
1521
  if (!config.theme) {
@@ -1220,6 +1580,242 @@ function findThemeCycle(themeName, themes, path) {
1220
1580
  }
1221
1581
  return findThemeCycle(nextThemeName, themes, [...path, themeName]);
1222
1582
  }
1583
+ function validateProjectIconReferences(project) {
1584
+ const messages = [];
1585
+ const iconSets = project.icons ?? {};
1586
+ for (const [iconSetName, iconSet] of Object.entries(iconSets)) {
1587
+ const extendedName = iconSetReference(iconSet);
1588
+ if (extendedName && !iconSets[extendedName]) {
1589
+ messages.push({
1590
+ level: "error",
1591
+ path: iconSetExtendsPath(`icons.${iconSetName}`, iconSet),
1592
+ message: `Unknown Tileflow icon set "${extendedName}"`
1593
+ });
1594
+ }
1595
+ }
1596
+ for (const iconSetName of Object.keys(iconSets)) {
1597
+ const cycle = findIconSetCycle(iconSetName, iconSets, []);
1598
+ if (cycle) {
1599
+ messages.push({
1600
+ level: "error",
1601
+ path: iconSetExtendsPath(`icons.${iconSetName}`, iconSets[iconSetName]),
1602
+ message: `Circular Tileflow icon set extends: ${cycle.join(" -> ")}`
1603
+ });
1604
+ }
1605
+ }
1606
+ for (const [mapName, mapConfig] of Object.entries(project.maps)) {
1607
+ if (!isIconSetObject(mapConfig.icons) || !mapConfig.icons.extends) {
1608
+ continue;
1609
+ }
1610
+ if (!iconSets[mapConfig.icons.extends]) {
1611
+ messages.push({
1612
+ level: "error",
1613
+ path: `maps.${mapName}.icons.extends`,
1614
+ message: `Unknown Tileflow icon set "${mapConfig.icons.extends}"`
1615
+ });
1616
+ }
1617
+ }
1618
+ return messages;
1619
+ }
1620
+ function findIconSetCycle(iconSetName, iconSets, path) {
1621
+ if (path.includes(iconSetName)) {
1622
+ return [...path, iconSetName];
1623
+ }
1624
+ const nextIconSetName = iconSetReference(iconSets?.[iconSetName]);
1625
+ if (!nextIconSetName || !iconSets?.[nextIconSetName]) {
1626
+ return void 0;
1627
+ }
1628
+ return findIconSetCycle(nextIconSetName, iconSets, [...path, iconSetName]);
1629
+ }
1630
+ function iconSetReference(iconSet) {
1631
+ if (typeof iconSet === "string") {
1632
+ return iconSet;
1633
+ }
1634
+ return isIconSetObject(iconSet) ? iconSet.extends : void 0;
1635
+ }
1636
+ function iconSetExtendsPath(basePath, iconSet) {
1637
+ return isIconSetObject(iconSet) ? `${basePath}.extends` : basePath;
1638
+ }
1639
+ function isIconSetObject(value) {
1640
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
1641
+ }
1642
+
1643
+ // src/terrain/index.ts
1644
+ function resolveTerrain(terrain, tileBaseUrl) {
1645
+ if (!terrain || terrain === "none") {
1646
+ return void 0;
1647
+ }
1648
+ const terrainConfig = typeof terrain === "string" ? { mode: terrain } : terrain;
1649
+ const mode = terrainConfig.mode ?? "hillshade";
1650
+ if (mode === "none") {
1651
+ return void 0;
1652
+ }
1653
+ const sourceId = terrainConfig.sourceId ?? "tileflow-terrain";
1654
+ const tileset = terrainConfig.tileset ?? "terrain";
1655
+ const url = terrainConfig.url ?? `${tileBaseUrl}/tiles/${tileset}/tiles.json`;
1656
+ const encoding = terrainConfig.encoding ?? "terrarium";
1657
+ return {
1658
+ exaggeration: terrainConfig.exaggeration ?? 1.2,
1659
+ mode,
1660
+ sourceId,
1661
+ source: {
1662
+ ...terrainConfig.attribution ? { attribution: terrainConfig.attribution } : {},
1663
+ encoding,
1664
+ type: "raster-dem",
1665
+ url
1666
+ }
1667
+ };
1668
+ }
1669
+ function withTerrainLayers(layers, terrain) {
1670
+ const hillshadeLayer = {
1671
+ id: "terrain-hillshade",
1672
+ type: "hillshade",
1673
+ source: terrain.sourceId,
1674
+ maxzoom: 15,
1675
+ paint: {
1676
+ "hillshade-accent-color": "rgba(255, 255, 255, 0.18)",
1677
+ "hillshade-exaggeration": terrain.mode === "3d" ? 0.24 : 0.42,
1678
+ "hillshade-highlight-color": "rgba(255, 255, 255, 0.28)",
1679
+ "hillshade-shadow-color": "rgba(38, 44, 50, 0.34)"
1680
+ }
1681
+ };
1682
+ const insertionIndex = layers.findIndex((layer) => layer.id === "water");
1683
+ if (insertionIndex === -1) {
1684
+ return [hillshadeLayer, ...layers];
1685
+ }
1686
+ return [
1687
+ ...layers.slice(0, insertionIndex + 1),
1688
+ hillshadeLayer,
1689
+ ...layers.slice(insertionIndex + 1)
1690
+ ];
1691
+ }
1692
+
1693
+ // src/templates/osm-bright/controls.ts
1694
+ function applyOsmBrightControls(layers, options) {
1695
+ return layers.map((layer) => {
1696
+ const id = stringValue(layer.id);
1697
+ const sourceLayer = stringValue(layer["source-layer"]);
1698
+ const type = stringValue(layer.type);
1699
+ if (type !== "symbol") {
1700
+ return layer;
1701
+ }
1702
+ if (isPoiLayer(id, sourceLayer)) {
1703
+ return applyPoiLayerControls(
1704
+ layer,
1705
+ options.poi,
1706
+ options.customIconSpriteId,
1707
+ options.customIconMapping
1708
+ );
1709
+ }
1710
+ if (isLabelLayer(id, sourceLayer)) {
1711
+ return applyLabelLayerControls(layer, options.labels);
1712
+ }
1713
+ return layer;
1714
+ });
1715
+ }
1716
+ function applyLabelLayerControls(layer, labels2) {
1717
+ const id = stringValue(layer.id);
1718
+ const sourceLayer = stringValue(layer["source-layer"]);
1719
+ const group = labelGroup(id, sourceLayer);
1720
+ const density = labelGroupDensity(labels2, group);
1721
+ if (labels2.mode === "none" || density === "none") {
1722
+ return setLayerVisibility(layer, "none");
1723
+ }
1724
+ const nextLayer = setLayerVisibility(layer, "visible");
1725
+ const layout = isRecord3(nextLayer.layout) ? { ...nextLayer.layout } : {};
1726
+ if ("text-field" in layout && !isHighwayShieldLayer(id)) {
1727
+ layout["text-field"] = labelTextField(labels2.language);
1728
+ }
1729
+ if (density === "minimal") {
1730
+ nextLayer.minzoom = Math.max(numberValue(nextLayer.minzoom) ?? 0, minimalMinZoom(group));
1731
+ }
1732
+ if (density === "detailed") {
1733
+ const currentMinZoom = numberValue(nextLayer.minzoom);
1734
+ if (currentMinZoom !== void 0) {
1735
+ nextLayer.minzoom = Math.max(0, currentMinZoom - 1);
1736
+ }
1737
+ }
1738
+ nextLayer.layout = layout;
1739
+ return nextLayer;
1740
+ }
1741
+ function applyPoiLayerControls(layer, poi2, customIconSpriteId2, customIconMapping) {
1742
+ if (poi2.mode === "none") {
1743
+ return setLayerVisibility(layer, "none");
1744
+ }
1745
+ const nextLayer = setLayerVisibility(layer, "visible");
1746
+ const layout = isRecord3(nextLayer.layout) ? { ...nextLayer.layout } : {};
1747
+ nextLayer.minzoom = Math.max(numberValue(nextLayer.minzoom) ?? 0, poi2.minZoom);
1748
+ if (poi2.categories && sourceLayerValue(nextLayer) === "poi") {
1749
+ nextLayer.filter = combineFilters(nextLayer.filter, poiCategoryFilter(poi2.categories));
1750
+ }
1751
+ if (poi2.labels === "none") {
1752
+ delete layout["text-field"];
1753
+ } else if ("text-field" in layout) {
1754
+ layout["text-field"] = labelTextField("auto");
1755
+ }
1756
+ if (poi2.icons === false) {
1757
+ delete layout["icon-image"];
1758
+ } else if ("icon-image" in layout || customIconSpriteId2) {
1759
+ layout["icon-image"] = poiIconImageExpression(customIconSpriteId2, customIconMapping);
1760
+ layout["icon-optional"] = true;
1761
+ }
1762
+ nextLayer.layout = layout;
1763
+ return nextLayer;
1764
+ }
1765
+ function labelGroupDensity(labels2, group) {
1766
+ if (group === "roads") return labels2.roads;
1767
+ if (group === "water") return labels2.water;
1768
+ return labels2.places;
1769
+ }
1770
+ function labelGroup(id, sourceLayer) {
1771
+ if (sourceLayer === "transportation_name" || id.includes("highway-name") || id.includes("highway-shield")) {
1772
+ return "roads";
1773
+ }
1774
+ if (sourceLayer === "water_name" || sourceLayer === "waterway" || id.includes("water-name")) {
1775
+ return "water";
1776
+ }
1777
+ return "places";
1778
+ }
1779
+ function minimalMinZoom(group) {
1780
+ if (group === "roads") return 13;
1781
+ if (group === "water") return 5;
1782
+ return 4;
1783
+ }
1784
+ function isLabelLayer(id, sourceLayer) {
1785
+ return sourceLayer === "place" || sourceLayer === "transportation_name" || sourceLayer === "water_name" || sourceLayer === "waterway" || id.includes("highway-name") || id.includes("highway-shield") || id.includes("water-name");
1786
+ }
1787
+ function isPoiLayer(id, sourceLayer) {
1788
+ return sourceLayer === "poi" || id.startsWith("poi-");
1789
+ }
1790
+ function isHighwayShieldLayer(id) {
1791
+ return id.includes("highway-shield");
1792
+ }
1793
+ function setLayerVisibility(layer, visibility) {
1794
+ const layout = isRecord3(layer.layout) ? { ...layer.layout } : {};
1795
+ layout.visibility = visibility;
1796
+ return {
1797
+ ...layer,
1798
+ layout
1799
+ };
1800
+ }
1801
+ function combineFilters(first, second) {
1802
+ if (!first) {
1803
+ return second;
1804
+ }
1805
+ return ["all", first, second];
1806
+ }
1807
+ function sourceLayerValue(layer) {
1808
+ return stringValue(layer["source-layer"]);
1809
+ }
1810
+ function stringValue(value) {
1811
+ return typeof value === "string" ? value : "";
1812
+ }
1813
+ function numberValue(value) {
1814
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1815
+ }
1816
+ function isRecord3(value) {
1817
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
1818
+ }
1223
1819
 
1224
1820
  // src/templates/osm-bright/style.json
1225
1821
  var style_default = {
@@ -3783,68 +4379,18 @@ var style_default = {
3783
4379
  id: "bright"
3784
4380
  };
3785
4381
 
3786
- // src/terrain/index.ts
3787
- function resolveTerrain(terrain, tileBaseUrl) {
3788
- if (!terrain || terrain === "none") {
3789
- return void 0;
3790
- }
3791
- const terrainConfig = typeof terrain === "string" ? { mode: terrain } : terrain;
3792
- const mode = terrainConfig.mode ?? "hillshade";
3793
- if (mode === "none") {
3794
- return void 0;
3795
- }
3796
- const sourceId = terrainConfig.sourceId ?? "tileflow-terrain";
3797
- const tileset = terrainConfig.tileset ?? "terrain";
3798
- const url = terrainConfig.url ?? `${tileBaseUrl}/tiles/${tileset}/tiles.json`;
3799
- const encoding = terrainConfig.encoding ?? "terrarium";
3800
- return {
3801
- exaggeration: terrainConfig.exaggeration ?? 1.2,
3802
- mode,
3803
- sourceId,
3804
- source: {
3805
- ...terrainConfig.attribution ? { attribution: terrainConfig.attribution } : {},
3806
- encoding,
3807
- type: "raster-dem",
3808
- url
3809
- }
3810
- };
3811
- }
3812
- function withTerrainLayers(layers, terrain) {
3813
- const hillshadeLayer = {
3814
- id: "terrain-hillshade",
3815
- type: "hillshade",
3816
- source: terrain.sourceId,
3817
- maxzoom: 15,
3818
- paint: {
3819
- "hillshade-accent-color": "rgba(255, 255, 255, 0.18)",
3820
- "hillshade-exaggeration": terrain.mode === "3d" ? 0.24 : 0.42,
3821
- "hillshade-highlight-color": "rgba(255, 255, 255, 0.28)",
3822
- "hillshade-shadow-color": "rgba(38, 44, 50, 0.34)"
3823
- }
3824
- };
3825
- const insertionIndex = layers.findIndex((layer) => layer.id === "water");
3826
- if (insertionIndex === -1) {
3827
- return [hillshadeLayer, ...layers];
3828
- }
3829
- return [
3830
- ...layers.slice(0, insertionIndex + 1),
3831
- hillshadeLayer,
3832
- ...layers.slice(insertionIndex + 1)
3833
- ];
3834
- }
3835
-
3836
4382
  // src/templates/osm-bright/theme.ts
3837
4383
  function applyOsmBrightTheme(layers, colors, theme, typography) {
3838
4384
  if (!theme.custom && theme.name === "light") {
3839
4385
  return layers;
3840
4386
  }
3841
4387
  return layers.map((layer) => {
3842
- const type = stringValue(layer.type);
3843
- const sourceLayer = stringValue(layer["source-layer"]);
3844
- const id = stringValue(layer.id);
4388
+ const type = stringValue2(layer.type);
4389
+ const sourceLayer = stringValue2(layer["source-layer"]);
4390
+ const id = stringValue2(layer.id);
3845
4391
  const nextLayer = { ...layer };
3846
- const paint = isRecord3(layer.paint) ? { ...layer.paint } : {};
3847
- const layout = isRecord3(layer.layout) ? { ...layer.layout } : {};
4392
+ const paint = isRecord4(layer.paint) ? { ...layer.paint } : {};
4393
+ const layout = isRecord4(layer.layout) ? { ...layer.layout } : {};
3848
4394
  let paintChanged = false;
3849
4395
  let layoutChanged = false;
3850
4396
  if (type === "background" && hasBaseColor(theme, ["background", "canvas"])) {
@@ -3891,43 +4437,40 @@ function applyOsmBrightTheme(layers, colors, theme, typography) {
3891
4437
  }
3892
4438
  function osmBrightFillColor(id, sourceLayer, colors, theme) {
3893
4439
  if (sourceLayer === "water" || id.includes("water")) {
3894
- return shouldThemeHydro(theme) ? colors.hydro.water : void 0;
4440
+ return hasBaseColor(theme, ["water"]) || hasNestedModuleColor(theme, "hydro", "water") ? colors.hydro.water : void 0;
3895
4441
  }
3896
4442
  if (sourceLayer === "building") {
3897
- return shouldThemeBuildings(theme) ? colors.buildings.fill : void 0;
4443
+ return hasBaseColor(theme, ["building"]) || hasNestedModuleColor(theme, "buildings", "fill") ? colors.buildings.fill : void 0;
3898
4444
  }
3899
4445
  if (sourceLayer === "landcover" || id.includes("park") || id.includes("wood")) {
3900
- if (!shouldThemeLandcover(theme)) {
3901
- return void 0;
3902
- }
3903
4446
  if (id.includes("wood") || id.includes("forest")) {
3904
- return colors.landcover.wood;
4447
+ return shouldThemeLandcoverColor(theme, "wood") ? colors.landcover.wood : void 0;
3905
4448
  }
3906
4449
  if (id.includes("grass")) {
3907
- return colors.landcover.grass;
4450
+ return shouldThemeLandcoverColor(theme, "grass") ? colors.landcover.grass : void 0;
3908
4451
  }
3909
4452
  if (id.includes("sand") || id.includes("beach")) {
3910
- return colors.landcover.sand;
4453
+ return shouldThemeLandcoverColor(theme, "sand") ? colors.landcover.sand : void 0;
3911
4454
  }
3912
4455
  if (id.includes("ice") || id.includes("glacier")) {
3913
- return colors.landcover.ice;
4456
+ return shouldThemeLandcoverColor(theme, "ice") ? colors.landcover.ice : void 0;
3914
4457
  }
3915
- return colors.landcover.park;
4458
+ return shouldThemeLandcoverColor(theme, "park") ? colors.landcover.park : void 0;
3916
4459
  }
3917
4460
  if (sourceLayer === "landuse" || id.includes("landuse")) {
3918
- if (!shouldThemeLanduse(theme)) {
3919
- return void 0;
3920
- }
3921
4461
  if (id.includes("commercial") || id.includes("retail")) {
3922
- return colors.landuse.commercial;
4462
+ return shouldThemeLanduseColor(theme, "commercial") ? colors.landuse.commercial : void 0;
3923
4463
  }
3924
4464
  if (id.includes("industrial")) {
3925
- return colors.landuse.industrial;
4465
+ return shouldThemeLanduseColor(theme, "industrial") ? colors.landuse.industrial : void 0;
3926
4466
  }
3927
4467
  if (id.includes("cemetery")) {
3928
- return colors.landuse.cemetery;
4468
+ return shouldThemeLanduseColor(theme, "cemetery") ? colors.landuse.cemetery : void 0;
3929
4469
  }
3930
- return colors.landuse.residential;
4470
+ if (id.includes("hospital") || id.includes("school") || id.includes("civic")) {
4471
+ return shouldThemeLanduseColor(theme, "civic") ? colors.landuse.civic : void 0;
4472
+ }
4473
+ return shouldThemeLanduseColor(theme, "residential") ? colors.landuse.residential : void 0;
3931
4474
  }
3932
4475
  return void 0;
3933
4476
  }
@@ -3936,43 +4479,45 @@ function osmBrightLineColor(id, sourceLayer, colors, theme) {
3936
4479
  return osmBrightRoadLineColor(id, colors, theme);
3937
4480
  }
3938
4481
  if (sourceLayer === "waterway") {
3939
- return shouldThemeHydro(theme) ? colors.hydro.waterway : void 0;
4482
+ return hasBaseColor(theme, ["water"]) || hasNestedModuleColor(theme, "hydro", "waterway") ? colors.hydro.waterway : void 0;
3940
4483
  }
3941
4484
  if (sourceLayer === "boundary") {
3942
- if (!shouldThemeBoundaries(theme)) {
3943
- return void 0;
3944
- }
3945
- return id.includes("country") || id.includes("admin") ? colors.boundaries.major : colors.boundaries.admin;
4485
+ const boundaryKey = boundaryColorKey(id);
4486
+ return hasBaseColor(theme, ["boundary"]) || hasNestedModuleColor(theme, "boundaries", boundaryKey) ? colors.boundaries[boundaryKey] : void 0;
3946
4487
  }
3947
4488
  return void 0;
3948
4489
  }
3949
4490
  function osmBrightTextColor(id, sourceLayer, colors, theme) {
3950
4491
  if (sourceLayer === "water_name" || id.includes("water")) {
3951
- return shouldThemeWaterLabels(theme) ? colors.labels.water : void 0;
4492
+ if (hasNestedModuleColor(theme, "labels", "water")) {
4493
+ return colors.labels.water;
4494
+ }
4495
+ if (hasNestedModuleColor(theme, "hydro", "label")) {
4496
+ return colors.hydro.label;
4497
+ }
4498
+ return hasBaseColor(theme, ["water"]) || shouldThemeLabels(theme) ? colors.labels.water : void 0;
3952
4499
  }
3953
4500
  if (sourceLayer === "transportation_name") {
3954
- return shouldThemeRoadLabels(theme) ? colors.labels.road : void 0;
4501
+ return shouldThemeLabelColor(theme, "road") ? colors.labels.road : void 0;
3955
4502
  }
3956
4503
  if (sourceLayer === "poi" || id.includes("poi")) {
3957
- return shouldThemePoiLabels(theme) ? colors.labels.poi : void 0;
4504
+ if (hasNestedModuleColor(theme, "poi", "label")) {
4505
+ return colors.poi.label;
4506
+ }
4507
+ return shouldThemeLabelColor(theme, "poi") ? colors.labels.poi : void 0;
3958
4508
  }
3959
4509
  if (id.includes("country")) {
3960
- return shouldThemeLabels(theme) ? colors.labels.country : void 0;
4510
+ return shouldThemeLabelColor(theme, "country") ? colors.labels.country : void 0;
3961
4511
  }
3962
4512
  if (sourceLayer === "place" || id.includes("place")) {
3963
- return shouldThemeLabels(theme) ? colors.labels.settlement : void 0;
4513
+ return shouldThemeLabelColor(theme, "settlement") ? colors.labels.settlement : void 0;
3964
4514
  }
3965
- return shouldThemeLabels(theme) ? colors.labels.primary : void 0;
3966
- }
3967
- function shouldThemeHydro(theme) {
3968
- return hasBaseColor(theme, ["water"]) || hasModuleColorGroup(theme, "hydro");
3969
- }
3970
- function shouldThemeWaterLabels(theme) {
3971
- return shouldThemeHydro(theme) || shouldThemeLabels(theme) || hasNestedModuleColor(theme, "labels", "water");
4515
+ return shouldThemeLabelColor(theme, "primary") ? colors.labels.primary : void 0;
3972
4516
  }
3973
4517
  function osmBrightRoadLineColor(id, colors, theme) {
3974
- if (hasModuleColorGroup(theme, "roads")) {
3975
- return osmBrightModuleRoadLineColor(id, colors, theme);
4518
+ const moduleColor = osmBrightModuleRoadLineColor(id, colors, theme);
4519
+ if (moduleColor) {
4520
+ return moduleColor;
3976
4521
  }
3977
4522
  if (isNonRoadThemeTarget(id)) {
3978
4523
  return void 0;
@@ -3991,16 +4536,25 @@ function osmBrightRoadLineColor(id, colors, theme) {
3991
4536
  function osmBrightModuleRoadLineColor(id, colors, theme) {
3992
4537
  const roadColor = (key) => hasNestedModuleColor(theme, "roads", key) ? colors.roads[key] : void 0;
3993
4538
  if (isRoadCasing(id)) {
3994
- return roadColor("casing");
4539
+ const casing = roadColor("casing");
4540
+ if (casing) {
4541
+ return casing;
4542
+ }
3995
4543
  }
3996
4544
  if (id.includes("ferry")) {
3997
4545
  return roadColor("ferry");
3998
4546
  }
3999
4547
  if (id.includes("rail")) {
4000
- return roadColor("rail");
4548
+ const rail = roadColor("rail");
4549
+ if (rail) {
4550
+ return rail;
4551
+ }
4001
4552
  }
4002
4553
  if (id.includes("path") || id.includes("track") || id.includes("steps")) {
4003
- return roadColor("path");
4554
+ const path = roadColor("path");
4555
+ if (path) {
4556
+ return path;
4557
+ }
4004
4558
  }
4005
4559
  if (id.includes("bridge")) {
4006
4560
  const bridge = roadColor("bridge");
@@ -4017,9 +4571,12 @@ function osmBrightModuleRoadLineColor(id, colors, theme) {
4017
4571
  if (id.includes("motorway")) {
4018
4572
  return roadColor("motorway");
4019
4573
  }
4020
- if (id.includes("trunk")) {
4574
+ if (id.includes("trunk-primary")) {
4021
4575
  return roadColor("trunk") ?? roadColor("primary");
4022
4576
  }
4577
+ if (id.includes("trunk")) {
4578
+ return roadColor("trunk");
4579
+ }
4023
4580
  if (id.includes("primary")) {
4024
4581
  return roadColor("primary");
4025
4582
  }
@@ -4034,6 +4591,15 @@ function osmBrightModuleRoadLineColor(id, colors, theme) {
4034
4591
  }
4035
4592
  return void 0;
4036
4593
  }
4594
+ function boundaryColorKey(id) {
4595
+ if (id.includes("water") || id.includes("maritime")) {
4596
+ return "maritime";
4597
+ }
4598
+ if (id.includes("disputed")) {
4599
+ return "disputed";
4600
+ }
4601
+ return id.includes("country") || id.includes("admin") || id.includes("level-2") ? "major" : "admin";
4602
+ }
4037
4603
  function isRoadCasing(id) {
4038
4604
  return id.includes("case") || id.includes("casing");
4039
4605
  }
@@ -4046,41 +4612,26 @@ function isMinorRoad(id) {
4046
4612
  function isNonRoadThemeTarget(id) {
4047
4613
  return id.includes("rail") || id.includes("ferry") || id.includes("cablecar") || id.includes("path") || id.includes("steps") || id.includes("pier") || id.includes("area");
4048
4614
  }
4049
- function shouldThemeLandcover(theme) {
4050
- return hasBaseColor(theme, ["park", "greenspace", "nature"]) || hasModuleColorGroup(theme, "landcover");
4615
+ function shouldThemeLandcoverColor(theme, key) {
4616
+ return hasBaseColor(theme, ["park", "greenspace", "nature"]) || hasNestedModuleColor(theme, "landcover", key);
4051
4617
  }
4052
- function shouldThemeLanduse(theme) {
4053
- return hasBaseColor(theme, ["land"]) || hasModuleColorGroup(theme, "landuse");
4054
- }
4055
- function shouldThemeBuildings(theme) {
4056
- return hasBaseColor(theme, ["building"]) || hasModuleColorGroup(theme, "buildings");
4057
- }
4058
- function shouldThemeBoundaries(theme) {
4059
- return hasBaseColor(theme, ["boundary"]) || hasModuleColorGroup(theme, "boundaries");
4618
+ function shouldThemeLanduseColor(theme, key) {
4619
+ return hasBaseColor(theme, ["land"]) || hasNestedModuleColor(theme, "landuse", key);
4060
4620
  }
4061
4621
  function shouldThemeLabels(theme) {
4062
- return hasBaseColor(theme, ["text", "textMuted"]) || hasModuleColorGroup(theme, "labels");
4622
+ return hasBaseColor(theme, ["text", "textMuted"]);
4063
4623
  }
4064
4624
  function shouldThemeLabelHalo(theme) {
4065
4625
  return hasBaseColor(theme, ["background", "canvas", "textHalo"]) || hasNestedModuleColor(theme, "labels", "halo");
4066
4626
  }
4067
- function shouldThemeRoadLabels(theme) {
4068
- return shouldThemeLabels(theme) || hasNestedModuleColor(theme, "labels", "road");
4069
- }
4070
- function shouldThemePoiLabels(theme) {
4071
- return shouldThemeLabels(theme) || hasNestedModuleColor(theme, "labels", "poi");
4627
+ function shouldThemeLabelColor(theme, key) {
4628
+ return shouldThemeLabels(theme) || hasNestedModuleColor(theme, "labels", key);
4072
4629
  }
4073
4630
  function hasBaseColor(theme, keys) {
4074
4631
  return keys.some(
4075
4632
  (key) => typeof theme.colorOverrides[key] === "string"
4076
4633
  );
4077
4634
  }
4078
- function hasModuleColorGroup(theme, group) {
4079
- const value = theme.moduleOverrides[group];
4080
- return Boolean(
4081
- value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0
4082
- );
4083
- }
4084
4635
  function hasNestedModuleColor(theme, group, key) {
4085
4636
  const value = theme.moduleOverrides[group];
4086
4637
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -4088,10 +4639,10 @@ function hasNestedModuleColor(theme, group, key) {
4088
4639
  }
4089
4640
  return typeof value[key] === "string";
4090
4641
  }
4091
- function isRecord3(value) {
4642
+ function isRecord4(value) {
4092
4643
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
4093
4644
  }
4094
- function stringValue(value) {
4645
+ function stringValue2(value) {
4095
4646
  return typeof value === "string" ? value : "";
4096
4647
  }
4097
4648
 
@@ -4105,7 +4656,13 @@ function createOsmBrightStyle(options) {
4105
4656
  options.theme,
4106
4657
  options.typography
4107
4658
  );
4108
- const resolvedLayers = options.terrain ? withTerrainLayers(themedLayers, options.terrain) : themedLayers;
4659
+ const controlledLayers = applyOsmBrightControls(themedLayers, {
4660
+ customIconMapping: options.customIconMapping,
4661
+ customIconSpriteId: options.customIconSpriteId,
4662
+ labels: options.labelsModule,
4663
+ poi: options.poiModule
4664
+ });
4665
+ const resolvedLayers = options.terrain ? withTerrainLayers(controlledLayers, options.terrain) : controlledLayers;
4109
4666
  return {
4110
4667
  ...template,
4111
4668
  name: options.name,
@@ -4167,6 +4724,7 @@ var defaultConfig = {
4167
4724
  };
4168
4725
  var defaultTileBaseUrl = "https://api.tileflow.dev";
4169
4726
  var defaultTileset = "world";
4727
+ var customIconSpriteId = "tileflow";
4170
4728
  function createStyle(input = {}, options = {}) {
4171
4729
  const parsed = configSchema.safeParse(input);
4172
4730
  if (!parsed.success) {
@@ -4199,9 +4757,21 @@ function createStyle(input = {}, options = {}) {
4199
4757
  const roadModule = config.modules?.find(
4200
4758
  (moduleConfig) => moduleConfig.type === "roads"
4201
4759
  );
4760
+ const labelsModule = config.modules?.find(
4761
+ (moduleConfig) => moduleConfig.type === "labels"
4762
+ );
4763
+ const poiModule = config.modules?.find(
4764
+ (moduleConfig) => moduleConfig.type === "poi"
4765
+ );
4202
4766
  const roads2 = resolveRoads(config.roads, roadModule, theme.preset.roads ?? defaultConfig.roads);
4203
- const labels = config.labels ?? theme.preset.labels ?? defaultConfig.labels;
4204
- const poi = config.poi ?? theme.preset.poi ?? defaultConfig.poi;
4767
+ const resolvedLabels = resolveLabels(
4768
+ config.labels,
4769
+ labelsModule,
4770
+ theme.preset.labels ?? defaultConfig.labels
4771
+ );
4772
+ const resolvedPoi = resolvePoi(config.poi, poiModule, theme.preset.poi ?? defaultConfig.poi);
4773
+ const labels2 = resolvedLabels.mode;
4774
+ const poi2 = resolvedPoi.mode;
4205
4775
  const buildings = config.buildings ?? "flat";
4206
4776
  const colors = resolveColors(config.colors, theme.colors, {}, theme.modules);
4207
4777
  const sourceId = config.tiles?.sourceId ?? basemap.sourceId ?? "tileflow";
@@ -4211,7 +4781,10 @@ function createStyle(input = {}, options = {}) {
4211
4781
  const typography = resolveTypography(config.typography, theme.typography);
4212
4782
  const terrain = resolveTerrain(config.terrain, tileBaseUrl);
4213
4783
  const glyphs = config.glyphs ?? basemap.glyphs ?? `${tileBaseUrl}/fonts/{fontstack}/{range}.pbf`;
4214
- const sprite = config.sprite ?? basemap.sprite;
4784
+ const iconSet = resolveIconSet(config.icons, options.iconSets);
4785
+ const iconSprite = iconSet?.sprite;
4786
+ const sprite = config.sprite ?? resolveSprite(basemap.sprite, iconSprite);
4787
+ const customIconSpriteName = Array.isArray(sprite) && iconSprite ? customIconSpriteId : void 0;
4215
4788
  const attribution = config.tiles?.attribution ?? basemap.attribution ?? "\xA9 OpenMapTiles \xA9 OpenStreetMap contributors";
4216
4789
  const metadata = {
4217
4790
  "tileflow:basemap": basemap.type,
@@ -4219,8 +4792,15 @@ function createStyle(input = {}, options = {}) {
4219
4792
  "tileflow:themeMode": theme.mode,
4220
4793
  "tileflow:density": density,
4221
4794
  "tileflow:roads": roads2,
4222
- "tileflow:labels": labels,
4223
- "tileflow:poi": poi,
4795
+ "tileflow:labels": labels2,
4796
+ "tileflow:labelsModule": labelsModule,
4797
+ "tileflow:poi": poi2,
4798
+ "tileflow:poiModule": poiModule,
4799
+ "tileflow:icons": iconSet ? {
4800
+ customSpriteId: customIconSpriteName,
4801
+ mapping: iconSet.mapping,
4802
+ sprite: iconSprite
4803
+ } : void 0,
4224
4804
  "tileflow:buildings": buildings,
4225
4805
  "tileflow:modules": config.modules?.map((moduleConfig) => moduleConfig.type) ?? [],
4226
4806
  "tileflow:themeModules": Object.keys(theme.moduleOverrides),
@@ -4248,7 +4828,13 @@ function createStyle(input = {}, options = {}) {
4248
4828
  terrain,
4249
4829
  theme,
4250
4830
  tilesUrl,
4251
- typography
4831
+ typography,
4832
+ labels: labels2,
4833
+ labelsModule: resolvedLabels,
4834
+ poi: poi2,
4835
+ poiModule: resolvedPoi,
4836
+ customIconMapping: iconSet?.mapping,
4837
+ customIconSpriteId: customIconSpriteName
4252
4838
  });
4253
4839
  return {
4254
4840
  ...style2,
@@ -4266,10 +4852,12 @@ function createStyle(input = {}, options = {}) {
4266
4852
  const layers = openMapTilesLayers(sourceId, sourceLayers, colors, {
4267
4853
  buildings,
4268
4854
  density,
4269
- labels,
4270
- poi,
4855
+ labels: resolvedLabels,
4856
+ poi: resolvedPoi,
4271
4857
  roads: roads2,
4272
- typography
4858
+ typography,
4859
+ customIconMapping: iconSet?.mapping,
4860
+ customIconSpriteId: customIconSpriteName
4273
4861
  });
4274
4862
  const terrainLayers = terrain ? withTerrainLayers(layers, terrain) : layers;
4275
4863
  const resolvedLayers = applyStyleOverrides(terrainLayers, styleOverrideModules);
@@ -4329,6 +4917,7 @@ function createStyleFromProject(projectConfig, mapName, options = {}) {
4329
4917
  },
4330
4918
  {
4331
4919
  ...options,
4920
+ iconSets: projectConfig.icons,
4332
4921
  themes: projectConfig.themes,
4333
4922
  tileset: resolvedTileset
4334
4923
  }
@@ -4361,7 +4950,9 @@ function resolveSourceLayers(mapLayers, tilesetLayers) {
4361
4950
  }
4362
4951
  function shouldUseOsmBrightTemplate(config, basemap, theme) {
4363
4952
  const templateTheme = theme.custom || theme.name === "light" || theme.name === "dark";
4364
- return basemap.type === "osm" && templateTheme && !hasEffectiveColorOverrides(config.colors) && !config.typography && !config.density && !config.roads && !config.labels && !config.poi && !config.buildings && !config.modules?.some((moduleConfig) => moduleConfig.type !== "styleOverride") && !basemap.sourceLayers && !config.tiles?.sourceLayers;
4953
+ return basemap.type === "osm" && templateTheme && !hasEffectiveColorOverrides(config.colors) && !config.typography && !config.density && !config.roads && !config.buildings && !config.modules?.some(
4954
+ (moduleConfig) => moduleConfig.type !== "styleOverride" && moduleConfig.type !== "labels" && moduleConfig.type !== "poi"
4955
+ ) && !basemap.sourceLayers && !config.tiles?.sourceLayers;
4365
4956
  }
4366
4957
  function hasEffectiveColorOverrides(colors) {
4367
4958
  if (!colors) {
@@ -4371,13 +4962,13 @@ function hasEffectiveColorOverrides(colors) {
4371
4962
  if (key === "accent" || value === void 0) {
4372
4963
  return false;
4373
4964
  }
4374
- if (isRecord4(value)) {
4965
+ if (isRecord5(value)) {
4375
4966
  return Object.values(value).some((nestedValue) => typeof nestedValue === "string");
4376
4967
  }
4377
4968
  return typeof value === "string";
4378
4969
  });
4379
4970
  }
4380
- function isRecord4(value) {
4971
+ function isRecord5(value) {
4381
4972
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
4382
4973
  }
4383
4974
  function normalizeBaseUrl(value) {
@@ -4389,6 +4980,48 @@ function normalizeStyleBaseUrl(value) {
4389
4980
  function normalizeUrl(value) {
4390
4981
  return value.replace(/\/+$/, "");
4391
4982
  }
4983
+ function resolveIconSet(icons, iconSets, path = []) {
4984
+ if (!icons) {
4985
+ return void 0;
4986
+ }
4987
+ if (typeof icons === "string") {
4988
+ const referenced = iconSets?.[icons];
4989
+ if (referenced) {
4990
+ if (path.includes(icons)) {
4991
+ throw new Error(`Circular Tileflow icon set extends: ${[...path, icons].join(" -> ")}`);
4992
+ }
4993
+ return resolveIconSet(referenced, iconSets, [...path, icons]);
4994
+ }
4995
+ if (isSpriteReference(icons)) {
4996
+ return { sprite: icons };
4997
+ }
4998
+ return { source: icons };
4999
+ }
5000
+ const extended = icons.extends ? resolveIconSet(icons.extends, iconSets, path) : void 0;
5001
+ return {
5002
+ ...extended,
5003
+ ...icons,
5004
+ mapping: {
5005
+ ...extended?.mapping,
5006
+ ...icons.mapping
5007
+ }
5008
+ };
5009
+ }
5010
+ function resolveSprite(basemapSprite, iconSprite) {
5011
+ if (!basemapSprite) {
5012
+ return iconSprite;
5013
+ }
5014
+ if (!iconSprite || iconSprite === basemapSprite) {
5015
+ return basemapSprite;
5016
+ }
5017
+ return [
5018
+ { id: "default", url: basemapSprite },
5019
+ { id: customIconSpriteId, url: iconSprite }
5020
+ ];
5021
+ }
5022
+ function isSpriteReference(value) {
5023
+ return value.startsWith("http://") || value.startsWith("https://") || value.startsWith("/") || value.startsWith("data:");
5024
+ }
4392
5025
 
4393
5026
  // src/config.ts
4394
5027
  function defineTileflow(config) {
@@ -4401,9 +5034,11 @@ export {
4401
5034
  createStyleFromProject,
4402
5035
  defineTileflow,
4403
5036
  getDefaultColors2 as getDefaultColors,
5037
+ labels,
4404
5038
  osm,
4405
5039
  parseTileflowMap,
4406
5040
  parseTileflowProject,
5041
+ poi,
4407
5042
  roads,
4408
5043
  styleOverride,
4409
5044
  tileflowMapSchema,